// file: kb_demo_app/frontend/ts/demo3.ts

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 type { Demo3LocalDexCorpusSearchRequest } from "./bindings/Demo3LocalDexCorpusSearchRequest.ts";
import type { Demo3LocalDexCorpusSearchPayload } from "./bindings/Demo3LocalDexCorpusSearchPayload.ts";
import type { Demo3OnchainDexDiscoveryRequest  } from "./bindings/Demo3OnchainDexDiscoveryRequest.ts";
import type { Demo3OnchainDexDiscoveryResult  } from "./bindings/Demo3OnchainDexDiscoveryResult.ts";
import type { Demo3OnchainDexPairCandidate  } from "./bindings/Demo3OnchainDexPairCandidate.ts";
import type { Demo3LocalDexCorpusSearchResult  } from "./bindings/Demo3LocalDexCorpusSearchResult.ts";
import type { Demo3OnchainDexDiscoveryPayload  } from "./bindings/Demo3OnchainDexDiscoveryPayload.ts";

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


interface Demo3Preset {
    label: string;
    dexCode: string;
    programId: string;
    description: string;
}

const presets: Demo3Preset[] = [
    { label: "PumpSwap", dexCode: "pump_swap", programId: "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA", description: "DEX effectif PumpSwap." },
    { label: "Raydium CPMM", dexCode: "raydium_cpmm", programId: "CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C", description: "Raydium CPMM." },
    { label: "Raydium CLMM", dexCode: "raydium_clmm", programId: "CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK", description: "Raydium CLMM." },
    { label: "Raydium AMM v4", dexCode: "raydium_amm_v4", programId: "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8", description: "Raydium AMM v4 legacy. À prouver par corpus avant décodage swap." },
    { label: "Raydium Stable Swap", dexCode: "raydium_stable_swap", programId: "5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h", description: "Stable Swap Raydium à vérifier par corpus." },
    { label: "Meteora DLMM", dexCode: "meteora_dlmm", programId: "LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo", description: "Meteora DLMM." },
    { label: "Meteora DAMM v1", dexCode: "meteora_damm_v1", programId: "Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB", description: "Meteora DAMM v1." },
    { label: "Meteora DAMM v2", dexCode: "meteora_damm_v2", programId: "cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG", description: "Meteora DAMM v2." },
    { label: "Meteora DBC", dexCode: "meteora_dbc", programId: "dbcij3LWUppWqq96dh6gJWwBifmcGfLSB5D4DuSMaqN", description: "Meteora DBC." },
    { label: "Orca Whirlpools", dexCode: "orca_whirlpools", programId: "whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc", description: "Orca Whirlpools CLMM." },
    { label: "FluxBeam", dexCode: "fluxbeam", programId: "FLUXubRmkEi2q6K3Y9kBPg9248ggaZVsoSFhtJHSrm1X", description: "FluxBeam." },
    { label: "DexLab", dexCode: "dexlab", programId: "DSwpgjMvXhtGn6BsbqmacdBZyfLj6jSWf3HJpdJtmg6N", description: "DexLab Swap/Pool." },
    { label: "metaDAO", dexCode: "metadao", programId: "", description: "DEX à vérifier. Aucun program id n'est inventé." },
    { label: "Printr", dexCode: "printr", programId: "", description: "DEX à vérifier. Aucun program id n'est inventé." },
];

let lastResultJson = "";

function byId<T extends HTMLElement>(id: string): T {
    const element = document.getElementById(id);
    if (element === null) {
        throw new Error(`missing element #${id}`);
    }
    return element as T;
}

function valueOrNull(value: string): string | null {
    const trimmed = value.trim();
    return trimmed === "" ? null : trimmed;
}

function numberValueOrNull(value: string): number | null {
    const trimmed = value.trim();
    if (trimmed === "") {
        return null;
    }
    const parsed = Number.parseInt(trimmed, 10);
    return Number.isFinite(parsed) ? parsed : null;
}

function intValue(id: string, fallback: number): number {
    const parsed = Number.parseInt(byId<HTMLInputElement>(id).value, 10);
    return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
}

