"use strict";

// ----------------------------- utilidades ---------------------------------
const $ = (sel) => document.querySelector(sel);
const el = (tag, attrs = {}, ...kids) => {
    const e = document.createElement(tag);
    for (const [k, v] of Object.entries(attrs)) {
        if (k === "class") e.className = v;
        else if (k === "style") e.style.cssText = v;
        else if (k.startsWith("on")) e.addEventListener(k.slice(2), v);
        else if (v !== null && v !== undefined) e.setAttribute(k, v);
    }
    for (const kid of kids) {
        if (kid === null || kid === undefined) continue;
        e.appendChild(typeof kid === "string" ? document.createTextNode(kid) : kid);
    }
    return e;
};

function fmtLap(ms) {
    if (!ms || ms <= 0) return "—";
    const m = Math.floor(ms / 60000);
    const s = Math.floor((ms % 60000) / 1000);
    const mm = ms % 1000;
    return `${m}:${String(s).padStart(2, "0")}.${String(mm).padStart(3, "0")}`;
}
function fmtGap(ms) {
    if (ms === null || ms === undefined) return "—";
    if (ms === 0) return "—";
    return "+" + (ms / 1000).toFixed(3);
}
function fmtDate(iso) {
    if (!iso) return "—";
    const d = new Date(iso);
    if (isNaN(d)) return iso;
    return d.toLocaleString("pt-BR", { day: "2-digit", month: "2-digit",
        hour: "2-digit", minute: "2-digit" });
}
const TYRE_LETTER = { "Macio": "S", "Médio": "M", "Duro": "H",
    "Intermediário": "I", "Chuva": "W" };
function wearColor(pct) {
    if (pct == null) return "#555";
    if (pct < 30) return "#2ecc71";
    if (pct < 60) return "#ffd400";
    return "#e10600";
}
function ersColor(pct) {
    if (pct == null) return "#555";
    if (pct > 50) return "#2ecc71";
    if (pct > 20) return "#ffd400";
    return "#e10600";
}

async function getJSON(url) {
    const r = await fetch(url);
    if (!r.ok) throw new Error(url + " -> " + r.status);
    return r.json();
}

// ------------------------- gráfico em canvas ------------------------------
// series: [{label, color, points:[{x,y}], width?, step?, fill?}]
function drawChart(canvas, series, opts = {}) {
    const dpr = window.devicePixelRatio || 1;
    const cssW = canvas.clientWidth || 800;
    const cssH = parseInt(canvas.getAttribute("height")) || 240;
    canvas.width = cssW * dpr;
    canvas.height = cssH * dpr;
    const ctx = canvas.getContext("2d");
    ctx.scale(dpr, dpr);
    ctx.clearRect(0, 0, cssW, cssH);

    const padL = 52, padR = 14, padT = 12, padB = 30;
    const W = cssW - padL - padR, H = cssH - padT - padB;

    let xs = [], ys = [];
    for (const s of series) for (const p of s.points) { xs.push(p.x); ys.push(p.y); }
    if (!xs.length) {
        ctx.fillStyle = "#9a9ab2"; ctx.font = "14px Titillium Web, sans-serif";
        ctx.fillText("Sem dados", padL, padT + H / 2);
        return;
    }
    let xMin = opts.xMin ?? Math.min(...xs), xMax = opts.xMax ?? Math.max(...xs);
    let yMin = opts.yMin ?? Math.min(...ys), yMax = opts.yMax ?? Math.max(...ys);
    if (xMin === xMax) xMax = xMin + 1;
    if (yMin === yMax) yMax = yMin + 1;
    const pad = (yMax - yMin) * 0.08; yMin -= pad; yMax += pad;

    const X = (x) => padL + ((x - xMin) / (xMax - xMin)) * W;
    const Y = (y) => padT + H - ((y - yMin) / (yMax - yMin)) * H;

    // grade + eixos
    ctx.strokeStyle = "#2a2a3a"; ctx.fillStyle = "#9a9ab2";
    ctx.font = "11px Titillium Web, sans-serif"; ctx.lineWidth = 1;
    const yTicks = 5;
    for (let i = 0; i <= yTicks; i++) {
        const yv = yMin + (i / yTicks) * (yMax - yMin);
        const py = Y(yv);
        ctx.beginPath(); ctx.moveTo(padL, py); ctx.lineTo(padL + W, py); ctx.stroke();
        const label = opts.formatY ? opts.formatY(yv) : yv.toFixed(0);
        ctx.fillText(label, 6, py + 4);
    }
    const xTicks = opts.xTicks || 6;
    for (let i = 0; i <= xTicks; i++) {
        const xv = xMin + (i / xTicks) * (xMax - xMin);
        const px = X(xv);
        ctx.strokeStyle = "#20202c";
        ctx.beginPath(); ctx.moveTo(px, padT); ctx.lineTo(px, padT + H); ctx.stroke();
        const label = opts.formatX ? opts.formatX(xv) : xv.toFixed(0);
        ctx.fillStyle = "#9a9ab2";
        ctx.textAlign = "center"; ctx.fillText(label, px, padT + H + 18);
        ctx.textAlign = "left";
    }

    // séries
    for (const s of series) {
        if (!s.points.length) continue;
        ctx.strokeStyle = s.color; ctx.lineWidth = s.width || 2;
        ctx.beginPath();
        s.points.forEach((p, i) => {
            const px = X(p.x), py = Y(p.y);
            if (i === 0) ctx.moveTo(px, py);
            else if (s.step) { ctx.lineTo(px, Y(s.points[i - 1].y)); ctx.lineTo(px, py); }
            else ctx.lineTo(px, py);
        });
        ctx.stroke();
        if (s.fill) {
            ctx.lineTo(X(s.points[s.points.length - 1].x), Y(yMin));
            ctx.lineTo(X(s.points[0].x), Y(yMin));
            ctx.closePath(); ctx.globalAlpha = 0.12; ctx.fillStyle = s.color;
            ctx.fill(); ctx.globalAlpha = 1;
        }
        if (s.dots) {
            ctx.fillStyle = s.color;
            for (const p of s.points) {
                ctx.beginPath(); ctx.arc(X(p.x), Y(p.y), 3.5, 0, Math.PI * 2); ctx.fill();
            }
        }
    }
}

// ------------------------------ abas --------------------------------------
const tabs = document.querySelectorAll("nav.tabs button");
const panels = { live: $("#panel-live"), evolution: $("#panel-evolution"),
    telemetry: $("#panel-telemetry"), setups: $("#panel-setups"),
    refsetups: $("#panel-refsetups"),
    insights: $("#panel-insights"), rivals: $("#panel-rivals"),
    sessions: $("#panel-sessions"), config: $("#panel-config") };
let activeTab = "live";
tabs.forEach((b) => b.addEventListener("click", () => {
    tabs.forEach((x) => x.classList.remove("active"));
    b.classList.add("active");
    Object.values(panels).forEach((p) => p && p.classList.remove("active"));
    activeTab = b.dataset.tab;
    if (panels[activeTab]) panels[activeTab].classList.add("active");
    onTabShown(activeTab);
}));

function onTabShown(tab) {
    if (tab === "evolution") loadTracks("#evo-track", renderEvolution);
    if (tab === "setups") loadTracks("#setup-track", renderSetups);
    if (tab === "refsetups") { loadRefTracks("#refsetup-track", renderRefSetups); refreshSetupCompare(); }
    if (tab === "telemetry") loadSessionsInto("#tel-session", onTelSession);
    if (tab === "insights") loadSessionsInto("#ins-session", renderInsights);
    if (tab === "rivals") loadSessionsInto("#riv-session", renderRivals);
    if (tab === "sessions") renderSessions();
    if (tab === "config") renderConfig();
}

