import * as bootstrap from "bootstrap";
import "simplebar";
import ResizeObserver from "resize-observer-polyfill";
import { invoke } from "@tauri-apps/api/core";
import { debug, takeoverConsole } from "@fltsci/tauri-plugin-tracing";
import { DemoHttpRequest } from './bindings/DemoHttpRequest.ts';
import { DemoHttpExecutionPayload } from './bindings/DemoHttpExecutionPayload.ts';

(window as Window & typeof globalThis & { bootstrap?: typeof bootstrap }).bootstrap = bootstrap;
(window as Window & typeof globalThis & { ResizeObserver?: typeof ResizeObserver }).ResizeObserver = ResizeObserver;

interface DemoHttpPoolClientSnapshot {
    endpointName: string;
    provider: string;
    endpointUrl: string;
    roles: string[];
    status: string;
    pausedRemainingMs: number | null;
    availableConcurrencySlots: number;
}

let demoHttpLastResponseRawText = "";

function appendLogLine(textarea: HTMLTextAreaElement, line: string): void {
    const now = new Date();
    const timestamp = now.toLocaleTimeString("fr-CH", { hour12: false });

    const lines = textarea.value === "" ? [] : textarea.value.split("\n");
    lines.push(`[${timestamp}] ${line}`);

    const maxLines = 400;
    textarea.value = lines.slice(-maxLines).join("\n");
    textarea.scrollTop = textarea.scrollHeight;
}

function methodNeedsFirstArg(method: string): boolean {
    return [
        "getBalance",
        "getAccountInfo",
        "getProgramAccounts",
        "getSignaturesForAddress",
        "getTransaction",
        "sendTransaction",
    ].includes(method);
}

function firstArgLabelForMethod(method: string): string {
    if (method === "getBalance") return "Address";
    if (method === "getAccountInfo") return "Address";
    if (method === "getProgramAccounts") return "Program id";
    if (method === "getSignaturesForAddress") return "Address";
    if (method === "getTransaction") return "Signature";
    if (method === "sendTransaction") return "Encoded transaction (base64)";
    return "First arg";
}

function firstArgHelpForMethod(method: string): string {
    if (method === "getBalance") return "Adresse Solana dont on veut lire le solde.";
    if (method === "getAccountInfo") return "Adresse Solana du compte à lire.";
    if (method === "getProgramAccounts") return "Program id dont on veut lister les comptes.";
    if (method === "getSignaturesForAddress") return "Adresse Solana dont on veut lire les signatures récentes.";
    if (method === "getTransaction") return "Signature exacte d’une transaction.";
    if (method === "sendTransaction") {
        return "Transaction signée encodée en base64. Le preset fourni est volontairement invalide et sert seulement à tester la gestion d’erreur.";
    }
    return "Adresse, program id, signature ou transaction encodée selon la méthode.";
}

function configHelpForMethod(method: string): string {
    if (method === "getProgramAccounts") {
        return "Preset volontairement filtré pour éviter une réponse trop large.";
    }

    if (method === "sendTransaction") {
        return "Configuration d’envoi. Avec le preset de transaction invalide, la requête doit échouer côté RPC.";
    }

    return "Configuration JSON optionnelle.";
}

function defaultRoleForMethod(method: string): string {
    if (method === "sendTransaction") return "http_transactions";
    if (method === "getProgramAccounts") return "http_heavy";
    return "http_queries";
}

function defaultFirstArgForMethod(method: string): string {
    if (method === "getBalance") return "11111111111111111111111111111111";
    if (method === "getAccountInfo") return "11111111111111111111111111111111";
    if (method === "getProgramAccounts") return "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
    if (method === "getSignaturesForAddress") return "11111111111111111111111111111111";
    if (method === "getTransaction") {
        return "5h6xBEauJ3PK6SWC7r7J2W8mE1D7aQj4J6Jg8n1SmWnVqSg9H6gq2K7xwJkL2GZ2RZ6n9wYk9cW1b2V3a4d5e6f7";
    }
    if (method === "sendTransaction") return "AQ==";
    return "";
}

