// file: kb_demo_app/frontend/ts/demo3old.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 { Demo3oldLocalDexCorpusSearchRequest } from "./bindings/Demo3oldLocalDexCorpusSearchRequest.ts";
import type { Demo3oldLocalDexCorpusSearchPayload } from "./bindings/Demo3oldLocalDexCorpusSearchPayload.ts";
import type { Demo3oldLocalDexCorpusSearchSummary  } from "./bindings/Demo3oldLocalDexCorpusSearchSummary.ts";
import type { Demo3oldLocalDexCorpusTransactionSample  } from "./bindings/Demo3oldLocalDexCorpusTransactionSample.ts";
import type { Demo3oldLocalDexCorpusPoolPairSample  } from "./bindings/Demo3oldLocalDexCorpusPoolPairSample.ts";
import type { Demo3oldLocalDexCorpusDecodedEventSample  } from "./bindings/Demo3oldLocalDexCorpusDecodedEventSample.ts";

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



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

const presets: Demo3oldPreset[] = [
    {
        label: "PumpSwap",
        dexCode: "pump_swap",
        programId: "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA",
        description: "DEX effectif PumpSwap.",
    },
    {
        label: "Raydium CPMM",
        dexCode: "raydium_cpmm",
        programId: "CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C",
        description: "Raydium CPMM, swap AMM moderne.",
    },
    {
        label: "Raydium CLMM",
        dexCode: "raydium_clmm",
        programId: "CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK",
        description: "Raydium CLMM, actuellement observé dans le corpus local.",
    },
    {
        label: "Raydium AMM v4",
        dexCode: "raydium_amm_v4",
        programId: "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8",
        description: "Raydium AMM v4 legacy. À rechercher par corpus avant décodage swap.",
    },
    {
        label: "Raydium Stable Swap",
        dexCode: "raydium_stable_swap",
        programId: "5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h",
        description: "Stable Swap Raydium à vérifier par transactions locales.",
    },
    {
        label: "Raydium Router",
        dexCode: "raydium_router",
        programId: "routeUGWgWzqBWFcrCfv8tritsqukccJPu3q5GPP3xS",
        description: "Router Raydium. Ne doit pas être promu comme DEX direct sans preuve.",
    },
    {
        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, swaps non actionnables sans montants exploitables.",
    },
    {
        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, bonding curve / DEX effectif partiel.",
    },
    {
        label: "Orca Whirlpools",
        dexCode: "orca_whirlpools",
        programId: "whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc",
        description: "Orca Whirlpools CLMM.",
    },
    {
        label: "FluxBeam",
        dexCode: "fluxbeam",
        programId: "FLUXubRmkEi2q6K3Y9kBPg9248ggaZVsoSFhtJHSrm1X",
        description: "FluxBeam AMM à consolider par corpus.",
    },
    {
        label: "DexLab",
        dexCode: "dexlab",
        programId: "DSwpgjMvXhtGn6BsbqmacdBZyfLj6jSWf3HJpdJtmg6N",
        description: "DexLab Swap/Pool à consolider par corpus.",
    },
    {
        label: "metaDAO",
        dexCode: "metadao",
        programId: "",
        description: "DEX à vérifier. Aucun program id ne doit être inventé.",
    },
    {
        label: "Printr",
        dexCode: "printr",
        programId: "",
        description: "DEX à vérifier. Aucun program id ne doit être 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();
    if (trimmed === "") {
        return null;
    }
    return trimmed;
}

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

function displayNullable(value: string | number | null): string {
    if (value === null) {
        return "-";
    }
    return String(value);
}

function boolBadge(value: boolean): string {
    if (value) {
        return '<span class="badge text-bg-success">yes</span>';
    }
    return '<span class="badge text-bg-secondary">no</span>';
}

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 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}`);
    textarea.value = lines.slice(-400).join("\n");
    textarea.scrollTop = textarea.scrollHeight;
}

function setStatus(statusBadge: HTMLElement, label: string, cssClass: string): void {
    statusBadge.className = `badge ${cssClass}`;
    statusBadge.textContent = label;
}

function readRequest(): Demo3oldLocalDexCorpusSearchRequest {
    const limitInput = byId<HTMLInputElement>("demo3oldLimitInput");
    const parsedLimit = Number.parseInt(limitInput.value, 10);
    const limit = Number.isFinite(parsedLimit) && parsedLimit > 0 ? parsedLimit : 50;
    return {
        dexCode: valueOrNull(byId<HTMLInputElement>("demo3oldDexCodeInput").value),
        programId: valueOrNull(byId<HTMLInputElement>("demo3oldProgramIdInput").value),
        pairId: numberValueOrNull(byId<HTMLInputElement>("demo3oldPairIdInput").value),
        poolAddress: valueOrNull(byId<HTMLInputElement>("demo3oldPoolAddressInput").value),
        tokenMint: valueOrNull(byId<HTMLInputElement>("demo3oldTokenMintInput").value),
        signature: valueOrNull(byId<HTMLInputElement>("demo3oldSignatureInput").value),
        limit,
    };
}

function populatePresetSelect(select: HTMLSelectElement): void {
    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>("demo3oldDexCodeInput").value = preset.dexCode;
    byId<HTMLInputElement>("demo3oldProgramIdInput").value = preset.programId;
    byId<HTMLElement>("demo3oldPresetHelp").textContent = preset.description;
}

function clearFilters(): void {
    byId<HTMLInputElement>("demo3oldDexCodeInput").value = "";
    byId<HTMLInputElement>("demo3oldProgramIdInput").value = "";
    byId<HTMLInputElement>("demo3oldPairIdInput").value = "";
    byId<HTMLInputElement>("demo3oldPoolAddressInput").value = "";
    byId<HTMLInputElement>("demo3oldTokenMintInput").value = "";
    byId<HTMLInputElement>("demo3oldSignatureInput").value = "";
    byId<HTMLSelectElement>("demo3oldPresetSelect").value = "";
    byId<HTMLElement>("demo3oldPresetHelp").textContent = "Choisis un preset ou saisis les filtres manuellement.";
}

function renderSummary(summary: Demo3oldLocalDexCorpusSearchSummary): void {
    byId<HTMLElement>("demo3oldSummaryTransactionCount").textContent = String(summary.transactionCount);
    byId<HTMLElement>("demo3oldSummaryInstructionCount").textContent = String(summary.instructionCount);
    byId<HTMLElement>("demo3oldSummaryDecodedEventCount").textContent = String(summary.decodedEventCount);
    byId<HTMLElement>("demo3oldSummaryPoolCount").textContent = String(summary.poolCount);
    byId<HTMLElement>("demo3oldSummaryPairCount").textContent = String(summary.pairCount);
    byId<HTMLElement>("demo3oldSummaryTradeEventCount").textContent = String(summary.tradeEventCount);
    byId<HTMLElement>("demo3oldSummaryCandleCount").textContent = String(summary.pairCandleCount);
    byId<HTMLElement>("demo3oldSummaryProtocolCandidateCount").textContent = String(summary.protocolCandidateCount);
}

function renderTransactionSamples(samples: Demo3oldLocalDexCorpusTransactionSample[]): void {
    const tbody = byId<HTMLTableSectionElement>("demo3oldTransactionTableBody");
    tbody.innerHTML = "";
    if (samples.length === 0) {
        tbody.innerHTML = '<tr><td colspan="8" class="text-body-secondary">No transaction sample.</td></tr>';
        return;
    }
    samples.forEach((sample) => {
        const tr = document.createElement("tr");
        tr.innerHTML = `
            <td class="font-monospace" title="${escapeHtml(sample.signature)}">${escapeHtml(shortText(sample.signature, 18))}</td>
            <td>${displayNullable(sample.slot)}</td>
            <td>${boolBadge(sample.failed)}</td>
            <td>${sample.instructionCount}</td>
            <td>${sample.decodedEventCount}</td>
            <td>${sample.tradeEventCount}</td>
            <td class="font-monospace" title="${escapeHtml(sample.programIdsCsv)}">${escapeHtml(shortText(sample.programIdsCsv, 44))}</td>
            <td>${sample.transactionId}</td>
        `;
        tbody.appendChild(tr);
    });
}

function renderPoolPairSamples(samples: Demo3oldLocalDexCorpusPoolPairSample[]): void {
    const tbody = byId<HTMLTableSectionElement>("demo3oldPoolPairTableBody");
    tbody.innerHTML = "";
    if (samples.length === 0) {
        tbody.innerHTML = '<tr><td colspan="9" class="text-body-secondary">No pool/pair sample.</td></tr>';
        return;
    }
    samples.forEach((sample) => {
        const baseLabel = `${displayNullable(sample.baseSymbol)} / ${shortText(sample.baseMint, 12)}`;
        const quoteLabel = `${displayNullable(sample.quoteSymbol)} / ${shortText(sample.quoteMint, 12)}`;
        const tr = document.createElement("tr");
        tr.innerHTML = `
            <td>${displayNullable(sample.dexCode)}</td>
            <td title="${escapeHtml(displayNullable(sample.poolAddress))}" class="font-monospace">${escapeHtml(shortText(sample.poolAddress, 16))}</td>
            <td>${displayNullable(sample.pairId)}</td>
            <td>${escapeHtml(displayNullable(sample.pairSymbol))}</td>
            <td class="font-monospace" title="${escapeHtml(displayNullable(sample.baseMint))}">${escapeHtml(baseLabel)}</td>
            <td class="font-monospace" title="${escapeHtml(displayNullable(sample.quoteMint))}">${escapeHtml(quoteLabel)}</td>
            <td>${sample.decodedEventCount}</td>
            <td>${sample.tradeEventCount}</td>
            <td>${sample.pairCandleCount}</td>
        `;
        tbody.appendChild(tr);
    });
}

function renderDecodedEventSamples(samples: Demo3oldLocalDexCorpusDecodedEventSample[]): void {
    const tbody = byId<HTMLTableSectionElement>("demo3oldDecodedEventTableBody");
    tbody.innerHTML = "";
    if (samples.length === 0) {
        tbody.innerHTML = '<tr><td colspan="11" class="text-body-secondary">No decoded event sample.</td></tr>';
        return;
    }
    samples.forEach((sample) => {
        const tr = document.createElement("tr");
        tr.innerHTML = `
            <td>${sample.decodedEventId}</td>
            <td class="font-monospace" title="${escapeHtml(sample.signature)}">${escapeHtml(shortText(sample.signature, 16))}</td>
            <td>${displayNullable(sample.slot)}</td>
            <td>${escapeHtml(sample.protocolName)}</td>
            <td>${escapeHtml(sample.eventKind)}</td>
            <td class="font-monospace" title="${escapeHtml(sample.programId)}">${escapeHtml(shortText(sample.programId, 16))}</td>
            <td class="font-monospace" title="${escapeHtml(displayNullable(sample.poolAccount))}">${escapeHtml(shortText(sample.poolAccount, 16))}</td>
            <td>${escapeHtml(displayNullable(sample.eventCategory))}</td>
            <td>${escapeHtml(displayNullable(sample.eventActionability))}</td>
            <td>${boolBadge(sample.tradeCandidate)}</td>
            <td>${boolBadge(sample.candleCandidate)}</td>
        `;
        tbody.appendChild(tr);
    });
}

function renderResult(payload: Demo3oldLocalDexCorpusSearchPayload): void {
    lastResultJson = payload.resultJson;
    byId<HTMLElement>("demo3oldDatabaseUrlText").textContent = payload.databaseUrl;
    byId<HTMLTextAreaElement>("demo3oldJsonTextarea").value = payload.resultJson;
    renderSummary(payload.result.summary);
    renderTransactionSamples(payload.result.transactionSamples);
    renderPoolPairSamples(payload.result.poolPairSamples);
    renderDecodedEventSamples(payload.result.decodedEventSamples);
}

async function runSearch(): Promise<void> {
    const statusBadge = byId<HTMLElement>("demo3oldStatusBadge");
    const logTextarea = byId<HTMLTextAreaElement>("demo3oldLogTextarea");
    setStatus(statusBadge, "Searching", "text-bg-warning");
    appendLogLine(logTextarea, "launching local DEX corpus search");
    try {
        const request = readRequest();
        const payload = await invoke<Demo3oldLocalDexCorpusSearchPayload>("demo3old_search_local_dex_corpus", { request });
        renderResult(payload);
        appendLogLine(
            logTextarea,
            `search completed: tx='${payload.result.summary.transactionCount}', pools='${payload.result.summary.poolCount}', pairs='${payload.result.summary.pairCount}', decoded='${payload.result.summary.decodedEventCount}'`,
        );
        setStatus(statusBadge, "Ready", "text-bg-success");
    } catch (error) {
        appendLogLine(logTextarea, `search failed: ${String(error)}`);
        setStatus(statusBadge, "Error", "text-bg-danger");
    }
}

async function copyJson(): Promise<void> {
    const logTextarea = byId<HTMLTextAreaElement>("demo3oldLogTextarea");
    if (lastResultJson === "") {
        appendLogLine(logTextarea, "no JSON result to copy");
        return;
    }
    try {
        await navigator.clipboard.writeText(lastResultJson);
        appendLogLine(logTextarea, "JSON result copied");
    } catch (error) {
        appendLogLine(logTextarea, `copy failed: ${String(error)}`);
    }
}

document.addEventListener("DOMContentLoaded", () => {
    void takeoverConsole();
    debug("demo3old 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 presetSelect = byId<HTMLSelectElement>("demo3oldPresetSelect");
    populatePresetSelect(presetSelect);
    presetSelect.addEventListener("change", () => {
        applyPreset(presetSelect.value);
    });

    byId<HTMLButtonElement>("demo3oldSearchButton").addEventListener("click", () => {
        void runSearch();
    });
    byId<HTMLButtonElement>("demo3oldClearFiltersButton").addEventListener("click", () => {
        clearFilters();
    });
    byId<HTMLButtonElement>("demo3oldCopyJsonButton").addEventListener("click", () => {
        void copyJson();
    });
    byId<HTMLButtonElement>("demo3oldClearLogButton").addEventListener("click", () => {
        byId<HTMLTextAreaElement>("demo3oldLogTextarea").value = "";
    });

    applyPreset("3");
});