// ----------------------------- configuração -------------------------------
const OVERLAY_WINDOWS = [
    ["hud", "HUD", "Posição, velocidade, marcha, DRS, pneu, ERS, gaps frente/trás"],
    ["car", "Carro", "Temperatura, desgaste e pressão dos pneus, danos, freios"],
    ["tower", "Torre de tempos", "Os 22 carros: pneu, intervalo, bateria, punições"],
    ["alerts", "Alertas", "Pneu quente, combustível, asa, rival, chuva, punição"],
    ["weather", "Clima", "Condição atual e previsão dos próximos minutos"],
    ["engenheiro", "Engenheiro", "Legendas do que o engenheiro de voz está dizendo"],
    ["setup", "Setup", "Assistente: setup-alvo × garagem ao vivo (fica verde quando bate)"],
    ["spotter", "Spotter", "Carro ao lado: faixas acendem em vermelho à esquerda/direita"],
    ["mapa", "Mapa", "Minimapa: os 22 carros vistos de cima (pista desenhada na 1ª volta)"],
    ["grip", "Aderência", "Força G (círculo de tração) + deslize e sub/sobre-esterço"],
    ["duelo", "Duelo (frente/trás)", "Mini-leaderboard: só o carro à frente e o de trás, com gap, pneu e última volta"],
];
async function renderConfig() {
    const box = $("#config-windows");
    let cfg;
    try { cfg = await getJSON("/api/overlay/config"); }
    catch (e) { box.innerHTML = ""; box.appendChild(el("div", { class: "empty" }, "Erro ao carregar.")); return; }
    const enabled = cfg.enabled || {}, opacity = cfg.opacity || {};
    Object.assign(lbPrefs, cfg.leaderboard || {});   // mantém o cache em dia
    box.innerHTML = "";

    // estado compartilhado dos atalhos (ação->tecla); o servidor troca o mapa inteiro a cada save
    const hotActionKey = {};
    for (const [k, a] of Object.entries(cfg.hotkeys || {})) hotActionKey[a] = k;
    const hotKeyOpts = ["—"];
    for (let i = 13; i <= 24; i++) hotKeyOpts.push("F" + i);
    for (let i = 0; i <= 9; i++) hotKeyOpts.push("NUM" + i);
    const saveHotkeys = () => {
        const m = {};
        for (const [a, k] of Object.entries(hotActionKey)) if (k && k !== "—") m[k] = a;
        saveConfig({ hotkeys: m });
    };
    const hotkeySelect = (action) => {
        const cur = hotActionKey[action] || "—";
        const sel = el("select", { class: "config-key" });
        const opts = hotKeyOpts.slice();
        if (cur !== "—" && !opts.includes(cur)) opts.splice(1, 0, cur);
        for (const k of opts) { const o = el("option", { value: k }, k); if (k === cur) o.selected = true; sel.appendChild(o); }
        sel.addEventListener("change", () => {
            if (sel.value === "—") delete hotActionKey[action];
            else hotActionKey[action] = sel.value;
            saveHotkeys();
        });
        return sel;
    };

    box.appendChild(el("h3", { class: "config-h3" }, "Janelas do overlay nativo"));
    for (const [key, name, desc] of OVERLAY_WINDOWS) {
        const check = el("input", { type: "checkbox" });
        check.checked = enabled[key] !== false;
        check.addEventListener("change", () => saveConfig({ enabled: { [key]: check.checked } }));
        const op = opacity[key] != null ? opacity[key] : 100;
        const slider = el("input", { type: "range", min: "20", max: "100", step: "5", class: "config-slider" });
        slider.value = op;
        const opval = el("span", { class: "config-opval" }, op + "%");
        slider.addEventListener("input", () => { opval.textContent = slider.value + "%"; });
        slider.addEventListener("change", () => saveConfig({ opacity: { [key]: Number(slider.value) } }));
        box.appendChild(el("div", { class: "config-row" },
            check,
            el("span", { class: "config-info" },
                el("span", { class: "config-name" }, name),
                el("span", { class: "config-desc" }, desc)),
            el("span", { class: "config-opacity" },
                el("span", { class: "config-oplabel" }, "opacidade"), slider, opval),
            el("span", { class: "config-hotkey" },
                el("span", { class: "config-keylabel" }, "tecla"), hotkeySelect("toggle:" + key))));
    }
    // modo contexto: mostrar cada janela só quando faz sentido
    const ctx = el("input", { type: "checkbox" });
    ctx.checked = cfg.context_aware !== false;
    ctx.addEventListener("change", () => saveConfig({ context_aware: ctx.checked }));
    box.appendChild(el("label", { class: "config-row" }, ctx,
        el("span", { class: "config-info" },
            el("span", { class: "config-name" }, "Mostrar cada janela só no contexto certo"),
            el("span", { class: "config-desc" },
                "Setup só na garagem; HUD, carro, alertas e torre só na pista. "
                + "Desligue para mostrar sempre tudo que está ligado acima."))));

    // voz do spotter: "carro à esquerda/direita" / "pista livre"
    const spv = el("input", { type: "checkbox" });
    spv.checked = cfg.spotter_voice !== false;
    spv.addEventListener("change", () => saveConfig({ spotter_voice: spv.checked }));
    box.appendChild(el("label", { class: "config-row" }, spv,
        el("span", { class: "config-info" },
            el("span", { class: "config-name" }, "Spotter por voz (carro ao lado)"),
            el("span", { class: "config-desc" },
                "O engenheiro avisa 'carro à esquerda/direita' e 'pista livre' "
                + "em tempo real. A faixa visual (janela Spotter) é independente disto."))));

    // tempo até a mensagem do engenheiro sumir
    const hide = cfg.radio_hide_s != null ? cfg.radio_hide_s : 0;
    const num = el("input", { type: "number", min: "0", max: "60", value: String(hide), class: "config-num" });
    num.addEventListener("change", () => saveConfig({ radio_hide_s: Number(num.value) }));
    box.appendChild(el("div", { class: "config-row" },
        el("span", { class: "config-info" },
            el("span", { class: "config-name" }, "Sumir mensagem do engenheiro"),
            el("span", { class: "config-desc" }, "Segundos até a legenda desaparecer (0 = nunca some)")),
        el("span", { class: "config-numwrap" }, num, el("span", { class: "config-numunit" }, "seg"))));

    // volume da voz do engenheiro
    const vol = cfg.voice_volume != null ? cfg.voice_volume : 100;
    const vslider = el("input", { type: "range", min: "0", max: "100", step: "5", class: "config-slider" });
    vslider.value = vol;
    const vval = el("span", { class: "config-opval" }, vol + "%");
    vslider.addEventListener("input", () => { vval.textContent = vslider.value + "%"; });
    vslider.addEventListener("change", () => saveConfig({ voice_volume: Number(vslider.value) }));
    box.appendChild(el("div", { class: "config-row" },
        el("span", { class: "config-info" },
            el("span", { class: "config-name" }, "Volume da voz do engenheiro"),
            el("span", { class: "config-desc" }, "Aplica em ~3 s (o engenheiro precisa estar rodando)")),
        el("span", { class: "config-opacity" }, vslider, vval)));

    // atalhos de teclado (Ulanzi/Stream Deck): ativar + atalhos gerais (cada painel tem o seu acima)
    box.appendChild(el("h3", { class: "config-h3" }, "Atalhos (Stream Deck / Ulanzi / teclado)"));
    const he = el("input", { type: "checkbox" });
    he.checked = cfg.hotkeys_enabled !== false;
    he.addEventListener("change", () => saveConfig({ hotkeys_enabled: he.checked }));
    box.appendChild(el("label", { class: "config-row" }, he,
        el("span", { class: "config-info" },
            el("span", { class: "config-name" }, "Ativar atalhos de teclado"),
            el("span", { class: "config-desc" },
                "Liga/desliga os painéis pela tecla escolhida em cada linha acima, sem sair do jogo. "
                + "No Ulanzi/Stream Deck, mapeie cada botão para enviar a tecla. (Só no Windows.)"))));
    for (const [action, label, desc] of [
        ["toggle:context", "Modo contexto", "atalho p/ ligar/desligar 'mostrar só no contexto certo'"],
        ["toggle_all", "Ligar/desligar tudo", "atalho p/ ligar/desligar todos os painéis de uma vez"],
    ]) {
        box.appendChild(el("div", { class: "config-row" },
            el("span", { class: "config-info" },
                el("span", { class: "config-name" }, label),
                el("span", { class: "config-desc" }, desc)),
            el("span", { class: "config-hotkey" },
                el("span", { class: "config-keylabel" }, "tecla"), hotkeySelect(action))));
    }
    box.appendChild(el("p", { class: "hint" },
        "F13–F24 não conflitam com o jogo e são ideais pro Ulanzi/Stream Deck. "
        + "Se o app do controlador não tiver F13–F24, use NUM0–NUM9."));

    // colunas do leaderboard (torre de tempos) — com atalho de tecla por coluna
    box.appendChild(el("h3", { class: "config-h3" }, "Colunas do leaderboard"));
    box.appendChild(el("p", { class: "hint" },
        "Ligue/desligue cada informação da torre. A coluna 'tecla' cria um atalho "
        + "pra mostrar/ocultar aquela info sem sair do jogo (Ulanzi/Stream Deck)."));
    const lbCols = cfg.leaderboard || {};
    for (const [key, name] of LEADERBOARD_COLS) {
        const c = el("input", { type: "checkbox" });
        c.checked = lbCols[key] !== false;
        c.addEventListener("change", () => saveConfig({ leaderboard: { [key]: c.checked } }));
        box.appendChild(el("div", { class: "config-row" }, c,
            el("span", { class: "config-info" }, el("span", { class: "config-name" }, name)),
            el("span", { class: "config-hotkey" },
                el("span", { class: "config-keylabel" }, "tecla"), hotkeySelect("lb:" + key))));
    }

    // orientação do minimapa — ajuste até casar com o mapa do jogo
    box.appendChild(el("h3", { class: "config-h3" }, "Mapa (orientação)"));
    box.appendChild(el("p", { class: "hint" },
        "Se o mapa estiver espelhado ou girado, ajuste aqui até bater com o do jogo "
        + "(muda na hora no overlay)."));
    for (const [key, name, desc] of [
        ["map_flip_h", "Espelhar horizontal", "troca esquerda ↔ direita"],
        ["map_flip_v", "Espelhar vertical", "troca cima ↔ baixo"],
        ["map_rot90", "Girar 90°", "deita / levanta o mapa"],
    ]) {
        const c = el("input", { type: "checkbox" });
        c.checked = cfg[key] === true;
        c.addEventListener("change", () => saveConfig({ [key]: c.checked }));
        box.appendChild(el("label", { class: "config-row" }, c,
            el("span", { class: "config-info" },
                el("span", { class: "config-name" }, name),
                el("span", { class: "config-desc" }, desc))));
    }
}

const LEADERBOARD_COLS = [
    ["tyre", "Pneu (composto + idade)"], ["interval", "Intervalo (p/ o da frente)"],
    ["leader", "Gap para o líder"], ["lastlap", "Última volta"],
    ["wear", "Desgaste do pneu"], ["battery", "Bateria (ERS)"],
    ["avisos", "Avisos (pit, punições, asa)"],
];
async function saveConfig(patch) {
    if (patch.radio_hide_s != null) radioHideS = patch.radio_hide_s;
    if (patch.leaderboard) Object.assign(lbPrefs, patch.leaderboard);
    try {
        await fetch("/api/overlay/config", {
            method: "POST", headers: { "Content-Type": "application/json" },
            body: JSON.stringify(patch),
        });
    } catch (e) { /* ignora */ }
}

// --------------------------- corrida ao vivo ------------------------------
let _liveTick = 0;
async function tickLive() {
    if (activeTab !== "live") return;
    try {
        // live a cada ciclo (HUD/velocidade fluidos); leaderboard a cada 3 ciclos
        const live = await getJSON("/api/live");
        renderConn(live);
        renderWeather(live);
        renderRadio(live);
        renderSpotter(live);
        renderFeed(live);
        renderHud(live);
        if (_liveTick++ % 3 === 0) {
            const lb = await getJSON("/api/leaderboard");
            renderRaceStatus(lb);
            renderLeaderboard(lb);
        }
    } catch (e) { /* servidor reiniciando */ }
}

function renderConn(live) {
    const dot = $("#conn-dot"), txt = $("#conn-text");
    if (live.connected) {
        dot.className = "dot on";
        txt.textContent = `Recebendo (F1 ${live.packet_format || "?"})` +
            (live.track_name ? " · " + live.track_name : "");
    } else {
        dot.className = "dot off";
        txt.textContent = "Jogo não detectado — habilite a telemetria UDP";
    }
}

// Banner de voltas em destaque (volta atual grande + total + restantes)
function renderRaceStatus(lb) {
    const b = $("#race-banner");
    // volta atual = a do PILOTO (não a do líder)
    const cur = lb && (lb.player_lap || lb.current_lap), tot = lb && lb.total_laps;
    if (!cur || !tot) { b.hidden = true; return; }
    b.hidden = false;
    const left = Math.max(0, tot - cur);
    b.innerHTML = "";
    b.appendChild(el("span", { class: "rb-label" }, "VOLTA"));
    b.appendChild(el("span", { class: "rb-cur" }, String(cur)));
    b.appendChild(el("span", { class: "rb-tot" }, "/ " + tot));
    b.appendChild(el("span", { class: "rb-left" }, left + " voltas restantes"));
}

// DRS: aberto agora > liberado (na zona) > indisponível
function drsBadge(live) {
    if (live.drs) return `<span class="drs-on">ABERTO</span>`;
    if (live.drs_allowed) return `<span class="drs-ready">LIBERADO</span>`;
    return `<span class="drs-off">—</span>`;
}

const WEATHER_ICON = {
    0: "☀️", 1: "🌤️", 2: "☁️", 3: "🌦️", 4: "🌧️", 5: "⛈️",
};