function defaultConfigForMethod(method: string): string {
    if (method === "getSlot" || method === "getBlockHeight" || method === "getLatestBlockhash") {
        return `{"commitment":"confirmed"}`;
    }

    if (method === "getBalance") {
        return `{"commitment":"confirmed"}`;
    }

    if (method === "getAccountInfo") {
        return `{"encoding":"base64","commitment":"confirmed"}`;
    }

    if (method === "getProgramAccounts") {
        return `{
  "encoding":"base64",
  "commitment":"confirmed",
  "dataSlice":{"offset":0,"length":0},
  "filters":[
    {"dataSize":165},
    {"memcmp":{"offset":32,"bytes":"11111111111111111111111111111111"}}
  ]
}`;
    }

    if (method === "getSignaturesForAddress") {
        return `{"limit":10,"commitment":"confirmed"}`;
    }

    if (method === "getTransaction") {
        return `{"encoding":"json","commitment":"confirmed","maxSupportedTransactionVersion":0}`;
    }

    if (method === "sendTransaction") {
        return `{"encoding":"base64","skipPreflight":true,"maxRetries":0}`;
    }

    return "";
}

function renderPoolSnapshots(
    snapshots: DemoHttpPoolClientSnapshot[],
    tableBody: HTMLTableSectionElement,
): void {
    tableBody.innerHTML = "";

    for (const snapshot of snapshots) {
        const row = document.createElement("tr");

        const endpointCell = document.createElement("td");
        endpointCell.innerHTML = `<div class="fw-semibold">${snapshot.endpointName}</div><div class="small text-body-secondary">${snapshot.endpointUrl}</div>`;

        const providerCell = document.createElement("td");
        providerCell.textContent = snapshot.provider;

        const statusCell = document.createElement("td");
        statusCell.textContent = snapshot.status;

        const pausedCell = document.createElement("td");
        pausedCell.textContent = snapshot.pausedRemainingMs === null ? "-" : String(snapshot.pausedRemainingMs);

        const concurrencyCell = document.createElement("td");
        concurrencyCell.textContent = String(snapshot.availableConcurrencySlots);

        const rolesCell = document.createElement("td");
        rolesCell.textContent = snapshot.roles.join(", ");

        row.appendChild(endpointCell);
        row.appendChild(providerCell);
        row.appendChild(statusCell);
        row.appendChild(pausedCell);
        row.appendChild(concurrencyCell);
        row.appendChild(rolesCell);

        tableBody.appendChild(row);
    }
}

function renderResponse(
    responseTextarea: HTMLTextAreaElement,
    prettyToggle: HTMLInputElement,
): void {
    if (demoHttpLastResponseRawText.trim() === "") {
        responseTextarea.value = "";
        return;
    }

    if (!prettyToggle.checked) {
        responseTextarea.value = demoHttpLastResponseRawText;
        return;
    }

    try {
        const parsed = JSON.parse(demoHttpLastResponseRawText);
        responseTextarea.value = JSON.stringify(parsed, null, 2);
    } catch {
        responseTextarea.value = demoHttpLastResponseRawText;
    }
}

async function copyTextToClipboard(text: string): Promise<void> {
    if (typeof navigator !== "undefined" && navigator.clipboard && navigator.clipboard.writeText) {
        await navigator.clipboard.writeText(text);
        return;
    }

    const tempTextarea = document.createElement("textarea");
    tempTextarea.value = text;
    tempTextarea.style.position = "fixed";
    tempTextarea.style.left = "-9999px";
    document.body.appendChild(tempTextarea);
    tempTextarea.focus();
    tempTextarea.select();
    document.execCommand("copy");
    document.body.removeChild(tempTextarea);
}