function escapeHtml(value: string): string {
    return value
        .replace(/&/g, "&amp;")
        .replace(/</g, "&lt;")
        .replace(/>/g, "&gt;")
        .replace(/"/g, "&quot;")
        .replace(/'/g, "&#039;");
}

function shortText(value: string | null, maxLength: number): string {
    if (value === null) {
        return "-";
    }
    if (value.length <= maxLength) {
        return value;
    }
    return `${value.slice(0, maxLength)}…`;
}

function shortList(values: string[], maxItems: number, itemLength: number): string {
    if (values.length === 0) {
        return "-";
    }
    return values.slice(0, maxItems).map((value) => shortText(value, itemLength)).join(", ");
}

function candidateAccountList(accounts: Demo3OnchainDexPairCandidate["candidatePoolAccounts"], maxItems: number): string {
    if (accounts.length === 0) {
        return "-";
    }
    return accounts.slice(0, maxItems).map((account) => shortText(account.address, 14)).join(", ");
}

function tokenDeltaList(candidate: Demo3OnchainDexPairCandidate): string {
    if (candidate.tokenBalanceDeltas.length === 0) {
        return "-";
    }
    return candidate.tokenBalanceDeltas.slice(0, 4).map((delta) => {
        const amount = delta.deltaRaw ?? "?";
        return `${shortText(delta.mint, 10)}:${amount}`;
    }).join(", ");
}

function appendLogLine(line: string): void {
    const textarea = byId<HTMLTextAreaElement>("demo3LogTextarea");
    const timestamp = new Date().toLocaleTimeString("fr-CH", { hour12: false });
    const lines = textarea.value === "" ? [] : textarea.value.split("\n");
    lines.push(`[${timestamp}] ${line}`);
    textarea.value = lines.slice(-400).join("\n");
    textarea.scrollTop = textarea.scrollHeight;
}

function setStatus(label: string, cssClass: string): void {
    const badge = byId<HTMLElement>("demo3StatusBadge");
    badge.className = `badge ${cssClass}`;
    badge.textContent = label;
}

function populatePresetSelect(): void {
    const select = byId<HTMLSelectElement>("demo3PresetSelect");
    select.innerHTML = '<option value="">Custom / empty</option>';
    presets.forEach((preset, index) => {
        const option = document.createElement("option");
        option.value = String(index);
        option.textContent = preset.label;
        select.appendChild(option);
    });
}

function applyPreset(indexText: string): void {
    if (indexText === "") {
        return;
    }
    const index = Number.parseInt(indexText, 10);
    if (!Number.isFinite(index) || index < 0 || index >= presets.length) {
        return;
    }
    const preset = presets[index];
    byId<HTMLInputElement>("demo3DexCodeInput").value = preset.dexCode;
    byId<HTMLInputElement>("demo3ProgramIdInput").value = preset.programId;
    byId<HTMLElement>("demo3PresetHelp").textContent = preset.description;
}

function readOnchainRequest(): Demo3OnchainDexDiscoveryRequest {
    return {
        dexCode: valueOrNull(byId<HTMLInputElement>("demo3DexCodeInput").value),
        programId: valueOrNull(byId<HTMLInputElement>("demo3ProgramIdInput").value),
        httpRole: byId<HTMLInputElement>("demo3HttpRoleInput").value.trim() || "history_backfill",
        signatureLimit: intValue("demo3SignatureLimitInput", 50),
        transactionLimit: intValue("demo3TransactionLimitInput", 25),
        candidateLimit: intValue("demo3CandidateLimitInput", 25),
    };
}

function readLocalRequest(): Demo3LocalDexCorpusSearchRequest {
    return {
        dexCode: valueOrNull(byId<HTMLInputElement>("demo3DexCodeInput").value),
        programId: valueOrNull(byId<HTMLInputElement>("demo3ProgramIdInput").value),
        pairId: numberValueOrNull(byId<HTMLInputElement>("demo3PairIdInput").value),
        poolAddress: valueOrNull(byId<HTMLInputElement>("demo3PoolAddressInput").value),
        tokenMint: valueOrNull(byId<HTMLInputElement>("demo3TokenMintInput").value),
        signature: valueOrNull(byId<HTMLInputElement>("demo3SignatureInput").value),
        limit: intValue("demo3CandidateLimitInput", 25),
    };
}

function clearFilters(): void {
    byId<HTMLInputElement>("demo3DexCodeInput").value = "";
    byId<HTMLInputElement>("demo3ProgramIdInput").value = "";
    byId<HTMLInputElement>("demo3PairIdInput").value = "";
    byId<HTMLInputElement>("demo3PoolAddressInput").value = "";
    byId<HTMLInputElement>("demo3TokenMintInput").value = "";
    byId<HTMLInputElement>("demo3SignatureInput").value = "";
    byId<HTMLSelectElement>("demo3PresetSelect").value = "";
    byId<HTMLElement>("demo3PresetHelp").textContent = "Choisis un DEX ou saisis un program id manuellement.";
}

function renderOnchainResult(result: Demo3OnchainDexDiscoveryResult): void {
    byId<HTMLElement>("demo3SummarySignatureCount").textContent = String(result.fetchedSignatureCount);
    byId<HTMLElement>("demo3SummaryFetchedTxCount").textContent = String(result.fetchedTransactionCount);
    byId<HTMLElement>("demo3SummaryMissingTxCount").textContent = String(result.missingTransactionCount);
    byId<HTMLElement>("demo3SummaryFailedTxCount").textContent = String(result.failedTransactionCount);
    byId<HTMLElement>("demo3SummaryCandidateCount").textContent = String(result.candidateCount);
    byId<HTMLElement>("demo3TargetText").textContent = `${result.resolvedDexCode ?? "custom"} / ${result.resolvedProgramId}`;
    renderOnchainCandidates(result.candidates);
}

function renderOnchainCandidates(candidates: Demo3OnchainDexPairCandidate[]): void {
    const body = byId<HTMLTableSectionElement>("demo3OnchainCandidateTableBody");
    if (candidates.length === 0) {
        body.innerHTML = '<tr><td colspan="11" class="text-body-secondary">No on-chain candidate.</td></tr>';
        return;
    }
    body.innerHTML = candidates.map((candidate) => {
        const verifiedPool = candidate.verifiedPoolAddress;
        const firstCandidatePool = candidate.candidatePoolAccounts.length > 0 ? candidate.candidatePoolAccounts[0].address : null;
        const poolForFilter = verifiedPool ?? candidate.poolAddress ?? firstCandidatePool;
        if (poolForFilter !== null) {
            byId<HTMLInputElement>("demo3PoolAddressInput").value = poolForFilter;
        }
        if (candidate.tokenAMint !== null && byId<HTMLInputElement>("demo3TokenMintInput").value.trim() === "") {
            byId<HTMLInputElement>("demo3TokenMintInput").value = candidate.tokenAMint;
        }
        if (candidate.tokenAMint === null && candidate.observedTokenMints.length > 0 && byId<HTMLInputElement>("demo3TokenMintInput").value.trim() === "") {
            byId<HTMLInputElement>("demo3TokenMintInput").value = candidate.observedTokenMints[0];
        }
        byId<HTMLInputElement>("demo3SignatureInput").value = candidate.signature;
        const accountTitle = candidate.candidatePoolAccounts.map((account) => `${account.address} (${account.reason})`).join("\n");
        const vaultTitle = candidate.candidateTokenVaultAccounts.map((account) => `${account.address} (${account.reason})`).join("\n");
        return `
            <tr>
                <td class="font-monospace" title="${escapeHtml(candidate.signature)}">${escapeHtml(shortText(candidate.signature, 18))}</td>
                <td>${candidate.slot ?? "-"}</td>
                <td><span class="badge text-bg-info">${escapeHtml(candidate.candidateKind)}</span></td>
                <td><span class="badge text-bg-${candidate.confidence === "high" ? "success" : candidate.confidence === "medium" ? "warning" : "secondary"}">${escapeHtml(candidate.confidence)}</span></td>
                <td class="font-monospace" title="${escapeHtml(verifiedPool ?? "")}">${escapeHtml(shortText(verifiedPool, 14))}</td>
                <td class="font-monospace" title="${escapeHtml(candidate.tokenAMint ?? "")}">${escapeHtml(shortText(candidate.tokenAMint, 14))}</td>
                <td class="font-monospace" title="${escapeHtml(candidate.tokenBMint ?? "")}">${escapeHtml(shortText(candidate.tokenBMint, 14))}</td>
                <td class="font-monospace" title="${escapeHtml(candidate.observedTokenMints.join("\n"))}">${escapeHtml(shortList(candidate.observedTokenMints, 3, 10))}</td>
                <td class="font-monospace" title="${escapeHtml(tokenDeltaList(candidate))}">${escapeHtml(tokenDeltaList(candidate))}</td>
                <td class="font-monospace" title="${escapeHtml(accountTitle)}">${escapeHtml(candidateAccountList(candidate.candidatePoolAccounts, 3))}<br /><span class="text-body-secondary">vaults: ${escapeHtml(candidateAccountList(candidate.candidateTokenVaultAccounts, 2))}</span></td>
                <td class="small" title="${escapeHtml(vaultTitle)}">${escapeHtml(candidate.backfillHint)}</td>
            </tr>`;
    }).join("");
}

function renderLocalResult(result: Demo3LocalDexCorpusSearchResult): void {
    byId<HTMLElement>("demo3SummaryLocalPairCount").textContent = String(result.summary.pairCount);
    const body = byId<HTMLTableSectionElement>("demo3LocalPoolPairTableBody");
    if (result.poolPairSamples.length === 0) {
        body.innerHTML = '<tr><td colspan="8" class="text-body-secondary">No local pool/pair sample.</td></tr>';
        return;
    }
    body.innerHTML = result.poolPairSamples.map((sample) => `
        <tr>
            <td>${escapeHtml(sample.dexCode ?? "-")}</td>
            <td class="font-monospace" title="${escapeHtml(sample.poolAddress ?? "")}">${escapeHtml(shortText(sample.poolAddress, 16))}</td>
            <td>${sample.pairId ?? "-"}</td>
            <td>${escapeHtml(sample.pairSymbol ?? "-")}</td>
            <td class="font-monospace" title="${escapeHtml(sample.baseMint ?? "")}">${escapeHtml(sample.baseSymbol ?? shortText(sample.baseMint, 12))}</td>
            <td class="font-monospace" title="${escapeHtml(sample.quoteMint ?? "")}">${escapeHtml(sample.quoteSymbol ?? shortText(sample.quoteMint, 12))}</td>
            <td>${sample.tradeEventCount}</td>
            <td>${sample.pairCandleCount}</td>
        </tr>`).join("");
}

async function discoverOnchain(): Promise<void> {
    const request = readOnchainRequest();
    setStatus("running", "text-bg-warning");
    appendLogLine(`on-chain discovery dex='${request.dexCode ?? ""}' program='${request.programId ?? ""}' role='${request.httpRole}'`);
    try {
        const payload = await invoke<Demo3OnchainDexDiscoveryPayload>("demo3_discover_onchain_dex_pairs", { request });
        lastResultJson = payload.resultJson;
        byId<HTMLTextAreaElement>("demo3JsonTextarea").value = payload.resultJson;
        renderOnchainResult(payload.result);
        setStatus("ok", "text-bg-success");
        appendLogLine(`on-chain discovery completed: candidates='${payload.result.candidateCount}' signatures='${payload.result.fetchedSignatureCount}'`);
    } catch (error) {
        setStatus("error", "text-bg-danger");
        appendLogLine(`on-chain discovery failed: ${String(error)}`);
    }
}

async function searchLocalDb(): Promise<void> {
    const request = readLocalRequest();
    setStatus("running", "text-bg-warning");
    appendLogLine("local DB search started");
    try {
        const payload = await invoke<Demo3LocalDexCorpusSearchPayload>("demo3_search_local_dex_corpus", { request });
        lastResultJson = payload.resultJson;
        byId<HTMLTextAreaElement>("demo3JsonTextarea").value = payload.resultJson;
        renderLocalResult(payload.result);
        setStatus("ok", "text-bg-success");
        appendLogLine(`local DB search completed: pairs='${payload.result.summary.pairCount}' tx='${payload.result.summary.transactionCount}'`);
    } catch (error) {
        setStatus("error", "text-bg-danger");
        appendLogLine(`local DB search failed: ${String(error)}`);
    }
}

async function copyJson(): Promise<void> {
    if (lastResultJson === "") {
        appendLogLine("nothing to copy");
        return;
    }
    try {
        await navigator.clipboard.writeText(lastResultJson);
        appendLogLine("JSON copied to clipboard");
    } catch (error) {
        appendLogLine(`copy failed: ${String(error)}`);
    }
}

function init(): void {
    takeoverConsole();
    void debug("demo3 on-chain discovery initialized");
    debug("demo3 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());
    });

    populatePresetSelect();
    byId<HTMLSelectElement>("demo3PresetSelect").addEventListener("change", (event) => {
        applyPreset((event.target as HTMLSelectElement).value);
    });
    byId<HTMLButtonElement>("demo3DiscoverButton").addEventListener("click", () => {
        void discoverOnchain();
    });
    byId<HTMLButtonElement>("demo3LocalSearchButton").addEventListener("click", () => {
        void searchLocalDb();
    });
    byId<HTMLButtonElement>("demo3ClearFiltersButton").addEventListener("click", clearFilters);
    byId<HTMLButtonElement>("demo3CopyJsonButton").addEventListener("click", () => {
        void copyJson();
    });
    byId<HTMLButtonElement>("demo3ClearLogButton").addEventListener("click", () => {
        byId<HTMLTextAreaElement>("demo3LogTextarea").value = "";
    });
    appendLogLine("ready");
}

document.addEventListener("DOMContentLoaded", init);