// Faixa de clima: condição atual + previsão pros próximos minutos (com chuva)
function renderWeather(live) {
    const strip = $("#weather-strip");
    const fc = live.weather_forecast;
    const hasTemp = live.track_temp != null || live.air_temp != null;
    if (!live.connected || (!fc && !hasTemp)) { strip.hidden = true; return; }
    strip.hidden = false;
    strip.innerHTML = "";

    // bloco "agora"
    const now = el("div", { class: "wx-now" },
        el("span", { class: "wx-label" }, "Agora"),
        el("span", { class: "wx-cond" },
            `${live.weather_name || "—"}` +
            (live.track_temp != null ? ` · pista ${live.track_temp}°` : "") +
            (live.air_temp != null ? ` · ar ${live.air_temp}°` : "")));
    strip.appendChild(now);

    if (!fc || !fc.length) return;
    const chips = el("div", { class: "wx-chips" });
    // alerta de chuva chegando: primeira amostra com chuva_pct alto
    const rainSoon = fc.find((s) => s.chuva_pct >= 40 && s.em_min > 0);
    if (rainSoon) {
        chips.appendChild(el("span", { class: "wx-alert" },
            `🌧️ Chuva em ~${rainSoon.em_min} min (${rainSoon.chuva_pct}%)`));
    }
    fc.slice(0, 6).forEach((s) => {
        if (s.em_min === 0) return;
        const wet = s.chuva_pct >= 40;
        chips.appendChild(el("span", { class: "wx-chip" + (wet ? " wet" : "") },
            el("span", { class: "wx-t" }, `+${s.em_min}min`),
            el("span", {}, `${WEATHER_ICON[s.clima_code] || ""} ${s.chuva_pct}%`)));
    });
    strip.appendChild(chips);
}

// Legendas do engenheiro de pista (vem de live.radio)
const RADIO_COL = { spotter: "#ff9f0a", urgent: "#e10600", lap: "#3aa0ff", info: "#2ecc71" };
let radioHideS = 0;
const lbPrefs = {};          // coluna do leaderboard -> bool (vazio = tudo visível)
let cfgLoaded = false;
function ensureCfg() {       // carrega a config do overlay uma vez (auto-hide + colunas)
    if (cfgLoaded) return;
    cfgLoaded = true;
    getJSON("/api/overlay/config").then((c) => {
        radioHideS = c.radio_hide_s || 0;
        Object.assign(lbPrefs, c.leaderboard || {});
    }).catch(() => { cfgLoaded = false; });
}
function renderRadio(live) {
    const strip = $("#radio-strip");
    ensureCfg();
    let msgs = live.radio || [];
    if (radioHideS > 0) {     // some depois de N segundos, igual ao overlay
        const cutoff = Date.now() / 1000 - radioHideS;
        msgs = msgs.filter((m) => (m.t || 0) >= cutoff);
    }
    msgs = msgs.slice(0, 3).reverse();   // 3 recentes, nova embaixo
    if (!live.connected || !msgs.length) { strip.hidden = true; return; }
    strip.hidden = false;
    strip.innerHTML = "";
    for (const m of msgs) {
        strip.appendChild(el("div", { class: "radio-row", style: `border-left-color:${RADIO_COL[m.kind] || RADIO_COL.info}` },
            el("span", { class: "radio-tag" }, "🎙️ Engenheiro"),
            el("span", { class: "radio-text" }, m.text)));
    }
}

const FEED_ICON = { penalty: "⛔", warning: "⚠️", retire: "🏳️", info: "🏁" };

// Feed de avisos e punições (vem de live.recent_events, mais recente primeiro)
function renderFeed(live) {
    const wrap = $("#feed-wrap"), box = $("#penalty-feed");
    const evs = live.recent_events || [];
    if (!evs.length) { wrap.hidden = true; return; }
    wrap.hidden = false;
    box.innerHTML = "";
    for (const e of evs) {
        box.appendChild(el("div", { class: "feed-row " + e.kind },
            el("span", { class: "feed-ico" }, FEED_ICON[e.kind] || "•"),
            e.lap != null ? el("span", { class: "feed-lap" }, "V" + e.lap) : null,
            el("span", { class: "feed-car" },
                el("span", { class: "feed-bar", style: `background:${e.team_color}` }),
                e.car),
            el("span", { class: "feed-msg" }, e.msg)));
    }
}

// Spotter visual: faixas que acendem quando há carro emparelhado à esq./dir.
function renderSpotter(live) {
    const bar = $("#spotter-bar");
    if (!bar) return;
    const prox = live.proximity || {};
    const onTrack = live.driver_status == null || live.driver_status !== 0;
    if (!live.connected || !onTrack || live.pit_status) { bar.hidden = true; return; }
    const left = !!prox.left, right = !!prox.right;
    bar.hidden = false;
    const ld = prox.left_dist, rd = prox.right_dist;
    bar.innerHTML = "";
    bar.appendChild(el("div", { class: "sp-side" + (left ? " on" : "") },
        el("span", { class: "sp-arrow" }, "◄"),
        el("span", { class: "sp-txt" }, left ? `ESQ ${ld != null ? ld.toFixed(1) + "m" : ""}` : "ESQ")));
    bar.appendChild(el("div", { class: "sp-mid" + (left || right ? " warn" : " free") },
        left && right ? "CARROS DOS DOIS LADOS" : (left || right ? "CARRO AO LADO" : "PISTA LIVRE")));
    bar.appendChild(el("div", { class: "sp-side" + (right ? " on" : "") },
        el("span", { class: "sp-txt" }, right ? `DIR ${rd != null ? rd.toFixed(1) + "m" : ""}` : "DIR"),
        el("span", { class: "sp-arrow" }, "►")));
}

function renderHud(live) {
    const hud = $("#hud");
    const wear = live.tyre_wear; // [TE, TD, DE, DD] = [RL, RR, FL, FR]
    const cells = [
        ["Volta", live.current_lap != null ? live.current_lap +
            (live.total_laps ? " / " + live.total_laps : "") : "—"],
        ["Posição", live.position != null ? "P" + live.position : "—"],
        ["Velocidade", live.speed != null ? `${live.speed}<small> km/h</small>` : "—"],
        ["Marcha", live.gear != null ? (live.gear < 0 ? "R" : live.gear === 0 ? "N" : live.gear) : "—"],
        ["DRS", drsBadge(live)],
        ["Última volta", fmtLap(live.last_lap_time_ms)],
        ["Pneu", `${live.tyre_compound || "—"}${live.tyre_age_laps != null ? ` <small>${live.tyre_age_laps}v</small>` : ""}`],
        ["Comb. (saldo)", live.fuel_remaining_laps != null ? (live.fuel_remaining_laps >= 0 ? "+" : "") + live.fuel_remaining_laps.toFixed(1) : "—"],
    ];
    hud.innerHTML = "";
    for (const [k, v] of cells) {
        hud.appendChild(el("div", { class: "cell" },
            el("div", { class: "k" }, k),
            (() => { const d = el("div", { class: "v" }); d.innerHTML = v; return d; })()));
    }
    // Desgaste por pneu (DT/DD/TE/TD)
    if (wear && wear.length === 4) {
        const labels = ["DT", "DD", "TE", "TD"];      // DianteTraz? -> mapeado abaixo
        // ordem do jogo: [RL, RR, FL, FR] = [TrasEsq, TrasDir, DiantEsq, DiantDir]
        const names = ["Tras.Esq", "Tras.Dir", "Diant.Esq", "Diant.Dir"];
        const cell = el("div", { class: "cell", style: "min-width:220px" },
            el("div", { class: "k" }, "Desgaste dos pneus"));
        const grid = el("div", { style: "display:grid;grid-template-columns:1fr 1fr;gap:4px 14px;margin-top:6px" });
        wear.forEach((w, i) => {
            grid.appendChild(el("div", { style: "display:flex;justify-content:space-between;font-size:13px" },
                el("span", { style: "color:#9a9ab2" }, names[i]),
                el("span", { style: `font-weight:700;color:${wearColor(w)}` }, `${w.toFixed(0)}%`)));
        });
        cell.appendChild(grid);
        hud.appendChild(cell);
    }
}

// Célula de punição: tempo + advertências + drive-through/stop&go a cumprir
function avisosBadges(r) {
    const b = [];
    if (r.num_pit_stops) b.push(el("span", { class: "badge pit" }, r.num_pit_stops + "×"));
    if (r.penalties_sec) b.push(el("span", { class: "badge pen" }, "+" + r.penalties_sec + "s"));
    if (r.unserved_drive_through) b.push(el("span", { class: "badge pen-serve", title: "Drive-through a cumprir" }, r.unserved_drive_through + "× DT"));
    if (r.unserved_stop_go) b.push(el("span", { class: "badge pen-serve", title: "Stop&go a cumprir" }, r.unserved_stop_go + "× S&G"));
    const warns = (r.total_warnings || 0);
    if (warns) b.push(el("span", { class: "badge warn", title: (r.corner_cut_warnings || 0) + " por cortar curva" }, "⚠ " + warns));
    if (r.front_wing_damage) b.push(el("span", { class: "badge wing" }, "Asa " + r.front_wing_damage + "%"));
    return b;
}

function tyreCircle(name, color) {
    const letter = TYRE_LETTER[name] || (name ? name[0] : "?");
    return el("span", { class: "tyre", style: `border-color:${color}`, title: name }, letter);
}

function renderLeaderboard(lb) {
    const box = $("#leaderboard");
    if (!lb.rows || !lb.rows.length) {
        box.innerHTML = "";
        box.appendChild(el("div", { class: "empty" }, "Aguardando dados da corrida…"));
        return;
    }
    const show = (c) => lbPrefs[c] !== false;   // coluna visível? (config do piloto)
    const table = el("table", { class: "tower" });
    const head = [el("th", {}, "Pos"), el("th", {}, "Piloto")];
    if (show("tyre")) head.push(el("th", {}, "Pneu"));
    if (show("interval")) head.push(el("th", {}, "Intervalo"));
    if (show("leader")) head.push(el("th", {}, "Líder"));
    if (show("lastlap")) head.push(el("th", {}, "Últ. volta"));
    if (show("wear")) head.push(el("th", {}, "Desgaste"));
    if (show("battery")) head.push(el("th", {}, "Bateria"));
    table.appendChild(el("tr", {}, ...head));
    for (const r of lb.rows) {
        const dnf = r.result_code >= 4 && r.result_code !== 3;
        const tr = el("tr", { class: "row" + (r.is_player ? " player" : "") });
        tr.appendChild(el("td", {},
            el("div", { class: "pos-cell" },
                el("span", { class: "pos-bar", style: `background:${r.team_color}` }),
                el("span", { class: "pos-num" }, String(r.position)))));
        tr.appendChild(el("td", {},
            el("div", { class: "driver" },
                el("span", { class: "name" }, r.name,
                    r.race_number != null ? el("span", { class: "num-badge" }, "#" + r.race_number) : null,
                    r.best_lap_overall ? el("span", { class: "badge fastlap",
                        title: "Volta mais rápida da corrida: " + fmtLap(r.best_lap_ms) }, "VR") : null),
                el("span", { class: "team" }, r.team))));
        if (show("tyre")) tr.appendChild(el("td", {},
            tyreCircle(r.tyre, r.tyre_color),
            el("span", { class: "tyre-age" }, r.tyre_age_laps != null ? r.tyre_age_laps + "v" : "")));
        if (show("interval")) tr.appendChild(el("td", { class: "mono interval" }, fmtGap(r.delta_front_ms)));
        if (show("leader")) tr.appendChild(el("td", { class: "mono gap-leader" }, fmtGap(r.delta_leader_ms)));
        if (show("lastlap")) tr.appendChild(el("td", { class: "mono lastlap" + (r.best_lap_overall ? " purple" : "") },
            fmtLap(r.last_lap_time_ms)));
        if (show("wear")) {
            const wmax = r.tyre_wear_max;
            tr.appendChild(el("td", {},
                el("span", { class: "wear-bar" },
                    el("span", { class: "wear-fill", style: `width:${Math.min(100, wmax || 0)}%;background:${wearColor(wmax)}` })),
                el("span", { class: "tyre-age", style: "margin-left:6px" }, wmax != null ? wmax.toFixed(0) + "%" : "")));
        }
        if (show("battery")) tr.appendChild(el("td", {},
            el("span", { class: "ers-bar", title: r.ers_pct != null ? "ERS " + Math.round(r.ers_pct) + "%" : "" },
                el("span", { class: "ers-fill", style: `width:${Math.min(100, r.ers_pct || 0)}%;background:${ersColor(r.ers_pct)}` })),
            el("span", { class: "tyre-age", style: "margin-left:6px" }, r.ers_pct != null ? Math.round(r.ers_pct) + "%" : "")));
        // avisos: marcação FLUTUANTE na linha (sem coluna, sem espaço reservado)
        if (show("avisos")) {
            const badges = avisosBadges(r);
            if (badges.length) tr.firstChild.appendChild(
                el("div", { class: "row-avisos" }, ...badges));
        }
        if (dnf) tr.style.opacity = "0.5";
        table.appendChild(tr);
    }
    box.innerHTML = "";
    box.appendChild(table);
}