function updateMethodForm(
    roleSelect: HTMLSelectElement,
    methodSelect: HTMLSelectElement,
    firstArgGroup: HTMLDivElement,
    firstArgLabel: HTMLLabelElement,
    firstArgHelp: HTMLDivElement,
    firstArgInput: HTMLInputElement,
    configTextarea: HTMLTextAreaElement,
    configHelp: HTMLDivElement,
): void {
    const method = methodSelect.value;

    firstArgGroup.style.display = methodNeedsFirstArg(method) ? "" : "none";
    firstArgLabel.textContent = firstArgLabelForMethod(method);
    firstArgHelp.textContent = firstArgHelpForMethod(method);
    firstArgInput.value = defaultFirstArgForMethod(method);
    configTextarea.value = defaultConfigForMethod(method);
    configHelp.textContent = configHelpForMethod(method);
    roleSelect.value = defaultRoleForMethod(method);
}

async function refreshPoolSnapshot(
    tableBody: HTMLTableSectionElement,
    logTextarea: HTMLTextAreaElement,
    shouldLog: boolean,
): Promise<void> {
    try {
        const snapshots = await invoke<DemoHttpPoolClientSnapshot[]>("demo_http_list_pool_clients");
        renderPoolSnapshots(snapshots, tableBody);

        if (shouldLog) {
            appendLogLine(logTextarea, `[ui] refreshed http pool snapshot (${snapshots.length} endpoint(s))`);
        }
    } catch (error) {
        appendLogLine(logTextarea, `[ui] refresh pool error: ${String(error)}`);
    }
}

