/**
 * SC2 Replay Analyzer - Web GUI Application
 * Vanilla JS with Chart.js for MMR graph
 */

const App = (() => {
    // ============================================================
    // CONSTANTS
    // ============================================================

    const REFRESH_INTERVAL_MS = 2000;
    const MMR_MIN_VALID = 2000;
    const MMR_MAX_VALID = 8000;
    const ADAPTIVE_PADDING_PERCENT = 0.18;
    const MIN_Y_RANGE = 200;

    const TAG_COLORS = [
        "#00d4ff", "#b966ff", "#ffd700",
        "#ff6bcd", "#00ffc8", "#6b9dff",
    ];

    // ============================================================
    // STATE
    // ============================================================

    let state = {
        filters: {
            matchup: "",
            result: "",
            map: "",
            days: "",
            limit: "50",
            player_race: "",
            region: "",
        },
        columns: { current: [], available: {} },
        lastReplayCount: null,
        chart: null,
        config: {},
        replays: [],
        selectedReplayId: null,
        analysisFields: [],
    };

    // ============================================================
    // API CLIENT
    // ============================================================

    async function api(endpoint, options = {}) {
        const url = options.params
            ? `${endpoint}?${new URLSearchParams(options.params)}`
            : endpoint;

        const fetchOptions = {};
        if (options.method) fetchOptions.method = options.method;
        if (options.body) {
            fetchOptions.headers = { "Content-Type": "application/json" };
            fetchOptions.body = JSON.stringify(options.body);
        }

        const response = await fetch(url, fetchOptions);
        return response.json();
    }

    const API = {
        getReplays: (params) => api("/api/v1/replays", { params }),
        getLatest: () => api("/api/v1/replays/latest"),
        getStats: (params) => api("/api/v1/stats", { params }),
        getMaps: () => api("/api/v1/maps"),
        getRaces: () => api("/api/v1/races"),
        getRegions: () => api("/api/v1/regions"),
        getTags: () => api("/api/v1/tags"),
        createTag: (data) => api("/api/v1/tags", { method: "POST", body: data }),
        endTag: (id, data) => api(`/api/v1/tags/${id}/end`, { method: "PUT", body: data }),
        deleteTag: (id) => api(`/api/v1/tags/${id}`, { method: "DELETE" }),
        getColumns: () => api("/api/v1/columns"),
        updateColumns: (columns) => api("/api/v1/columns", { method: "PUT", body: { columns } }),
        triggerScan: () => api("/api/v1/scan", { method: "POST" }),
        getConfig: () => api("/api/v1/config"),
        updateConfig: (data) => api("/api/v1/config", { method: "PUT", body: data }),
        getMmrHistory: (params) => api("/api/v1/mmr/history", { params }),
        getNotes: (replayId) => api(`/api/v1/replays/${replayId}/notes`),
        saveNotes: (replayId, analysis) => api(`/api/v1/replays/${replayId}/notes`, { method: "PUT", body: { analysis } }),
        deleteNotes: (replayId) => api(`/api/v1/replays/${replayId}/notes`, { method: "DELETE" }),
        getNotesFeed: (params) => api("/api/v1/notes/feed", { params }),
        getAnalysisConfig: () => api("/api/v1/analysis/config"),
        setDraft: (data) => api("/api/v1/analysis/draft", { method: "PUT", body: data }),
        clearDraft: () => api("/api/v1/analysis/draft", { method: "DELETE" }),
    };

    // ============================================================
    // FORMATTERS
    // ============================================================

    function formatDuration(seconds) {
        if (seconds == null) return "-";
        const mins = Math.floor(seconds / 60);
        const secs = seconds % 60;
        if (mins >= 60) {
            const hours = Math.floor(mins / 60);
            const remMins = mins % 60;
            return `${hours}:${String(remMins).padStart(2, "0")}:${String(secs).padStart(2, "0")}`;
        }
        return `${mins}:${String(secs).padStart(2, "0")}`;
    }

    function formatDate(isoDate) {
        if (!isoDate) return "-";
        try {
            const d = new Date(isoDate);
            const months = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
            return `${months[d.getMonth()]} ${String(d.getDate()).padStart(2, "0")} ${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
        } catch {
            return isoDate.substring(0, 16);
        }
    }

    function formatArmy(supply, minerals) {
        if (supply == null) return "-";
        if (minerals == null) return String(supply);
        if (minerals >= 1000) return `${supply} (${(minerals / 1000).toFixed(1)}k)`;
        return `${supply} (${minerals})`;
    }

    function getTagColor(label) {
        let hash = 0;
        for (let i = 0; i < label.length; i++) {
            hash = ((hash << 5) - hash) + label.charCodeAt(i);
            hash = hash & hash;
        }
        return TAG_COLORS[Math.abs(hash) % TAG_COLORS.length];
    }

    function escapeHtml(text) {
        const div = document.createElement("div");
        div.textContent = text;
        return div.innerHTML;
    }

    function debounce(fn, ms) {
        let timer;
        return (...args) => {
            clearTimeout(timer);
            timer = setTimeout(() => fn(...args), ms);
        };
    }

    const DRAFT_DEBOUNCE_MS = 500;

    // ============================================================
    // TABLE RENDERER
    // ============================================================

    function getCellValue(colKey, replay, benchmarks) {
        const r = replay;
        const b6 = (benchmarks && benchmarks.workers_6m) || 40;
        const b8 = (benchmarks && benchmarks.workers_8m) || 55;

        switch (colKey) {
            case "date":
                return { text: formatDate(r.played_at), align: "left" };
            case "map":
                return { text: (r.map_name || "-").substring(0, 14), align: "left" };
            case "opponent":
                return { text: (r.opponent_name || "-").substring(0, 16), align: "left" };
            case "matchup":
                return { text: r.matchup || "-", align: "left" };
            case "result": {
                const result = r.result || "-";
                const cls = result.toLowerCase() === "win" ? "result-win" : result.toLowerCase() === "loss" ? "result-loss" : "";
                return { text: result, align: "left", className: cls };
            }
            case "mmr": {
                if (r.post_mmr == null && r.player_mmr == null) return { text: "-", align: "right" };
                const displayMmr = r.post_mmr != null ? r.post_mmr : r.player_mmr;
                let html = String(displayMmr);
                if (r.mmr_change != null && r.mmr_change !== 0) {
                    const cls = r.mmr_change > 0 ? "mmr-positive" : "mmr-negative";
                    const sign = r.mmr_change > 0 ? "+" : "";
                    html += ` <span class="${cls}">(${sign}${r.mmr_change})</span>`;
                }
                return { html, align: "right" };
            }
            case "opponent_mmr":
                return { text: r.opponent_mmr != null ? String(r.opponent_mmr) : "-", align: "right" };
            case "apm":
                return { text: r.player_apm != null ? String(r.player_apm) : "-", align: "right" };
            case "opponent_apm":
                return { text: r.opponent_apm != null ? String(r.opponent_apm) : "-", align: "right" };
            case "workers_6m": {
                const val = r.workers_6m;
                if (val == null) return { text: "-", align: "right" };
                const warn = val < b6 ? ' <span class="worker-warning">!</span>' : "";
                return { html: `${val}${warn}`, align: "right" };
            }
            case "workers_8m": {
                const val = r.workers_8m;
                if (val == null) return { text: "-", align: "right" };
                const warn = val < b8 ? ' <span class="worker-warning">!</span>' : "";
                return { html: `${val}${warn}`, align: "right" };
            }
            case "workers_10m":
                return { text: r.workers_10m != null ? String(r.workers_10m) : "-", align: "right" };
            case "army":
                return { text: formatArmy(r.army_supply_8m, r.army_minerals_8m), align: "right" };
            case "length":
                return { text: formatDuration(r.game_length_sec), align: "right" };
            case "bases_6m":
                return { text: r.bases_by_6m != null ? String(r.bases_by_6m) : "-", align: "right" };
            case "bases_8m":
                return { text: r.bases_by_8m != null ? String(r.bases_by_8m) : "-", align: "right" };
            case "bases_10m":
                return { text: r.bases_by_10m != null ? String(r.bases_by_10m) : "-", align: "right" };
            case "worker_kills":
                return { text: String(r.worker_kills_8m || 0), align: "right" };
            case "worker_losses":
                return { text: String(r.worker_losses_8m || 0), align: "right" };
            default:
                return { text: "-", align: "left" };
        }
    }

    function renderTable(replays) {
        state.replays = replays;
        const columns = state.columns.current;
        const available = state.columns.available;
        const benchmarks = state.config.benchmarks;

        const thead = document.getElementById("tableHead");
        thead.innerHTML = columns.map(col => {
            const info = available[col];
            if (!info) return "";
            const align = info.justify === "right" ? "text-right" : "text-left";
            return `<th class="${align}">${info.header}</th>`;
        }).join("");

        const tbody = document.getElementById("tableBody");
        if (!replays || replays.length === 0) {
            tbody.innerHTML = `<tr><td colspan="${columns.length}" class="text-center py-8 text-white/40 font-display">No replays found</td></tr>`;
            return;
        }

        tbody.innerHTML = replays.map(replay => {
            const rid = escapeHtml(replay.replay_id);
            const hasNotes = replay._has_notes;
            const noteIndicator = hasNotes ? '<span class="text-[var(--accent-terran)] text-xs ml-1" title="Has analysis">*</span>' : "";
            const cells = columns.map((col, idx) => {
                const cell = getCellValue(col, replay, benchmarks);
                const alignClass = cell.align === "right" ? "col-right" : "col-left";
                const extraClass = cell.className || "";
                // Append note indicator to first cell
                const suffix = idx === 0 ? noteIndicator : "";
                if (cell.html) {
                    return `<td class="${alignClass} ${extraClass}">${cell.html}${suffix}</td>`;
                }
                return `<td class="${alignClass} ${extraClass}">${escapeHtml(cell.text)}${suffix}</td>`;
            }).join("");
            return `<tr class="cursor-pointer" onclick="App.selectReplay('${rid}')" data-rid="${rid}">${cells}</tr>`;
        }).join("");
    }

    function renderSummary(summary) {
        const el = document.getElementById("tableSummary");
        if (!summary || summary.wins == null) {
            el.textContent = "";
            return;
        }

        const wrColor = summary.winrate >= 50 ? "var(--color-win)" : "var(--color-loss)";
        const parts = [];
        parts.push(`<span style="color: var(--color-win)">${summary.wins}W</span> / <span style="color: var(--color-loss)">${summary.losses}L</span> (<span style="color: ${wrColor}">${summary.winrate.toFixed(1)}%</span>)`);
        if (summary.avg_apm != null) parts.push(`Avg APM: ${Math.round(summary.avg_apm)}`);
        if (summary.avg_workers_8m != null) parts.push(`Avg W@8m: ${Math.round(summary.avg_workers_8m)}`);
        if (summary.avg_length != null) parts.push(`Avg Length: ${formatDuration(Math.round(summary.avg_length))}`);

        el.innerHTML = parts.join("  |  ");
    }

    function renderStatsCards(summary) {
        const total = (summary.wins || 0) + (summary.losses || 0);
        document.getElementById("statGames").textContent = total || "-";

        const wrEl = document.getElementById("statWinrate");
        if (total > 0) {
            wrEl.textContent = `${summary.winrate.toFixed(1)}%`;
            wrEl.style.color = summary.winrate >= 50 ? "var(--color-win)" : "var(--color-loss)";
        } else {
            wrEl.textContent = "-";
            wrEl.style.color = "";
        }

        document.getElementById("statWorkers").textContent =
            summary.avg_workers_8m != null ? Math.round(summary.avg_workers_8m) : "-";
        document.getElementById("statApm").textContent =
            summary.avg_apm != null ? Math.round(summary.avg_apm) : "-";
    }

    // ============================================================
    // FILTER CONTROLS
    // ============================================================

    function getFilterParams() {
        const params = {};
        const f = state.filters;
        if (f.matchup) params.matchup = f.matchup;
        if (f.result) params.result = f.result;
        if (f.map) params.map = f.map;
        if (f.days) params.days = f.days;
        if (f.limit) params.limit = f.limit;
        if (f.player_race) params.player_race = f.player_race;
        if (f.region) params.region = f.region;
        return params;
    }

    function getChartFilterParams() {
        const params = getFilterParams();
        delete params.limit;  // Chart has its own limit
        return params;
    }

    function readFiltersFromUI() {
        state.filters.matchup = document.getElementById("filterMatchup").value;
        state.filters.result = document.getElementById("filterResult").value;
        state.filters.map = document.getElementById("filterMap").value;
        state.filters.days = document.getElementById("filterDays").value;
        state.filters.limit = document.getElementById("filterLimit").value;
        state.filters.player_race = document.getElementById("filterRace").value;
        state.filters.region = document.getElementById("filterRegion").value;
    }

    function clearFilters() {
        const defaultRace = state.defaultRace || "";
        const defaultRegion = state.defaultRegion || "";
        state.filters = { matchup: "", result: "", map: "", days: "", limit: "50", player_race: defaultRace, region: defaultRegion };
        document.getElementById("filterMatchup").value = "";
        document.getElementById("filterResult").value = "";
        document.getElementById("filterMap").value = "";
        document.getElementById("filterDays").value = "";
        document.getElementById("filterLimit").value = "50";
        document.getElementById("filterRace").value = defaultRace;
        document.getElementById("filterRegion").value = defaultRegion;
        refresh();
    }

    function populateMatchupFilter() {
        const select = document.getElementById("filterMatchup");
        const matchups = ["TvZ", "TvP", "TvT", "ZvP", "ZvZ", "PvP"];
        matchups.forEach(m => {
            const opt = document.createElement("option");
            opt.value = m;
            opt.textContent = m;
            select.appendChild(opt);
        });
    }

    // ============================================================
    // MMR CHART (with filter support - BUG FIX)
    // ============================================================

    function hexToRgba(hex, alpha) {
        const r = parseInt(hex.slice(1, 3), 16);
        const g = parseInt(hex.slice(3, 5), 16);
        const b = parseInt(hex.slice(5, 7), 16);
        return `rgba(${r}, ${g}, ${b}, ${alpha})`;
    }

    const tagLinesPlugin = {
        id: "tagLines",
        beforeDatasetsDraw(chart) {
            const pluginOptions = chart.options.plugins.tagLines || {};
            const tagPositions = pluginOptions.tagPositions || [];
            if (tagPositions.length === 0) return;

            const { ctx, chartArea, scales } = chart;
            const xScale = scales.x;
            ctx.save();

            tagPositions.forEach(pos => {
                if (pos.type === "range" || pos.type === "ongoing") {
                    const startX = xScale.getPixelForValue(pos.startIndex);
                    const endX = pos.type === "ongoing" ? chartArea.right : xScale.getPixelForValue(pos.endIndex);
                    const baseColor = pos.color;

                    if (pos.type === "range") {
                        ctx.fillStyle = hexToRgba(baseColor, 0.12);
                        ctx.fillRect(startX, chartArea.top, endX - startX, chartArea.bottom - chartArea.top);
                    } else {
                        const gradient = ctx.createLinearGradient(startX, 0, chartArea.right, 0);
                        gradient.addColorStop(0, hexToRgba(baseColor, 0.15));
                        gradient.addColorStop(0.7, hexToRgba(baseColor, 0.06));
                        gradient.addColorStop(1, hexToRgba(baseColor, 0));
                        ctx.fillStyle = gradient;
                        ctx.fillRect(startX, chartArea.top, chartArea.right - startX, chartArea.bottom - chartArea.top);
                    }
                }
            });

            ctx.restore();
        },
        afterDatasetsDraw(chart) {
            const pluginOptions = chart.options.plugins.tagLines || {};
            const tagPositions = pluginOptions.tagPositions || [];
            if (tagPositions.length === 0) return;

            const { ctx, chartArea, scales } = chart;
            const xScale = scales.x;
            ctx.save();

            tagPositions.forEach(pos => {
                const startX = xScale.getPixelForValue(pos.startIndex);

                if (pos.type === "single") {
                    ctx.beginPath();
                    ctx.setLineDash([4, 4]);
                    ctx.strokeStyle = pos.color;
                    ctx.lineWidth = 1.5;
                    ctx.globalAlpha = 0.7;
                    ctx.moveTo(startX, chartArea.top);
                    ctx.lineTo(startX, chartArea.bottom);
                    ctx.stroke();
                    ctx.setLineDash([]);
                    ctx.globalAlpha = 1;
                    ctx.fillStyle = pos.color;
                    ctx.font = "10px sans-serif";
                    ctx.textAlign = "center";
                    ctx.fillText("\u25C6", startX, chartArea.bottom + 12);
                } else if (pos.type === "range") {
                    const endX = xScale.getPixelForValue(pos.endIndex);
                    ctx.setLineDash([]);
                    ctx.strokeStyle = pos.color;
                    ctx.lineWidth = 2;
                    ctx.globalAlpha = 0.6;
                    ctx.beginPath(); ctx.moveTo(startX, chartArea.top); ctx.lineTo(startX, chartArea.bottom); ctx.stroke();
                    ctx.beginPath(); ctx.moveTo(endX, chartArea.top); ctx.lineTo(endX, chartArea.bottom); ctx.stroke();
                    ctx.globalAlpha = 1;
                    ctx.fillStyle = pos.color;
                    ctx.font = "10px sans-serif";
                    ctx.textAlign = "center";
                    ctx.fillText("\u25C6", startX, chartArea.bottom + 12);
                    ctx.fillText("\u25C6", endX, chartArea.bottom + 12);
                } else if (pos.type === "ongoing") {
                    ctx.setLineDash([4, 4]);
                    ctx.strokeStyle = pos.color;
                    ctx.lineWidth = 2;
                    ctx.globalAlpha = 0.6;
                    ctx.beginPath(); ctx.moveTo(startX, chartArea.top); ctx.lineTo(startX, chartArea.bottom); ctx.stroke();
                    ctx.setLineDash([]);
                    ctx.globalAlpha = 1;
                    ctx.fillStyle = pos.color;
                    ctx.font = "10px sans-serif";
                    ctx.textAlign = "center";
                    ctx.fillText("\u25B8", startX, chartArea.bottom + 12);
                }
            });

            ctx.restore();
        },
    };

    function findTagPositions(data, tags) {
        if (!tags || tags.length === 0) return [];

        const dateToIndex = {};
        data.forEach((d, index) => {
            const dateStr = d.date.substring(0, 10);
            if (dateToIndex[dateStr] === undefined) {
                dateToIndex[dateStr] = { first: index, last: index };
            } else {
                dateToIndex[dateStr].last = index;
            }
        });

        const positions = [];
        const lastIndex = data.length - 1;

        tags.forEach(tag => {
            const color = getTagColor(tag.label);
            const startInfo = dateToIndex[tag.start_date];

            if (tag.type === "single" && startInfo) {
                positions.push({ type: "single", label: tag.label, color, startIndex: startInfo.first, endIndex: startInfo.first });
            } else if (tag.type === "range") {
                const endInfo = dateToIndex[tag.end_date];
                if (startInfo || endInfo) {
                    positions.push({ type: "range", label: tag.label, color, startIndex: startInfo ? startInfo.first : 0, endIndex: endInfo ? endInfo.last : lastIndex });
                }
            } else if (tag.type === "ongoing") {
                positions.push({ type: "ongoing", label: tag.label, color, startIndex: startInfo ? startInfo.first : 0, endIndex: lastIndex });
            }
        });

        return positions;
    }

    async function initChart() {
        Chart.register(tagLinesPlugin);

        const params = getChartFilterParams();
        const result = await API.getMmrHistory(params);
        const data = (result.data || []).filter(d => d.mmr >= MMR_MIN_VALID && d.mmr <= MMR_MAX_VALID);
        const tags = result.tags || [];
        const tagPositions = findTagPositions(data, tags);

        const yAxis = calculateAdaptiveYAxis(data);
        const ctx = document.getElementById("mmrChart").getContext("2d");

        state.chart = new Chart(ctx, {
            type: "line",
            data: {
                labels: data.map((_, i) => i + 1),
                datasets: [{
                    label: "MMR",
                    data: data.map(d => d.mmr),
                    borderColor: "#ff9d5c",
                    borderWidth: 3,
                    backgroundColor: function(context) {
                        const chart = context.chart;
                        const { ctx, chartArea } = chart;
                        if (!chartArea) return null;
                        const gradient = ctx.createLinearGradient(0, chartArea.top, 0, chartArea.bottom);
                        gradient.addColorStop(0, "rgba(255, 157, 92, 0.25)");
                        gradient.addColorStop(0.5, "rgba(255, 120, 60, 0.12)");
                        gradient.addColorStop(1, "rgba(255, 120, 60, 0.02)");
                        return gradient;
                    },
                    pointBackgroundColor: data.map(d => d.result === "Win" ? "#00ff88" : "#ff4444"),
                    pointBorderColor: data.map(d => d.result === "Win" ? "#00ff88" : "#ff4444"),
                    pointRadius: 4,
                    pointHoverRadius: 6,
                    pointBorderWidth: 2,
                    tension: 0.3,
                    fill: true,
                }],
            },
            options: {
                responsive: true,
                maintainAspectRatio: false,
                interaction: { mode: "nearest", axis: "x", intersect: false },
                plugins: {
                    legend: { display: false },
                    tagLines: { tagPositions },
                    tooltip: {
                        backgroundColor: "rgba(20, 25, 35, 0.95)",
                        titleColor: "#ff9d5c",
                        bodyColor: "#ffffff",
                        borderColor: "rgba(255, 120, 60, 0.5)",
                        borderWidth: 2,
                        padding: 12,
                        displayColors: false,
                        callbacks: {
                            title: (ctx) => {
                                const idx = ctx[0].dataIndex;
                                return `Game ${idx + 1} - ${data[idx] ? data[idx].result : ""}`;
                            },
                            label: (ctx) => `MMR: ${ctx.parsed.y}`,
                        },
                    },
                },
                scales: {
                    x: {
                        ticks: { color: "rgba(255,255,255,0.7)", maxTicksLimit: 8, font: { family: "Rajdhani", size: 11 } },
                        grid: { color: "rgba(255,255,255,0.06)" },
                        border: { display: false },
                    },
                    y: {
                        min: yAxis.min, max: yAxis.max,
                        ticks: { color: "rgba(255,255,255,0.8)", font: { family: "Roboto Mono", size: 12 }, callback: v => v.toLocaleString() },
                        grid: { color: "rgba(255,255,255,0.08)" },
                        border: { display: false },
                    },
                },
                animation: { duration: 750, easing: "easeInOutQuart" },
            },
        });
    }

    async function updateChart() {
        if (!state.chart) return;

        const params = getChartFilterParams();
        const result = await API.getMmrHistory(params);
        const data = (result.data || []).filter(d => d.mmr >= MMR_MIN_VALID && d.mmr <= MMR_MAX_VALID);
        const tags = result.tags || [];
        const tagPositions = findTagPositions(data, tags);
        const yAxis = calculateAdaptiveYAxis(data);

        state.chart.data.labels = data.map((_, i) => i + 1);
        state.chart.data.datasets[0].data = data.map(d => d.mmr);
        state.chart.data.datasets[0].pointBackgroundColor = data.map(d => d.result === "Win" ? "#00ff88" : "#ff4444");
        state.chart.data.datasets[0].pointBorderColor = data.map(d => d.result === "Win" ? "#00ff88" : "#ff4444");
        state.chart.options.plugins.tagLines.tagPositions = tagPositions;
        state.chart.options.scales.y.min = yAxis.min;
        state.chart.options.scales.y.max = yAxis.max;
        state.chart.update("none");
    }

    function calculateAdaptiveYAxis(data) {
        if (!data || data.length === 0) return { min: 0, max: 5000 };
        const mmrValues = data.map(d => d.mmr);
        const minMMR = Math.min(...mmrValues);
        const maxMMR = Math.max(...mmrValues);
        const range = Math.max(maxMMR - minMMR, MIN_Y_RANGE);
        const padding = range * ADAPTIVE_PADDING_PERCENT;
        return {
            min: Math.floor((minMMR - padding) / 50) * 50,
            max: Math.ceil((maxMMR + padding) / 50) * 50,
        };
    }

    // ============================================================
    // REPLAY ANALYSIS (Post Game Analysis)
    // ============================================================

    async function selectReplay(replayId) {
        // If clicking the same replay, toggle closed
        if (state.selectedReplayId === replayId) {
            closeDetail();
            return;
        }

        // Close any existing inline form
        closeDetail();

        state.selectedReplayId = replayId;
        const replay = state.replays.find(r => r.replay_id === replayId);
        if (!replay) return;

        // Load analysis config if not yet loaded
        if (state.analysisFields.length === 0) {
            const configResult = await API.getAnalysisConfig();
            state.analysisFields = configResult.fields || [];
        }

        // Load existing notes
        const notesResult = await API.getNotes(replayId);
        const existingData = (notesResult.notes && notesResult.notes.analysis) || {};

        const colCount = state.columns.current.length;
        const resultClass = (replay.result || "").toLowerCase() === "win" ? "text-[var(--color-win)]" : "text-[var(--color-loss)]";

        const formFields = state.analysisFields.map(field => {
            const val = escapeHtml(existingData[field.key] || "");
            if (field.type === "textarea") {
                return `<div>
                    <label class="block text-xs font-display text-white/50 mb-1">${escapeHtml(field.label)}</label>
                    <textarea name="${field.key}" rows="2" class="w-full bg-[var(--bg-primary)] border border-white/[0.08] text-white rounded px-2 py-1 text-sm font-display resize-y">${val}</textarea>
                </div>`;
            }
            return `<div>
                <label class="block text-xs font-display text-white/50 mb-1">${escapeHtml(field.label)}</label>
                <input type="text" name="${field.key}" value="${val}" class="w-full bg-[var(--bg-primary)] border border-white/[0.08] text-white rounded px-2 py-1 text-sm font-display">
            </div>`;
        }).join("");

        const inlineRow = document.createElement("tr");
        inlineRow.id = "analysisRow";
        inlineRow.innerHTML = `<td colspan="${colCount}" class="p-0">
            <div class="p-4 bg-white/[0.02] border-t border-b border-[var(--border-accent)]">
                <div class="flex items-center justify-between mb-3">
                    <div class="font-display text-sm">
                        <span class="${resultClass} font-bold">${replay.result || "?"}</span>
                        <span class="text-white/60">vs ${replay.matchup || "?"} on ${escapeHtml(replay.map_name || "?")}</span>
                        <span class="text-white/40 text-xs ml-2">${formatDate(replay.played_at)}</span>
                    </div>
                    <button onclick="App.closeDetail()" class="text-white/40 hover:text-white text-lg">&times;</button>
                </div>
                <form id="analysisForm" class="space-y-3 max-w-2xl">${formFields}</form>
                <div class="mt-3 flex gap-2">
                    <button onclick="App.saveAnalysis()" class="btn-primary">Save Analysis</button>
                </div>
            </div>
        </td>`;

        // Insert right after the clicked row
        const clickedRow = document.querySelector(`tr[data-rid="${escapeHtml(replayId)}"]`);
        if (clickedRow) {
            clickedRow.after(inlineRow);
            clickedRow.classList.add("bg-white/[0.05]");
        }

        // Wire up live draft sending on input
        const replayContext = {
            result: replay.result,
            matchup: replay.matchup,
            map_name: replay.map_name,
            played_at: replay.played_at,
            opponent_name: replay.opponent_name,
            player_mmr: replay.player_mmr,
        };

        function sendDraft() {
            const form = document.getElementById("analysisForm");
            if (!form) return;
            const analysis = {};
            state.analysisFields.forEach(field => {
                const el = form.querySelector(`[name="${field.key}"]`);
                if (el) analysis[field.key] = el.value;
            });
            API.setDraft({ replay: replayContext, analysis });
        }

        const debouncedSendDraft = debounce(sendDraft, DRAFT_DEBOUNCE_MS);

        // Send initial draft (shows game context on widget immediately)
        sendDraft();

        // Listen for typing
        const form = document.getElementById("analysisForm");
        if (form) {
            form.addEventListener("input", debouncedSendDraft);
        }
    }

    async function saveAnalysis() {
        if (!state.selectedReplayId) return;

        const form = document.getElementById("analysisForm");
        const analysis = {};
        state.analysisFields.forEach(field => {
            const el = form.querySelector(`[name="${field.key}"]`);
            if (el) analysis[field.key] = el.value;
        });

        await API.saveNotes(state.selectedReplayId, analysis);

        // Mark this replay as having notes in local state
        const replay = state.replays.find(r => r.replay_id === state.selectedReplayId);
        if (replay) replay._has_notes = true;

        closeDetail();
        renderAnalysisFeed();
        // Re-render table to show note indicator without losing position
        renderTable(state.replays);
    }

    function closeDetail() {
        const existingRow = document.getElementById("analysisRow");
        if (existingRow) existingRow.remove();

        // Remove highlight from previously selected row
        if (state.selectedReplayId) {
            const prevRow = document.querySelector(`tr[data-rid="${escapeHtml(state.selectedReplayId)}"]`);
            if (prevRow) prevRow.classList.remove("bg-white/[0.05]");
        }

        state.selectedReplayId = null;

        // Clear the live draft so widget goes idle
        API.clearDraft();
    }

    // ============================================================
    // ANALYSIS FEED (preview on main page)
    // ============================================================

    async function renderAnalysisFeed() {
        const container = document.getElementById("analysisFeed");
        if (!container) return;

        const result = await API.getNotesFeed({ limit: 5 });
        const notes = result.notes || [];

        if (notes.length === 0) {
            container.innerHTML = '<div class="text-white/30 text-sm font-display">No analyses yet. Click a replay to add one.</div>';
            return;
        }

        container.innerHTML = notes.map(note => {
            const analysis = note.analysis || {};
            const resultClass = (note.result || "").toLowerCase() === "win" ? "text-[var(--color-win)]" : "text-[var(--color-loss)]";
            const fields = Object.entries(analysis)
                .filter(([, v]) => v && v.trim())
                .map(([k, v]) => {
                    const fieldDef = state.analysisFields.find(f => f.key === k);
                    const label = fieldDef ? fieldDef.label : k;
                    return `<div class="text-xs"><span class="text-white/40">${escapeHtml(label)}:</span> <span class="text-white/70">${escapeHtml(v)}</span></div>`;
                }).join("");

            return `<div class="p-2 bg-white/[0.03] rounded border border-white/[0.05] space-y-1">
                <div class="flex items-center gap-2 text-sm">
                    <span class="${resultClass} font-bold">${note.result || "?"}</span>
                    <span class="text-white/60">${note.matchup || "?"}</span>
                    <span class="text-white/40">${escapeHtml((note.map_name || "").substring(0, 14))}</span>
                    <span class="text-white/30 text-xs ml-auto">${formatDate(note.played_at)}</span>
                </div>
                ${fields}
            </div>`;
        }).join("");
    }

    // ============================================================
    // TAGS
    // ============================================================

    async function renderTags() {
        const result = await API.getTags();
        const tags = result.tags || [];
        const container = document.getElementById("tagsList");

        if (tags.length === 0) {
            container.innerHTML = '<div class="text-white/30 text-sm font-display">No tags yet</div>';
            return;
        }

        container.innerHTML = tags.map(tag => {
            const color = getTagColor(tag.label);
            let icon, info;
            if (tag.type === "ongoing") {
                icon = `<span class="tag-icon" style="color: ${color}">\u25B8</span>`;
                info = `<span class="text-white/40 text-xs">since ${tag.start_date}</span>`;
            } else if (tag.type === "range") {
                icon = `<span class="tag-icon" style="color: ${color}">\u25C6\u2500\u25C6</span>`;
                info = `<span class="text-white/40 text-xs">${tag.start_date} \u2192 ${tag.end_date}</span>`;
            } else {
                icon = `<span class="tag-icon" style="color: ${color}">\u25C6</span>`;
                info = `<span class="text-white/40 text-xs">${tag.start_date}</span>`;
            }

            const actions = [];
            if (tag.type === "ongoing") {
                actions.push(`<button onclick="App.endTag(${tag.id})" class="btn-success">End</button>`);
            }
            actions.push(`<button onclick="App.deleteTag(${tag.id})" class="btn-danger">Del</button>`);

            return `<div class="tag-item">
                <div class="flex items-center gap-2">
                    ${icon}
                    <span class="font-display font-semibold text-sm text-white/80">${escapeHtml(tag.label)}</span>
                    ${info}
                </div>
                <div class="flex gap-1">${actions.join("")}</div>
            </div>`;
        }).join("");
    }

    function showAddTag() {
        document.getElementById("addTagForm").classList.remove("hidden");
        document.getElementById("tagLabel").focus();
        document.getElementById("tagDate").value = new Date().toISOString().split("T")[0];
    }

    function hideAddTag() {
        document.getElementById("addTagForm").classList.add("hidden");
        document.getElementById("tagLabel").value = "";
    }

    async function createTag(type) {
        const label = document.getElementById("tagLabel").value.trim();
        if (!label) return;

        const date = document.getElementById("tagDate").value || undefined;
        const data = { label, date };
        if (type === "single") data.end_date = date;

        await API.createTag(data);
        hideAddTag();
        renderTags();
        updateChart();
    }

    async function endTag(id) {
        await API.endTag(id, {});
        renderTags();
        updateChart();
    }

    async function deleteTag(id) {
        await API.deleteTag(id);
        renderTags();
        updateChart();
    }

    // ============================================================
    // SETTINGS (Columns + Config)
    // ============================================================

    function toggleSettings() {
        const modal = document.getElementById("settingsModal");
        const isHidden = modal.classList.contains("hidden");
        if (isHidden) {
            renderSettingsPanel();
        }
        modal.classList.toggle("hidden");
    }

    function renderSettingsPanel() {
        // Config section
        const configContainer = document.getElementById("configSettings");
        if (configContainer) {
            configContainer.innerHTML = `
                <div class="space-y-2">
                    <div>
                        <label class="block text-xs font-display text-white/50 mb-1">Player Name</label>
                        <input type="text" id="settingPlayerName" value="${escapeHtml(state.config.player_name || "")}" class="w-full bg-[var(--bg-primary)] border border-white/[0.08] text-white rounded px-2 py-1 text-sm font-display">
                    </div>
                    <div>
                        <label class="block text-xs font-display text-white/50 mb-1">Region</label>
                        <select id="settingRegion" class="w-full bg-[var(--bg-primary)] border border-white/[0.08] text-white rounded px-2 py-1 text-sm font-display">
                            <option value="">Auto-detect</option>
                            <option value="us" ${state.config.region === "us" ? "selected" : ""}>US</option>
                            <option value="eu" ${state.config.region === "eu" ? "selected" : ""}>EU</option>
                            <option value="kr" ${state.config.region === "kr" ? "selected" : ""}>KR</option>
                        </select>
                    </div>
                </div>
            `;
        }

        // Columns section
        const container = document.getElementById("columnsConfig");
        const current = state.columns.current;
        const available = state.columns.available;

        container.innerHTML = Object.entries(available).map(([key, info]) => {
            const checked = current.includes(key) ? "checked" : "";
            return `<label class="flex items-center gap-2 py-1 px-2 rounded hover:bg-white/[0.05] cursor-pointer">
                <input type="checkbox" value="${key}" ${checked} class="col-checkbox accent-[var(--accent-terran)]">
                <span class="font-display font-semibold text-sm text-white/80">${key}</span>
                <span class="text-white/40 text-xs ml-auto">${info.header}</span>
            </label>`;
        }).join("");
    }

    async function saveSettings() {
        // Save config
        const playerName = document.getElementById("settingPlayerName")?.value;
        const region = document.getElementById("settingRegion")?.value;
        if (playerName !== undefined) {
            await API.updateConfig({ player_name: playerName, region: region || "" });
            state.config.player_name = playerName;
            state.config.region = region;
            document.getElementById("playerName").textContent = playerName;
        }

        // Save columns
        const checkboxes = document.querySelectorAll(".col-checkbox:checked");
        const columns = Array.from(checkboxes).map(cb => cb.value);
        if (columns.length > 0) {
            await API.updateColumns(columns);
            state.columns.current = columns;
        }

        toggleSettings();
        refresh();
    }

    async function resetColumns() {
        const result = await API.getColumns();
        await API.updateColumns(result.current);
        state.columns = result;
        toggleSettings();
        refresh();
    }

    // ============================================================
    // SCAN
    // ============================================================

    async function triggerScan() {
        const btn = document.getElementById("scanBtn");
        const originalText = btn.textContent;
        btn.textContent = "Scanning...";
        btn.disabled = true;

        try {
            const result = await API.triggerScan();
            if (result.new_replays > 0) {
                btn.textContent = `+${result.new_replays} new`;
                refresh();
            } else {
                btn.textContent = "No new";
            }
        } catch {
            btn.textContent = "Error";
        }

        setTimeout(() => {
            btn.textContent = originalText;
            btn.disabled = false;
        }, 2000);
    }

    // ============================================================
    // AUTO-REFRESH
    // ============================================================

    const SCAN_INTERVAL_MS = 10000; // Scan for new files every 10s
    let lastScanTime = 0;

    async function pollForNewReplays() {
        try {
            // Periodically scan for new replay files on disk
            const now = Date.now();
            if (now - lastScanTime >= SCAN_INTERVAL_MS) {
                lastScanTime = now;
                const scanResult = await API.triggerScan();
                if (scanResult.new_replays > 0) {
                    refresh();
                    return;
                }
            }

            // Quick check: did the filtered count change?
            const params = getFilterParams();
            params.limit = 1;
            const result = await API.getReplays(params);
            const currentCount = result.total;

            if (state.lastReplayCount !== null && currentCount !== state.lastReplayCount) {
                refresh();
            }
            state.lastReplayCount = currentCount;
        } catch {
            // Silent failure during polling
        }
    }

    // ============================================================
    // MAIN REFRESH (chart now syncs with table filters)
    // ============================================================

    async function refresh() {
        readFiltersFromUI();
        const params = getFilterParams();

        const result = await API.getReplays(params);
        renderTable(result.replays);
        renderSummary(result.summary);
        renderStatsCards(result.summary);
        updateChart();

        const filterDesc = buildFilterDescription(result.total);
        document.getElementById("filterStatus").textContent = filterDesc;
    }

    function applyFilters() {
        refresh();
    }

    function buildFilterDescription(count) {
        const f = state.filters;
        const parts = [];
        if (f.region) parts.push(f.region.toUpperCase());
        if (f.player_race) parts.push(f.player_race);
        if (f.matchup) parts.push(f.matchup);
        if (f.result) parts.push(f.result === "Win" ? "wins" : "losses");
        if (f.map) parts.push(`on "${f.map}"`);
        if (f.days) parts.push(`last ${f.days}d`);
        parts.unshift(`${count} games`);
        return parts.join(", ");
    }

    // ============================================================
    // INITIALIZATION
    // ============================================================

    async function init() {
        try {
            const [configResult, columnsResult, racesResult, regionsResult] = await Promise.all([
                API.getConfig(),
                API.getColumns(),
                API.getRaces(),
                API.getRegions(),
            ]);

            state.config = configResult;
            state.columns = columnsResult;

            // Set player name
            document.getElementById("playerName").textContent = configResult.player_name || "";

            // Auto-detect race and set filter default
            const defaultRace = racesResult.default || "";
            state.defaultRace = defaultRace;
            if (defaultRace) {
                state.filters.player_race = defaultRace;
                const raceSelect = document.getElementById("filterRace");
                if (raceSelect) raceSelect.value = defaultRace;
            }

            // Auto-detect region and set filter default
            const defaultRegion = regionsResult.default || "";
            state.defaultRegion = defaultRegion;
            if (defaultRegion) {
                state.filters.region = defaultRegion;
                const regionSelect = document.getElementById("filterRegion");
                if (regionSelect) regionSelect.value = defaultRegion;
            }

            // Populate region dropdown with discovered regions
            const regionSelect = document.getElementById("filterRegion");
            if (regionSelect && regionsResult.regions) {
                regionsResult.regions.forEach(r => {
                    if (!regionSelect.querySelector(`option[value="${r.region}"]`)) {
                        const opt = document.createElement("option");
                        opt.value = r.region;
                        opt.textContent = r.region.toUpperCase() + ` (${r.count})`;
                        regionSelect.appendChild(opt);
                    }
                });
                if (defaultRegion) regionSelect.value = defaultRegion;
            }

            // Populate filter options
            populateMatchupFilter();

            // Load analysis config
            const analysisConfig = await API.getAnalysisConfig();
            state.analysisFields = analysisConfig.fields || [];

            // Initial data load and chart
            await Promise.all([refresh(), initChart()]);

            // Load tags and analysis feed
            renderTags();
            renderAnalysisFeed();

            // Start auto-refresh polling
            const interval = configResult.auto_scan_interval_ms || REFRESH_INTERVAL_MS;
            setInterval(pollForNewReplays, interval);

        } catch (err) {
            console.error("Failed to initialize:", err);
        }
    }

    document.addEventListener("DOMContentLoaded", init);

    // ============================================================
    // PUBLIC API
    // ============================================================

    return {
        applyFilters,
        clearFilters,
        triggerScan,
        toggleSettings,
        saveSettings,
        saveColumns: () => saveSettings(),
        resetColumns,
        showAddTag,
        hideAddTag,
        createTag,
        endTag,
        deleteTag,
        selectReplay,
        saveAnalysis,
        closeDetail,
    };
})();