// ----------------------------- pistas -------------------------------------
let tracksCache = null;
async function loadTracks(selectSel, onChange) {
    const sel = $(selectSel);
    if (!tracksCache) tracksCache = await getJSON("/api/tracks");
    const prev = sel.value;
    sel.innerHTML = "";
    if (!tracksCache.length) {
        sel.appendChild(el("option", {}, "Nenhuma pista com dados ainda"));
        if (onChange) onChange(null);
        return;
    }
    for (const t of tracksCache) {
        sel.appendChild(el("option", { value: t.track_id },
            `${t.track_name} — ${t.num_sessions} sessão(ões)` +
            (t.best_lap_ms ? `, melhor ${fmtLap(t.best_lap_ms)}` : "")));
    }
    if (prev) sel.value = prev;
    sel.onchange = () => onChange(parseInt(sel.value));
    onChange(parseInt(sel.value));
}

// Pistas com setup de referência (offline) — independe de sessão gravada.
let refTracksCache = null;
async function loadRefTracks(selectSel, onChange) {
    const sel = $(selectSel);
    if (!refTracksCache) refTracksCache = await getJSON("/api/reference/tracks");
    const prev = sel.value;
    sel.innerHTML = "";
    if (!refTracksCache.length) {
        sel.appendChild(el("option", {}, "Nenhuma pista com referência"));
        if (onChange) onChange(null);
        return;
    }
    for (const t of refTracksCache) {
        sel.appendChild(el("option", { value: t.track_id }, t.track_name));
    }
    if (prev) sel.value = prev;
    sel.onchange = () => onChange(parseInt(sel.value));
    onChange(parseInt(sel.value));
}

// ---------------------------- evolução ------------------------------------
async function renderEvolution(trackId) {
    const tableBox = $("#evo-table");
    if (trackId == null) { tableBox.innerHTML = ""; drawChart($("#evo-chart"), []); return; }
    const data = await getJSON(`/api/track/${trackId}/evolution`);
    if (!data.length) {
        tableBox.innerHTML = "";
        tableBox.appendChild(el("div", { class: "empty" }, "Sem voltas válidas nesta pista."));
        drawChart($("#evo-chart"), []);
        return;
    }
    const points = data.map((d, i) => ({ x: i, y: d.best_lap_ms }));
    drawChart($("#evo-chart"), [
        { label: "Melhor volta", color: "#e10600", points, width: 2.5, dots: true, fill: true }
    ], {
        xTicks: Math.min(data.length - 1, 8) || 1,
        formatY: (v) => fmtLap(v),
        formatX: (v) => { const d = data[Math.round(v)]; return d ? fmtDate(d.started_at).split(" ")[0] : ""; },
    });

    const tbl = el("table", { class: "grid" });
    tbl.appendChild(el("tr", {}, el("th", {}, "Data"), el("th", {}, "Sessão"),
        el("th", { class: "mono" }, "Melhor volta"), el("th", { class: "mono" }, "Voltas"),
        el("th", {}, "Pista (°C)")));
    let best = Math.min(...data.map((d) => d.best_lap_ms));
    for (const d of data) {
        const isBest = d.best_lap_ms === best;
        tbl.appendChild(el("tr", {},
            el("td", {}, fmtDate(d.started_at)),
            el("td", {}, d.session_type_name),
            el("td", { class: "mono" + (isBest ? " purpletime" : "") }, fmtLap(d.best_lap_ms)),
            el("td", { class: "mono" }, String(d.num_laps)),
            el("td", {}, d.track_temp != null ? d.track_temp + "°" : "—")));
    }
    tableBox.innerHTML = "";
    tableBox.appendChild(tbl);
}

// ---------------------------- telemetria ----------------------------------
let telSessions = null;
async function loadSessionsInto(selectSel, onChange) {
    const sel = $(selectSel);
    telSessions = await getJSON("/api/sessions");
    sel.innerHTML = "";
    const withData = telSessions;
    if (!withData.length) {
        sel.appendChild(el("option", {}, "Nenhuma sessão ainda"));
        onChange(null); return;
    }
    for (const s of withData) {
        sel.appendChild(el("option", { value: s.session_uid },
            `${fmtDate(s.started_at)} — ${s.track_name} (${s.session_type_name})`));
    }
    sel.onchange = () => onChange(sel.value);
    onChange(sel.value);
}

let telLaps = [];
async function onTelSession(uid) {
    const a = $("#tel-lapA"), b = $("#tel-lapB");
    a.innerHTML = ""; b.innerHTML = "";
    if (!uid) { drawTelemetry(); return; }
    const info = await getJSON(`/api/session/${uid}`);
    telLaps = info.laps_with_telemetry || [];
    if (!telLaps.length) {
        a.appendChild(el("option", {}, "Sem telemetria"));
        drawTelemetry(); return;
    }
    b.appendChild(el("option", { value: "" }, "— nenhuma —"));
    for (const l of telLaps) {
        const label = `Volta ${l.lap_number}` + (l.lap_time_ms ? " — " + fmtLap(l.lap_time_ms) : "");
        a.appendChild(el("option", { value: l.lap_number }, label));
        b.appendChild(el("option", { value: l.lap_number }, label));
    }
    a.onchange = b.onchange = () => loadTelemetry(uid);
    loadTelemetry(uid);
}

async function loadTelemetry(uid) {
    const lapA = $("#tel-lapA").value, lapB = $("#tel-lapB").value;
    const reqs = [getJSON(`/api/lap/${uid}/${lapA}/telemetry`)];
    if (lapB) reqs.push(getJSON(`/api/lap/${uid}/${lapB}/telemetry`));
    const [tA, tB] = await Promise.all(reqs.concat(lapB ? [] : [Promise.resolve(null)]));
    drawTelemetry(tA, tB, lapA, lapB);
}

function drawTelemetry(tA, tB, lapA, lapB) {
    if (!tA) { ["#tel-speed", "#tel-pedals", "#tel-gear"].forEach((s) => drawChart($(s), [])); $("#tel-legend").innerHTML = ""; return; }
    const cA = "#e10600", cB = "#27a6e1";
    const sp = [{ color: cA, points: tA.map((p) => ({ x: p.lap_distance, y: p.speed })), width: 2 }];
    const th = [{ color: "#2ecc71", points: tA.map((p) => ({ x: p.lap_distance, y: p.throttle * 100 })), width: 2 },
        { color: "#e10600", points: tA.map((p) => ({ x: p.lap_distance, y: p.brake * 100 })), width: 2 }];
    const gr = [{ color: cA, points: tA.map((p) => ({ x: p.lap_distance, y: p.gear })), width: 2, step: true }];
    if (tB && tB.length) {
        sp.push({ color: cB, points: tB.map((p) => ({ x: p.lap_distance, y: p.speed })), width: 2 });
        gr.push({ color: cB, points: tB.map((p) => ({ x: p.lap_distance, y: p.gear })), width: 2, step: true });
    }
    drawChart($("#tel-speed"), sp, { yMin: 0, formatY: (v) => v.toFixed(0), formatX: (v) => v.toFixed(0) + "m" });
    drawChart($("#tel-pedals"), th, { yMin: 0, yMax: 100, formatY: (v) => v.toFixed(0) + "%", formatX: (v) => v.toFixed(0) + "m" });
    drawChart($("#tel-gear"), gr, { yMin: 0, yMax: 8, formatY: (v) => v.toFixed(0), formatX: (v) => v.toFixed(0) + "m" });

    const leg = $("#tel-legend"); leg.innerHTML = "";
    leg.appendChild(el("span", {}, el("i", { style: `background:${cA}` }), `Volta ${lapA}`));
    if (tB && tB.length) leg.appendChild(el("span", {}, el("i", { style: `background:${cB}` }), `Volta ${lapB}`));
    leg.appendChild(el("span", {}, el("i", { style: "background:#2ecc71" }), "Acelerador"));
    leg.appendChild(el("span", {}, el("i", { style: "background:#e10600" }), "Freio"));
}

// ------------------------------- setups -----------------------------------
const SETUP_FIELDS = [
    ["Asa dianteira", "front_wing"], ["Asa traseira", "rear_wing"],
    ["Diferencial (acel.)", "on_throttle", "%"], ["Diferencial (desacel.)", "off_throttle", "%"],
    ["Câmber dianteiro", "front_camber", "°"], ["Câmber traseiro", "rear_camber", "°"],
    ["Convergência diant.", "front_toe", "°"], ["Convergência tras.", "rear_toe", "°"],
    ["Suspensão diant.", "front_suspension"], ["Suspensão tras.", "rear_suspension"],
    ["Barra estab. diant.", "front_arb"], ["Barra estab. tras.", "rear_arb"],
    ["Altura diant.", "front_height"], ["Altura tras.", "rear_height"],
    ["Pressão freio", "brake_pressure", "%"], ["Balanço freio", "brake_bias", "%"],
    ["Freio motor", "engine_braking", "%"],
    ["Pressão DD", "fr_pressure", " psi"], ["Pressão DE", "fl_pressure", " psi"],
    ["Pressão TD", "rr_pressure", " psi"], ["Pressão TE", "rl_pressure", " psi"],
    ["Combustível", "fuel_load", " kg"],
];