document.addEventListener("DOMContentLoaded", async () => {
    void takeoverConsole();
    debug("demo_http window loaded");

    const sidebarToggle = document.querySelector<HTMLButtonElement>('#sidebarToggle');
    if (sidebarToggle) {
        // restaurer l’état depuis localStorage
        if (localStorage.getItem('sidebar-toggle') === 'true') {
            document.body.classList.add('sidenav-toggled');
        }

        sidebarToggle.addEventListener('click', (event) => {
            event.preventDefault();
            document.body.classList.toggle('sidenav-toggled');
            localStorage.setItem('sidebar-toggle', document.body.classList.contains('sidenav-toggled') ? 'true' : 'false');
        });
    }

    const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
    Array.from(tooltipTriggerList).map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl));
    const toastElList = document.querySelectorAll('.toast');
    Array.from(toastElList).map(toastEl => new bootstrap.Toast(toastEl));
    const popoverTriggerList = document.querySelectorAll('[data-bs-toggle="popover"]');
    Array.from(popoverTriggerList).map(popoverTriggerEl => new bootstrap.Popover(popoverTriggerEl));

    const gobackto = location.pathname + location.search;

    document.querySelectorAll<HTMLAnchorElement>('a[data-setlang]').forEach((a) => {
        const href = a.getAttribute("href");
        if (!href) return; // pas de href => on ignore

        const url = new URL(href, location.origin);
        url.searchParams.set("gobackto", gobackto);

        // conserve une URL relative (path + query)
        a.setAttribute("href", url.pathname + "?" + url.searchParams.toString());
    });

    const roleSelect = document.querySelector<HTMLSelectElement>("#demoHttpRoleSelect");
    const methodSelect = document.querySelector<HTMLSelectElement>("#demoHttpMethodSelect");
    const firstArgGroup = document.querySelector<HTMLDivElement>("#demoHttpFirstArgGroup");
    const firstArgLabel = document.querySelector<HTMLLabelElement>("#demoHttpFirstArgLabel");
    const firstArgHelp = document.querySelector<HTMLDivElement>("#demoHttpFirstArgHelp");
    const firstArgInput = document.querySelector<HTMLInputElement>("#demoHttpFirstArgInput");
    const configTextarea = document.querySelector<HTMLTextAreaElement>("#demoHttpConfigTextarea");
    const configHelp = document.querySelector<HTMLDivElement>("#demoHttpConfigHelp");
    const executeButton = document.querySelector<HTMLButtonElement>("#demoHttpExecuteButton");
    const refreshPoolButton = document.querySelector<HTMLButtonElement>("#demoHttpRefreshPoolButton");
    const clearLogButton = document.querySelector<HTMLButtonElement>("#demoHttpClearLogButton");
    const copyResponseButton = document.querySelector<HTMLButtonElement>("#demoHttpCopyResponseButton");
    const prettyToggle = document.querySelector<HTMLInputElement>("#demoHttpPrettyToggle");
    const responseTextarea = document.querySelector<HTMLTextAreaElement>("#demoHttpResponseTextarea");
    const logTextarea = document.querySelector<HTMLTextAreaElement>("#demoHttpLogTextarea");
    const poolTableBody = document.querySelector<HTMLTableSectionElement>("#demoHttpPoolTableBody");
    const lastEndpointText = document.querySelector<HTMLSpanElement>("#demoHttpLastEndpointText");
    const lastProviderText = document.querySelector<HTMLSpanElement>("#demoHttpLastProviderText");
    const lastMethodClassText = document.querySelector<HTMLSpanElement>("#demoHttpLastMethodClassText");

    if (
        !roleSelect ||
        !methodSelect ||
        !firstArgGroup ||
        !firstArgLabel ||
        !firstArgHelp ||
        !firstArgInput ||
        !configTextarea ||
        !configHelp ||
        !executeButton ||
        !refreshPoolButton ||
        !clearLogButton ||
        !copyResponseButton ||
        !prettyToggle ||
        !responseTextarea ||
        !logTextarea ||
        !poolTableBody ||
        !lastEndpointText ||
        !lastProviderText ||
        !lastMethodClassText
    ) {
        debug("demo_http UI controls not found");
        return;
    }

    updateMethodForm(
        roleSelect,
        methodSelect,
        firstArgGroup,
        firstArgLabel,
        firstArgHelp,
        firstArgInput,
        configTextarea,
        configHelp,
    );

    methodSelect.addEventListener("change", () => {
        updateMethodForm(
            roleSelect,
            methodSelect,
            firstArgGroup,
            firstArgLabel,
            firstArgHelp,
            firstArgInput,
            configTextarea,
            configHelp,
        );
    });

    prettyToggle.addEventListener("change", () => {
        renderResponse(responseTextarea, prettyToggle);
    });

    refreshPoolButton.addEventListener("click", async () => {
        await refreshPoolSnapshot(poolTableBody, logTextarea, true);
    });

    clearLogButton.addEventListener("click", () => {
        logTextarea.value = "";
    });

    copyResponseButton.addEventListener("click", async () => {
        if (responseTextarea.value.trim() === "") {
            appendLogLine(logTextarea, "[ui] copy skipped: no response available");
            return;
        }

        try {
            await copyTextToClipboard(responseTextarea.value);
            appendLogLine(logTextarea, "[ui] response copied to clipboard");
        } catch (error) {
            appendLogLine(logTextarea, `[ui] copy response error: ${String(error)}`);
        }
    });

    executeButton.addEventListener("click", async () => {
        const request: DemoHttpRequest = {
            role: roleSelect.value,
            method: methodSelect.value,
            firstArg: firstArgInput.value.trim() === "" ? null : firstArgInput.value.trim(),
            configJson: configTextarea.value.trim() === "" ? null : configTextarea.value.trim(),
        };

        try {
            const response = await invoke<DemoHttpExecutionPayload>("demo_http_execute_request", { request });

            lastEndpointText.textContent = `${response.endpointName} (${response.endpointUrl})`;
            lastProviderText.textContent = response.provider;
            lastMethodClassText.textContent = response.methodClass;

            demoHttpLastResponseRawText = response.responseJson;
            renderResponse(responseTextarea, prettyToggle);

            appendLogLine(
                logTextarea,
                `[ui] ${response.method} via ${response.endpointName} / role=${response.role} / class=${response.methodClass}`,
            );

            await refreshPoolSnapshot(poolTableBody, logTextarea, true);
        } catch (error) {
            demoHttpLastResponseRawText = String(error);
            renderResponse(responseTextarea, prettyToggle);

            appendLogLine(logTextarea, `[ui] execute error: ${String(error)}`);
            await refreshPoolSnapshot(poolTableBody, logTextarea, true);
        }
    });

    appendLogLine(logTextarea, "[ui] demo_http window loaded");
    await refreshPoolSnapshot(poolTableBody, logTextarea, true);
});