async function renderSetups(trackId) {
    const box = $("#setup-cards");
    box.innerHTML = "";
    if (trackId == null) return;
    const setups = await getJSON(`/api/track/${trackId}/setups`);
    if (!setups.length) {
        box.appendChild(el("div", { class: "empty" }, "Nenhum setup capturado nesta pista ainda."));
        return;
    }
    for (const s of setups) {
        const card = el("div", { class: "card" },
            el("h3", {}, s.session_type_name),
            el("div", { class: "sub" }, fmtDate(s.captured_at) +
                (s.best_lap_ms ? " · melhor volta " + fmtLap(s.best_lap_ms) : "")));
        for (const [label, key, suffix] of SETUP_FIELDS) {
            const v = s[key];
            card.appendChild(el("div", { class: "kv" },
                el("span", { class: "label" }, label),
                el("span", { class: "val" }, v != null ? v + (suffix || "") : "—")));
        }
        box.appendChild(card);
    }
}

// -------------------- setups de referência + assistente -------------------
const SESS_LABEL = { qualify: "Classificação", race: "Corrida", parc_ferme: "Parc Fermé (Q+R)" };
const COND_LABEL = { dry: "Pista seca", wet: "Pista molhada" };
const TYPE_LABEL = { esports: "Esports", safe: "Seguro" };
let refSetupsCache = [];        // setups da pista atual (todas as fontes)

function fillSelect(sel, values, labelFn, allLabel) {
    const cur = sel.value;
    sel.innerHTML = "";
    sel.appendChild(el("option", { value: "" }, allLabel));
    for (const v of values) sel.appendChild(el("option", { value: v }, labelFn(v)));
    if ([...sel.options].some((o) => o.value === cur)) sel.value = cur;
}

async function renderRefSetups(trackId) {
    const box = $("#refsetup-cards");
    box.innerHTML = "";
    if (trackId == null) return;
    let data;
    try { data = await getJSON(`/api/track/${trackId}/reference`); }
    catch (e) { box.appendChild(el("div", { class: "empty" }, "Erro ao carregar.")); return; }
    refSetupsCache = data.setups || [];
    if (!refSetupsCache.length) {
        ["#refsetup-source", "#refsetup-session", "#refsetup-condition"].forEach((s) => $(s).innerHTML = "");
        box.appendChild(el("div", { class: "empty" }, "Nenhum setup de referência para esta pista."));
        return;
    }
    const uniq = (k) => [...new Set(refSetupsCache.map((s) => s[k]).filter(Boolean))];
    fillSelect($("#refsetup-source"), uniq("source"), (v) => v, "Todas as fontes");
    fillSelect($("#refsetup-session"), uniq("session"), (v) => SESS_LABEL[v] || v, "Todas as sessões");
    fillSelect($("#refsetup-condition"), uniq("condition"), (v) => COND_LABEL[v] || v, "Todas as condições");
    $("#refsetup-source").onchange = $("#refsetup-session").onchange =
        $("#refsetup-condition").onchange = drawRefSetupCards;
    drawRefSetupCards();
}

function drawRefSetupCards() {
    const box = $("#refsetup-cards");
    box.innerHTML = "";
    const fSrc = $("#refsetup-source").value, fSes = $("#refsetup-session").value, fCon = $("#refsetup-condition").value;
    const list = refSetupsCache.filter((s) =>
        (!fSrc || s.source === fSrc) && (!fSes || s.session === fSes) && (!fCon || s.condition === fCon));
    if (!list.length) { box.appendChild(el("div", { class: "empty" }, "Nenhum setup com esses filtros.")); return; }
    for (const s of list) {
        const isTarget = activeTargetId && s.id === activeTargetId.id && s.source === activeTargetId.source;
        const head = el("div", { class: "refset-head" },
            el("span", { class: "badge" }, s.source || "?"),
            s.type ? el("span", { class: "badge alt" }, TYPE_LABEL[s.type] || s.type) : "");
        const btn = el("button", { class: "btn" + (isTarget ? " on" : ""),
            onclick: () => setSetupTarget(s) }, isTarget ? "✓ Alvo atual" : "Usar como alvo");
        const card = el("div", { class: "card" + (isTarget ? " card-active" : "") },
            head,
            el("h3", {}, [SESS_LABEL[s.session] || s.session, COND_LABEL[s.condition] || s.condition].filter(Boolean).join(" · ")));
        for (const [key, label, unit] of REF_FIELDS) {
            const v = (s.values || {})[key];
            if (v == null) continue;
            card.appendChild(el("div", { class: "kv" },
                el("span", { class: "label" }, label),
                el("span", { class: "val" }, fmtNum(v) + (unit || ""))));
        }
        card.appendChild(btn);
        box.appendChild(card);
    }
}

// rótulos/unidades dos campos (espelha reference.SETUP_FIELDS, ordem das telas do jogo)
const REF_FIELDS = [
    ["front_wing", "Asa dianteira", ""], ["rear_wing", "Asa traseira", ""],
    ["on_throttle", "Diferencial acel.", "%"], ["off_throttle", "Diferencial desacel.", "%"],
    ["engine_braking", "Freio motor", "%"],
    ["front_camber", "Câmber diant.", "°"], ["rear_camber", "Câmber tras.", "°"],
    ["front_toe", "Convergência diant.", "°"], ["rear_toe", "Convergência tras.", "°"],
    ["front_suspension", "Suspensão diant.", ""], ["rear_suspension", "Suspensão tras.", ""],
    ["front_arb", "Barra estab. diant.", ""], ["rear_arb", "Barra estab. tras.", ""],
    ["front_height", "Altura diant.", ""], ["rear_height", "Altura tras.", ""],
    ["brake_pressure", "Pressão freio", "%"], ["brake_bias", "Balanço freio", "%"],
    ["fr_pressure", "Pressão DD", " psi"], ["fl_pressure", "Pressão DE", " psi"],
    ["rr_pressure", "Pressão TD", " psi"], ["rl_pressure", "Pressão TE", " psi"],
];

function fmtNum(v) {
    if (v == null) return "—";
    if (typeof v === "number" && !Number.isInteger(v)) return (Math.round(v * 100) / 100).toString();
    return String(v);
}

let activeTargetId = null;      // {id, source} do alvo atual

async function setSetupTarget(s) {
    try {
        const r = await fetch("/api/setup/target", { method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({ id: s.id, source: s.source }) });
        const j = await r.json();
        activeTargetId = j.target ? { id: j.target.id, source: j.target.source } : null;
    } catch (e) { /* silencioso */ }
    drawRefSetupCards();
    refreshSetupCompare();
}

async function clearSetupTarget() {
    try { await fetch("/api/setup/target", { method: "POST",
        headers: { "Content-Type": "application/json" }, body: JSON.stringify({ clear: true }) }); }
    catch (e) { /* */ }
    activeTargetId = null;
    drawRefSetupCards();
    refreshSetupCompare();
}

async function refreshSetupCompare() {
    const box = $("#refsetup-compare");
    if (!box) return;
    let d;
    try { d = await getJSON("/api/setup/compare"); }
    catch (e) { box.hidden = true; return; }
    if (!d.target) { box.hidden = true; activeTargetId = null; return; }
    activeTargetId = { id: d.target.id, source: d.target.source };
    box.hidden = false;
    const ok = d.rows.filter((r) => r.match).length, total = d.rows.length;
    const inGarage = d.current != null;
    box.innerHTML = "";
    box.appendChild(el("div", { class: "sc-head" },
        el("div", {},
            el("span", { class: "sc-title" }, "Alvo: " + (d.target.title || "—")),
            el("span", { class: "badge" }, d.target.source || "")),
        el("div", { class: "sc-count" + (ok === total ? " all" : "") },
            inGarage ? `${ok}/${total} certos` : "abra a garagem no jogo"),
        el("button", { class: "btn small", onclick: clearSetupTarget }, "Limpar alvo")));
    const grid = el("div", { class: "sc-grid" });
    for (const r of d.rows) {
        const cur = r.current == null ? "—" : fmtNum(r.current);
        const dir = (!r.match && r.current != null) ? (r.current < r.target ? " ▲" : " ▼") : "";
        grid.appendChild(el("div", { class: "sc-row" + (r.match ? " ok" : (inGarage ? " off" : "")) },
            el("span", { class: "sc-label" }, r.label),
            el("span", { class: "sc-cur" }, cur + dir),
            el("span", { class: "sc-arrow" }, "→"),
            el("span", { class: "sc-tgt" }, fmtNum(r.target) + (r.unit ? (r.unit === "psi" ? " psi" : r.unit) : ""))));
    }
    box.appendChild(grid);
}

// poller leve: só busca quando a aba de setups de referência está visível
setInterval(() => { if (activeTab === "refsetups") refreshSetupCompare(); }, 1000);

// ----------------------------- rivais -------------------------------------
const COMPOUND_COLOR = { 7: "#43b02a", 8: "#0067b1", 16: "#da291c", 17: "#ffd700", 18: "#f0f0f0" };
const COMPOUND_LETTER = { 7: "I", 8: "W", 16: "S", 17: "M", 18: "H" };
let rivUid = null, rivPlayerIdx = null, rivCars = [];

async function renderRivals(uid) {
    rivUid = uid;
    if (!uid) return;
    await renderRivalStrategy(uid);
    await renderStrategyRec(uid);
    await setupRivalCompare(uid);
}

const STRAT_COL = { "Macio": "#da291c", "Médio": "#ffd700", "Duro": "#c9c9c9", "Inter": "#43b02a", "Chuva": "#0067b1" };
function fmtLapShort(ms) { return ms == null ? "—" : fmtLap(ms); }
async function renderStrategyRec(uid) {
    const box = $("#riv-strategy-rec"); box.innerHTML = "";
    let info, d;
    try { info = await getJSON(`/api/session/${uid}/cars`); }
    catch (e) { return; }
    const tid = info.track_id;
    if (tid == null) { box.appendChild(el("div", { class: "empty" }, "Pista desconhecida.")); return; }
    try { d = await getJSON(`/api/track/${tid}/strategy`); }
    catch (e) { box.appendChild(el("div", { class: "empty" }, "Sem dados suficientes.")); return; }
    // modelo por composto
    const mdl = el("div", { class: "strat-model" });
    for (const [code, m] of Object.entries(d.model || {})) {
        mdl.appendChild(el("div", { class: "sm-chip" },
            el("span", { class: "sm-dot", style: `background:${STRAT_COL[m.name] || "#888"}` }),
            el("span", {}, `${m.name}: ${(m.fresh_ms / 1000).toFixed(2)}s base · degr ${(m.deg_ms / 1000).toFixed(3)}s/v · ${m.n} voltas`)));
    }
    box.appendChild(el("div", { class: "hint" }, `Para corrida de ${d.total_laps} voltas · pit ≈ ${d.pit_loss_s}s`));
    box.appendChild(mdl);
    const s = d.strategy;
    if (!s) { box.appendChild(el("div", { class: "empty" }, "Dados insuficientes para recomendar (rode mais voltas com compostos variados).")); return; }
    const plan = (p, title, hl) => {
        const seq = p.compounds.map((c, i) => `${c} (${p.stints[i]}v)`).join(" → ");
        const pits = p.pit_laps.length ? `pit nas voltas ${p.pit_laps.join(", ")}` : "sem parada";
        const dl = p.delta_ms ? ` (+${(p.delta_ms / 1000).toFixed(1)}s)` : " ⭐ melhor";
        return el("div", { class: "strat-plan" + (hl ? " best" : "") },
            el("div", { class: "sp-title" }, `${title} — ${p.stops} parada(s)${dl}`),
            el("div", { class: "sp-seq" }, seq),
            el("div", { class: "sp-pit" }, pits));
    };
    box.appendChild(plan(s.best, "Recomendado", true));
    for (const ns of Object.keys(s.by_stops)) {
        if (JSON.stringify(s.by_stops[ns].stints) !== JSON.stringify(s.best.stints))
            box.appendChild(plan(s.by_stops[ns], `Alternativa`, false));
    }
    box.appendChild(el("div", { class: "hint" },
        "Baseado no ritmo/desgaste MEDIDOS (não modela o efeito do combustível, que mascara a "
        + "degradação). A recomendação fica mais precisa quanto mais corridas você gravar nesta pista."));
}

function rivStatus(d, cars, samplesCars) {
    // diagnóstico: o que foi capturado + dica quando falta (online restrito)
    const nCars = cars.length;
    const nLaps = d.laps.length;
    const withCompound = d.laps.filter((l) => l.compound != null).length;
    const rivals = cars.filter((ci) => {
        const drv = (d.drivers || []).find((x) => x.car_idx === ci);
        return !drv || !drv.is_player;
    }).length;
    const parts = [`${nCars} carro(s)`, `${nLaps} voltas registradas`,
        `${samplesCars} com pilotagem gravada`];
    const box = el("div", { class: "riv-status" }, "Captura: " + parts.join(" · "));
    const notes = [];
    if (rivals === 0)
        notes.push("Só o seu carro foi capturado — no online os rivais podem estar com a "
            + "telemetria RESTRITA (cada um escolhe), então o jogo não envia os dados deles.");
    else if (withCompound === 0)
        notes.push("Os tempos/posições dos rivais vieram, mas o composto/desgaste não "
            + "(telemetria restrita no online). A comparação de ritmo abaixo ainda funciona.");
    if (notes.length) box.appendChild(el("div", { class: "hint" }, notes.join(" ")));
    return box;
}

async function renderRivalStrategy(uid) {
    const box = $("#riv-strategy");
    box.innerHTML = "";
    let d, carsInfo = { samples: [] };
    try { d = await getJSON(`/api/session/${uid}/strategy`); }
    catch (e) { box.appendChild(el("div", { class: "empty" }, "Erro ao carregar.")); return; }
    try { carsInfo = await getJSON(`/api/session/${uid}/cars`); } catch (e) { /* ok */ }
    const byCar = {};
    for (const l of d.laps) (byCar[l.car_idx] = byCar[l.car_idx] || []).push(l);
    const cars = Object.keys(byCar).map(Number);
    box.appendChild(rivStatus(d, cars, (carsInfo.samples || []).length));
    if (!cars.length) {
        box.appendChild(el("div", { class: "empty" },
            "Nenhum dado de carro nesta sessão. Se foi corrida online, veja a nota acima; "
            + "verifique também se selecionou a sessão da CORRIDA (não o lobby pós-corrida)."));
        return;
    }
    // ordena pela posição final (última volta conhecida)
    const lastPos = (ci) => { const a = byCar[ci]; return (a[a.length - 1] || {}).position || 99; };
    cars.sort((a, b) => lastPos(a) - lastPos(b));
    const maxLap = Math.max(...d.laps.map((l) => l.lap_number));
    const drv = {}; (d.drivers || []).forEach((x) => drv[x.car_idx] = x);

    const table = el("table", { class: "strat" });
    const head = [el("th", {}, "Pos"), el("th", {}, "Piloto"), el("th", {}, "Melhor"),
        el("th", {}, "Stints / pneu por volta")];
    table.appendChild(el("tr", {}, ...head));
    for (const ci of cars) {
        const info = drv[ci] || {};
        const laps = byCar[ci];
        const best = Math.min(...laps.map((l) => l.lap_time_ms || Infinity));
        const lapByN = {}; laps.forEach((l) => lapByN[l.lap_number] = l);
        const cells = el("div", { class: "strat-laps" });
        for (let n = 1; n <= maxLap; n++) {
            const l = lapByN[n];
            // composto colorido; se não veio (online restrito) mas a volta existe,
            // mostra cinza visível; sem volta = fundo escuro.
            const col = !l ? "#1a1a22"
                : (l.compound != null ? (COMPOUND_COLOR[l.compound] || "#888") : "#46465a");
            const wear = l && l.tyre_wear != null ? Math.round(l.tyre_wear) : null;
            const tip = l ? `V${n} ${COMPOUND_LETTER[l.compound] || "?"}` +
                (wear != null ? ` ${wear}%` : "") + (l.lap_time_ms ? " " + fmtLap(l.lap_time_ms) : "") +
                (l.pit ? ` (${l.pit} pit)` : "") : `V${n}`;
            cells.appendChild(el("span", { class: "strat-cell", title: tip,
                style: `background:${col};${wear != null && wear >= 80 ? "outline:2px solid #e10600;" : ""}` }));
        }
        table.appendChild(el("tr", { class: info.is_player ? "me" : "" },
            el("td", {}, String(lastPos(ci) === 99 ? "—" : lastPos(ci))),
            el("td", {}, (info.name || `Carro ${ci}`) + (info.is_player ? " (você)" : "")),
            el("td", {}, isFinite(best) ? fmtLap(best) : "—"),
            el("td", {}, cells)));
    }
    box.appendChild(table);
    box.appendChild(el("div", { class: "hint" },
        "Cada quadradinho = 1 volta (cor = composto; contorno vermelho = pneu ≥80%). Passe o mouse para tempo/desgaste."));
}

async function setupRivalCompare(uid) {
    let d;
    try { d = await getJSON(`/api/session/${uid}/cars`); }
    catch (e) { return; }
    rivPlayerIdx = (d.drivers.find((x) => x.is_player) || {}).car_idx;
    const drvName = {}; d.drivers.forEach((x) => drvName[x.car_idx] = x.name || `Carro ${x.car_idx}`);
    rivCars = (d.samples || []).map((s) => s.car_idx);
    const maxLaps = Math.max(0, ...(d.samples || []).map((s) => s.laps));
    const fill = (sel, opts) => {
        sel.innerHTML = "";
        for (const [val, lbl] of opts) sel.appendChild(el("option", { value: val }, lbl));
    };
    fill($("#riv-lap"), Array.from({ length: maxLaps }, (_, i) => [i + 1, "Volta " + (i + 1)]));
    const rivals = rivCars.filter((c) => c !== rivPlayerIdx);
    fill($("#riv-car"), rivals.map((c) => [c, drvName[c] || ("Carro " + c)]));
    $("#riv-lap").onchange = $("#riv-car").onchange = $("#riv-color").onchange = drawRivalLine;
    drawRivalLine();
}

async function drawRivalLine() {
    const cv = $("#riv-line"); if (!cv || !rivUid) return;
    const lap = $("#riv-lap").value, rival = $("#riv-car").value;
    const mode = $("#riv-color").value;
    const want = [rivPlayerIdx, rival].filter((x) => x != null && x !== "");
    const legend = $("#riv-line-legend");
    if (!want.length) { legend.textContent = "Sem dados de pilotagem."; return; }
    let d;
    try { d = await getJSON(`/api/session/${rivUid}/line?lap=${lap}&cars=${want.join(",")}`); }
    catch (e) { return; }
    const sets = Object.entries(d.cars).map(([ci, pts]) => ({ ci: Number(ci), pts }))
        .filter((s) => s.pts && s.pts.length > 2);
    const ctx = cv.getContext("2d");
    const W = cv.width, H = cv.height; ctx.clearRect(0, 0, W, H); ctx.fillStyle = "#0e0e16"; ctx.fillRect(0, 0, W, H);
    if (!sets.length) { legend.textContent = "Sem amostras nesta volta."; return; }
    const all = sets.flatMap((s) => s.pts);
    const xs = all.map((p) => p.x), zs = all.map((p) => p.z);
    const minx = Math.min(...xs), maxx = Math.max(...xs), minz = Math.min(...zs), maxz = Math.max(...zs);
    const pad = 30, k = Math.min((W - 2 * pad) / (maxx - minx || 1), (H - 2 * pad) / (maxz - minz || 1));
    const offx = (W - (maxx - minx) * k) / 2, offz = (H - (maxz - minz) * k) / 2;
    const TX = (x) => offx + (x - minx) * k, TZ = (z) => H - (offz + (z - minz) * k);
    const DRIVER_COLOR = ["#ffffff", "#e10600"];   // você = branco, rival = vermelho
    sets.forEach((s, si) => {
        const pts = s.pts;
        for (let i = 1; i < pts.length; i++) {
            const a = pts[i - 1], b = pts[i];
            if (Math.hypot(b.x - a.x, b.z - a.z) > 60) continue;  // pula salto (volta)
            let col;
            if (mode === "driver") col = DRIVER_COLOR[si % 2];
            else if (mode === "brake") col = b.brake > 0.2 ? "#e10600" : "#3a7";
            else { const sp = Math.max(0, Math.min(1, (b.speed || 0) / 340));
                col = `rgb(${Math.round(255 * (1 - sp))},${Math.round(80 + 150 * sp)},${Math.round(60)})`; }
            ctx.strokeStyle = col; ctx.lineWidth = si === 0 ? 3 : 2;
            ctx.beginPath(); ctx.moveTo(TX(a.x), TZ(a.z)); ctx.lineTo(TX(b.x), TZ(b.z)); ctx.stroke();
        }
    });
    const names = sets.map((s, i) => (s.ci === rivPlayerIdx ? "você (linha grossa)" : "rival"));
    legend.innerHTML = mode === "driver"
        ? "Branco = você · Vermelho = rival"
        : (mode === "brake" ? "Vermelho = freando · Verde = sem freio"
            : "Vermelho = devagar → Verde = rápido (linha grossa = você)");
}

// ----------------------- gráficos SVG (sem biblioteca) --------------------
function chartTicks(min, max, n) {
    if (max <= min) return [min];
    const out = [];
    for (let i = 0; i <= n; i++) out.push(Math.round(min + (max - min) * i / n));
    return out;
}
function chartXTicks(min, max, n) {
    const out = [], step = Math.max(1, Math.round((max - min) / n));
    for (let v = min; v <= max; v += step) out.push({ v, label: String(v) });
    return out;
}
function svgLineChart(o) {
    const w = o.w || 600, h = o.h || 230;
    const padL = 52, padR = 16, padT = 14, padB = 26;
    const iw = w - padL - padR, ih = h - padT - padB;
    const sx = x => padL + (o.xmax === o.xmin ? 0 : (x - o.xmin) / (o.xmax - o.xmin)) * iw;
    const sy = y => { const t = o.ymax === o.ymin ? 0 : (y - o.ymin) / (o.ymax - o.ymin); return padT + (o.invertY ? t : 1 - t) * ih; };
    let s = `<svg viewBox="0 0 ${w} ${h}" class="ins-chart" xmlns="http://www.w3.org/2000/svg">`;
    (o.yticks || []).forEach(t => {
        const y = sy(t.v).toFixed(1);
        s += `<line x1="${padL}" y1="${y}" x2="${w - padR}" y2="${y}" stroke="#2a2a3a" stroke-width="1"/>`;
        s += `<text x="${padL - 6}" y="${(+y + 3).toFixed(1)}" fill="#8a8a9a" font-size="10" text-anchor="end">${t.label}</text>`;
    });
    (o.xticks || []).forEach(t => {
        s += `<text x="${sx(t.v).toFixed(1)}" y="${h - 8}" fill="#8a8a9a" font-size="10" text-anchor="middle">${t.label}</text>`;
    });
    (o.series || []).forEach(ser => {
        if (!ser.pts || !ser.pts.length) return;
        const d = ser.pts.map((p, i) => (i ? "L" : "M") + sx(p[0]).toFixed(1) + " " + sy(p[1]).toFixed(1)).join(" ");
        s += `<path d="${d}" fill="none" stroke="${ser.color}" stroke-width="${ser.width || 2}"${ser.dash ? ' stroke-dasharray="5 4"' : ""} stroke-linejoin="round" stroke-linecap="round"/>`;
    });
    (o.dots || []).forEach(dt => {
        s += `<circle cx="${sx(dt.x).toFixed(1)}" cy="${sy(dt.y).toFixed(1)}" r="${dt.r || 3.5}" fill="${dt.color}" stroke="#15151e" stroke-width="1"/>`;
    });
    return s + "</svg>";
}
function svgBarChart(o) {
    const w = o.w || 620, h = o.h || 190, padL = 46, padR = 12, padT = 12, padB = 28;
    const iw = w - padL - padR, ih = h - padT - padB, n = o.bars.length;
    let ymin = Math.min(0, ...o.bars.map(b => b.v)), ymax = Math.max(0, ...o.bars.map(b => b.v));
    if (ymin === ymax) ymax = ymin + 1;
    const sy = v => padT + (1 - (v - ymin) / (ymax - ymin)) * ih;
    const gap = iw / n, bw = gap * 0.7;
    const fmtY = o.yfmt || (v => String(v));
    let s = `<svg viewBox="0 0 ${w} ${h}" class="ins-chart" xmlns="http://www.w3.org/2000/svg">`;
    [ymax, 0, ymin].forEach(v => {
        const y = sy(v);
        s += `<line x1="${padL}" y1="${y.toFixed(1)}" x2="${w - padR}" y2="${y.toFixed(1)}" stroke="${v === 0 ? "#55556a" : "#2a2a3a"}" stroke-width="1"/>`;
        s += `<text x="${padL - 6}" y="${(y + 3).toFixed(1)}" fill="#8a8a9a" font-size="10" text-anchor="end">${fmtY(v)}</text>`;
    });
    const y0 = sy(0);
    o.bars.forEach((b, i) => {
        const x = padL + gap * i + (gap - bw) / 2, yv = sy(b.v);
        const top = Math.min(yv, y0), hgt = Math.max(0.5, Math.abs(yv - y0));
        const col = b.v > 0 ? (o.posColor || "#e10600") : (o.negColor || "#2ecc71");
        s += `<rect x="${x.toFixed(1)}" y="${top.toFixed(1)}" width="${bw.toFixed(1)}" height="${hgt.toFixed(1)}" fill="${col}" rx="1.5"/>`;
        if (b.label != null && i % (o.labelEvery || 3) === 0)
            s += `<text x="${(x + bw / 2).toFixed(1)}" y="${h - 8}" fill="#8a8a9a" font-size="9" text-anchor="middle">${b.label}</text>`;
    });
    return s + "</svg>";
}

// ----------------------- histórico / insights -----------------------------
async function renderInsights(uid) {
    const box = $("#ins-report");
    if (!uid) { box.innerHTML = ""; box.appendChild(el("div", { class: "empty" }, "Sem corrida selecionada.")); return; }
    box.innerHTML = "";
    box.appendChild(el("div", { class: "empty" }, "Calculando relatório…"));
    let r;
    try { r = await getJSON(`/api/session/${uid}/insights`); }
    catch (e) { box.innerHTML = ""; box.appendChild(el("div", { class: "empty" }, "Erro ao carregar.")); return; }
    box.innerHTML = "";
    if (r.error) { box.appendChild(el("div", { class: "empty" }, r.error)); return; }

    // cabeçalho
    box.appendChild(el("div", { class: "ins-head" },
        el("div", {},
            el("h3", {}, `${r.track_name || "—"} · ${r.session_type_name || ""}`),
            el("span", { class: "ins-sub" }, `${fmtDate(r.started_at)} · ${r.num_laps} voltas · melhor ${fmtLap(r.best_lap_ms)}`))));

    // destaques (o "resumo do engenheiro")
    if (r.highlights && r.highlights.length) {
        const ul = el("div", { class: "ins-highlights" });
        for (const h of r.highlights) ul.appendChild(el("div", { class: "ins-hl" }, el("span", { class: "ins-dot" }), h));
        box.appendChild(ul);
    }

    // métricas de estilo
    const s = r.style;
    if (s) {
        const grid = el("div", { class: "ins-cards" });
        const card = (k, v, sub) => el("div", { class: "ins-card" },
            el("div", { class: "ins-k" }, k),
            el("div", { class: "ins-v" }, v),
            sub ? el("div", { class: "ins-sub2" }, sub) : null);
        grid.appendChild(card("Acelerador 100%", s.full_throttle_pct + "%"));
        grid.appendChild(card("Freando", s.braking_pct + "%"));
        grid.appendChild(card("Coasting", s.coasting_pct + "%", "tempo livre"));
        grid.appendChild(card("Trail braking", s.trail_braking_pct + "%"));
        grid.appendChild(card("Vel. máxima", s.top_speed + " km/h"));
        grid.appendChild(card("DRS", s.drs_pct + "%", "da volta"));
        box.appendChild(grid);
    }

    // tendências
    const t = r.trends || {};
    const trBits = [];
    if (t.degradation_ms_per_lap != null) trBits.push(`Degradação: ${(t.degradation_ms_per_lap / 1000).toFixed(2)}s/volta`);
    if (t.consistency_ms != null) trBits.push(`Consistência: ±${(t.consistency_ms / 1000).toFixed(2)}s`);
    if (t.clean_laps != null) trBits.push(`${t.clean_laps} voltas limpas`);
    if (trBits.length) box.appendChild(el("p", { class: "hint" }, trBits.join("  ·  ")));

    // térmico do pneu / freio / ERS
    const th = r.thermal;
    if (th) {
        box.appendChild(el("h4", { class: "ins-h4" }, "Térmico e desgaste"));
        const grid = el("div", { class: "ins-cards" });
        const card = (k, v, sub, warn) => el("div", { class: "ins-card" + (warn ? " ins-warn" : "") },
            el("div", { class: "ins-k" }, k),
            el("div", { class: "ins-v" }, v),
            sub ? el("div", { class: "ins-sub2" }, sub) : null);
        if (th.hottest_temp != null)
            grid.appendChild(card("Pneu mais quente", th.hottest_temp + "°", th.hottest_wheel, th.hottest_temp >= 110));
        if (th.brake_temp_max != null)
            grid.appendChild(card("Pico de freio", th.brake_temp_max + "°", null, th.brake_temp_max >= 1000));
        if (th.ers_min != null)
            grid.appendChild(card("ERS na volta", th.ers_min + "–" + th.ers_max + "%", "mín–máx"));
        if (r.wear && r.wear.wear_rate_pct_per_lap != null)
            grid.appendChild(card("Desgaste", r.wear.wear_rate_pct_per_lap + "%/volta", "último " + r.wear.wear_last_pct + "%"));
        box.appendChild(grid);
    }

    // saídas de curva lentas
    if (r.slow_exits && r.slow_exits.length) {
        box.appendChild(el("h4", { class: "ins-h4" }, `Análise por curva (volta de referência: ${r.reference_lap})`));
        const tbl = el("table", { class: "grid" });
        tbl.appendChild(el("tr", {}, el("th", {}, "Curva (m)"), el("th", { class: "mono" }, "Vel. mín."),
            el("th", { class: "mono" }, "Marcha"), el("th", { class: "mono" }, "Até 100% acel.")));
        for (const c of r.slow_exits) {
            const slow = (c.meters_to_full || 0) >= 60;
            tbl.appendChild(el("tr", { class: slow ? "row-warn" : "" },
                el("td", {}, c.distance_m + " m"),
                el("td", { class: "mono" }, c.vmin + " km/h"),
                el("td", { class: "mono" }, c.gear + "ª"),
                el("td", { class: "mono" }, c.meters_to_full != null ? "+" + c.meters_to_full + " m" : "—")));
        }
        box.appendChild(tbl);
    }

    // ====================== ANÁLISE DE CORRIDA (vs. o grid) ======================
    const ra = r.race;
    if (ra) {
        // estratégia / stints
        if (ra.stints && ra.stints.length) {
            box.appendChild(el("h4", { class: "ins-h4" },
                `Estratégia · ${ra.pits} parada${ra.pits === 1 ? "" : "s"}`));
            const tbl = el("table", { class: "grid" });
            tbl.appendChild(el("tr", {}, el("th", {}, "Stint"), el("th", {}, "Pneu"),
                el("th", { class: "mono" }, "Voltas"), el("th", { class: "mono" }, "Melhor"),
                el("th", { class: "mono" }, "Média")));
            ra.stints.forEach((s, i) => tbl.appendChild(el("tr", {},
                el("td", {}, "#" + (i + 1)), el("td", {}, s.compound),
                el("td", { class: "mono" }, `V${s.lap_from}–${s.lap_to} (${s.laps})`),
                el("td", { class: "mono" }, fmtLap(s.best_ms)),
                el("td", { class: "mono" }, fmtLap(s.avg_ms)))));
            box.appendChild(tbl);
        }
        // ritmo vs. o grid (gap por setor)
        const g = ra.grid || {};
        if (g.sector_gap) {
            box.appendChild(el("h4", { class: "ins-h4" }, "Ritmo vs. o grid"));
            const grid = el("div", { class: "ins-cards" });
            for (let i = 0; i < 3; i++) {
                const gp = g.sector_gap[i];
                grid.appendChild(el("div", { class: "ins-card" + (g.worst_sector === i + 1 ? " ins-warn" : "") },
                    el("div", { class: "ins-k" }, "Setor " + (i + 1)),
                    el("div", { class: "ins-v" }, gp == null ? "—" : (gp >= 0 ? "+" : "") + (gp / 1000).toFixed(2) + "s"),
                    el("div", { class: "ins-sub2" }, "vs. melhor do grid")));
            }
            box.appendChild(grid);
            const line = [];
            if (g.field_best_ms) line.push(`Melhor do grid: ${fmtLap(g.field_best_ms)}`);
            if (r.best_lap_ms) line.push(`sua melhor: ${fmtLap(r.best_lap_ms)}`);
            if (g.gap_to_field_ms != null) line.push(`gap ${(g.gap_to_field_ms / 1000).toFixed(2)}s/volta`);
            if (line.length) box.appendChild(el("p", { class: "hint" }, line.join("  ·  ")));
            if (ra.start_pos && ra.end_pos) {
                const d = ra.start_pos - ra.end_pos;
                const txt = d > 0 ? `ganhou ${d}` : d < 0 ? `perdeu ${-d}` : "manteve a posição";
                box.appendChild(el("p", { class: "hint" },
                    `Posição: largou P${ra.start_pos} → terminou P${ra.end_pos} (${txt})` +
                    (ra.field_size ? ` · grid de ${ra.field_size} carros` : "")));
            }
        }
        // ---- gráficos (SVG puro) ----
        if (ra.lap_series && ra.lap_series.length) {
            const racing = ra.lap_series.filter(p => !p.pit && p.ms <= 120000);
            if (racing.length >= 3) {
                const fp = ra.field_pace || [];
                const ys = racing.map(p => p.ms).concat(fp.map(f => f.median));
                const ymin = Math.min(...ys), ymax = Math.max(...ys);
                const xs = racing.map(p => p.lap).concat(fp.map(f => f.lap));
                const xmin = Math.min(...xs), xmax = Math.max(...xs);
                box.appendChild(el("h4", { class: "ins-h4" }, "Ritmo volta a volta (você vs. o grid)"));
                const wrap = el("div", { class: "ins-chartwrap" });
                wrap.innerHTML = svgLineChart({
                    xmin, xmax, ymin, ymax,
                    yticks: chartTicks(ymin, ymax, 5).map(v => ({ v, label: fmtLap(v) })),
                    xticks: chartXTicks(xmin, xmax, 6),
                    series: [
                        { pts: fp.map(f => [f.lap, f.median]), color: "#8a8a9c", dash: true, width: 1.6 },
                        { pts: racing.map(p => [p.lap, p.ms]), color: "#e10600", width: 2.2 },
                    ],
                    dots: racing.filter(p => p.incident).map(p => ({ x: p.lap, y: p.ms, color: "#ffcc00", r: 3.6 })),
                });
                box.appendChild(wrap);
                box.appendChild(el("div", { class: "ins-legend" },
                    el("span", { class: "lg lg-you" }, "Você"),
                    el("span", { class: "lg lg-grid" }, "Grid (mediana)"),
                    el("span", { class: "lg lg-inc" }, "Incidente")));
            }
            const pos = ra.lap_series.filter(p => p.pos);
            if (pos.length >= 3) {
                const py = pos.map(p => p.pos);
                const ymin = Math.max(1, Math.min(...py) - 1), ymax = Math.max(...py) + 1;
                const step = Math.max(1, Math.round((ymax - ymin) / 5));
                const yt = [];
                for (let v = Math.ceil(ymin); v <= Math.floor(ymax); v += step) yt.push({ v, label: "P" + v });
                box.appendChild(el("h4", { class: "ins-h4" }, "Evolução de posição"));
                const wrap2 = el("div", { class: "ins-chartwrap" });
                wrap2.innerHTML = svgLineChart({
                    xmin: pos[0].lap, xmax: pos[pos.length - 1].lap, ymin, ymax, invertY: true,
                    yticks: yt, xticks: chartXTicks(pos[0].lap, pos[pos.length - 1].lap, 6),
                    series: [{ pts: pos.map(p => [p.lap, p.pos]), color: "#2ee6c8", width: 2.2 }],
                });
                box.appendChild(wrap2);
            }
        }
        // onde perdeu tempo (voltas-incidente)
        if (ra.incidents && ra.incidents.length) {
            box.appendChild(el("h4", { class: "ins-h4" },
                `Onde perdeu tempo · ~${Math.round(ra.incidents_lost_ms / 1000)}s no total`));
            const tbl = el("table", { class: "grid" });
            tbl.appendChild(el("tr", {}, el("th", {}, "Volta"),
                el("th", { class: "mono" }, "Tempo"), el("th", { class: "mono" }, "Perda")));
            for (const i of ra.incidents) {
                tbl.appendChild(el("tr", { class: i.lost_ms >= 8000 ? "row-warn" : "" },
                    el("td", {}, "V" + i.lap),
                    el("td", { class: "mono" }, fmtLap(i.lap_time_ms)),
                    el("td", { class: "mono" }, "+" + (i.lost_ms / 1000).toFixed(1) + "s")));
            }
            box.appendChild(tbl);
        }
        // prós e contras das decisões (estratégia, box, degradação)
        if (ra.decisions && ra.decisions.length) {
            box.appendChild(el("h4", { class: "ins-h4" }, "Prós e contras das decisões"));
            for (const d of ra.decisions) {
                const card = el("div", { class: "ins-decision" },
                    el("div", { class: "ins-dec-title" }, d.title));
                if (d.pro) card.appendChild(el("div", { class: "ins-dec-line pro" }, "✔ " + d.pro));
                if (d.con) card.appendChild(el("div", { class: "ins-dec-line con" }, "✘ " + d.con));
                if (d.verdict) card.appendChild(el("div", { class: "ins-dec-line verdict" }, "➜ " + d.verdict));
                box.appendChild(card);
            }
        }
        // ====== ANÁLISE FINA (cruza os 22 carros + quali + telemetria) ======
        const deep = ra.deep || {};
        // micro-setores: onde perde tempo vs o mais rápido
        const ms = deep.microsectors;
        if (ms && ms.segments && ms.segments.length) {
            box.appendChild(el("h4", { class: "ins-h4" },
                `Micro-setores: onde você perde tempo (vs. ${ms.ref_name || "o mais rápido"})`));
            const wrap = el("div", { class: "ins-chartwrap" });
            wrap.innerHTML = svgBarChart({
                bars: ms.segments.map(s => ({ v: s.delta_ms / 1000, label: s.from_m })),
                yfmt: v => (v > 0 ? "+" : "") + v.toFixed(1) + "s",
                posColor: "#e10600", negColor: "#2ecc71", labelEvery: 3,
            });
            box.appendChild(wrap);
            box.appendChild(el("p", { class: "hint" },
                "Barras vermelhas = você perde tempo nesse trecho (metros). Piores: " +
                ms.worst.map(w => `${w.from_m}–${w.to_m}m (${(w.delta_ms / 1000).toFixed(2)}s)`).join(" · ")));
        }
        // velocidade mínima por curva vs o grid
        const cs = deep.corner_speed;
        if (cs && cs.worst && cs.worst.length) {
            box.appendChild(el("h4", { class: "ins-h4" }, "Velocidade nas curvas (vs. o grid)"));
            const tbl = el("table", { class: "grid" });
            tbl.appendChild(el("tr", {}, el("th", {}, "Curva (m)"),
                el("th", { class: "mono" }, "Você"), el("th", { class: "mono" }, "Melhor do grid"),
                el("th", { class: "mono" }, "Diferença")));
            for (const c of cs.worst) {
                tbl.appendChild(el("tr", { class: c.delta >= 15 ? "row-warn" : "" },
                    el("td", {}, c.dist_m + " m"),
                    el("td", { class: "mono" }, c.you + " km/h"),
                    el("td", { class: "mono" }, c.field_best + " km/h"),
                    el("td", { class: "mono" }, "+" + c.delta + " km/h")));
            }
            box.appendChild(tbl);
        }
        // cards: quali, balanço, consistência por setor
        const cards = [];
        if (deep.quali) cards.push(["Corrida vs. seu quali",
            "+" + (deep.quali.gap_ms / 1000).toFixed(2) + "s", "melhor de corrida × quali"]);
        if (deep.balance) cards.push(["Balanço do carro",
            deep.balance.diff > 0 ? "Subesterço" : deep.balance.diff < 0 ? "Sobre-esterço" : "Equilibrado",
            `diant. ${deep.balance.front}° × tras. ${deep.balance.rear}°`]);
        if (deep.sector_consistency) {
            const sc = deep.sector_consistency;
            cards.push(["Setor mais inconsistente", "S" + sc.worst,
                "±" + (sc.per_sector[sc.worst] / 1000).toFixed(2) + "s entre voltas"]);
        }
        if (cards.length) {
            box.appendChild(el("h4", { class: "ins-h4" }, "Diagnóstico fino"));
            const grid = el("div", { class: "ins-cards" });
            for (const [k, v, sub] of cards)
                grid.appendChild(el("div", { class: "ins-card" },
                    el("div", { class: "ins-k" }, k), el("div", { class: "ins-v" }, v),
                    el("div", { class: "ins-sub2" }, sub)));
            box.appendChild(grid);
        }
    }

    // plano para a próxima corrida (insights acionáveis)
    if (r.pro_insights && r.pro_insights.length) {
        box.appendChild(el("h4", { class: "ins-h4" }, "Plano para a próxima corrida (mesma pista)"));
        const ul = el("div", { class: "ins-highlights" });
        for (const h of r.pro_insights)
            ul.appendChild(el("div", { class: "ins-hl" }, el("span", { class: "ins-dot" }), h));
        box.appendChild(ul);
    }
}

// ------------------------------ sessões -----------------------------------
async function renderSessions() {
    const box = $("#sessions-table");
    const data = await getJSON("/api/sessions");
    if (!data.length) {
        box.innerHTML = "";
        box.appendChild(el("div", { class: "empty" }, "Nenhuma sessão registrada ainda. Entre numa sessão no jogo."));
        return;
    }
    const tbl = el("table", { class: "grid" });
    tbl.appendChild(el("tr", {}, el("th", {}, "Data"), el("th", {}, "Pista"),
        el("th", {}, "Sessão"), el("th", { class: "mono" }, "Melhor volta"),
        el("th", { class: "mono" }, "Voltas"), el("th", {}, "Clima")));
    for (const s of data) {
        tbl.appendChild(el("tr", {},
            el("td", {}, fmtDate(s.started_at)),
            el("td", {}, s.track_name),
            el("td", {}, s.session_type_name),
            el("td", { class: "mono" }, fmtLap(s.best_lap_ms)),
            el("td", { class: "mono" }, String(s.num_laps || 0)),
            el("td", {}, s.weather != null ? weatherName(s.weather) : "—")));
    }
    box.innerHTML = "";
    box.appendChild(tbl);
}
const WEATHER_NAMES = ["Céu limpo", "Pouca nuvem", "Encoberto", "Chuva fraca", "Chuva forte", "Tempestade"];
const weatherName = (w) => WEATHER_NAMES[w] || "—";

// ------------------------------ início ------------------------------------
setInterval(tickLive, 250);
tickLive();
window.addEventListener("resize", () => onTabShown(activeTab));
