{% extends "base.html" %}
{% block title %}JA4 SOC — Browser Signatures{% endblock %}
{% block page_title %}
    Browser Signature Detection
    <span class="relative inline-block ml-1"><button onclick="docToggle(this)" class="doc-btn">ⓘ</button><div class="doc-panel">
        <h4>Détection passive des navigateurs (§4)</h4>
        <p>Analyse croisée du fingerprint HTTP/2 (SETTINGS, WINDOW_UPDATE, pseudo-headers)
        et du JA4 TLS pour identifier les vrais navigateurs et détecter les imposteurs.</p>
        <p><strong>TLS↔H2 mismatch :</strong> sessions où le JA4 identifie une famille
        (ex: Chromium) mais les SETTINGS H2 appartiennent à une autre (ex: Firefox).
        Signal fort d'un outil qui émule un TLS de navigateur sans répliquer le H2.</p>
        <p class="doc-source">Source : view_ai_features_1h (dict_browser_h2, h2_window_*)</p>
    </div></span>
{% endblock %}
{% block content %}
<div class="p-4 lg:p-6 space-y-4 max-w-[1920px] mx-auto">

    <!-- ═══ KPI Header ═══ -->
    <div class="flex flex-wrap items-center gap-6 mb-2">
        <div class="text-center px-3">
            <div class="text-2xl font-bold text-white" id="kpi-total">—</div>
            <div class="text-[10px] text-gray-500 uppercase tracking-wider">Sessions (24h)</div>
        </div>
        <div class="text-center px-3 border-l border-gray-700">
            <div class="text-2xl font-bold text-indigo-400" id="kpi-h2">—</div>
            <div class="text-[10px] text-gray-500 uppercase tracking-wider">Avec données H2</div>
        </div>
        <div class="text-center px-3 border-l border-gray-700">
            <div class="text-2xl font-bold text-emerald-400" id="kpi-matched">—</div>
            <div class="text-[10px] text-gray-500 uppercase tracking-wider">Famille H2 connue</div>
        </div>
        <div class="text-center px-3 border-l border-gray-700">
            <div class="text-2xl font-bold text-amber-400" id="kpi-match-rate">—</div>
            <div class="text-[10px] text-gray-500 uppercase tracking-wider">Taux match H2</div>
        </div>
        <div class="text-center px-3 border-l border-gray-700">
            <div class="text-2xl font-bold text-red-400" id="kpi-mismatch">—</div>
            <div class="text-[10px] text-gray-500 uppercase tracking-wider">Mismatch TLS↔H2</div>
        </div>
        <div class="text-center px-3 border-l border-gray-700">
            <div class="text-2xl font-bold text-red-300" id="kpi-mismatch-rate">—</div>
            <div class="text-[10px] text-gray-500 uppercase tracking-wider">% Mismatch / H2</div>
        </div>
    </div>

    <!-- ═══ Doc banner ═══ -->
    <div class="bg-gray-900/50 border border-gray-800 rounded-lg px-4 py-3 text-xs text-gray-400 leading-relaxed">
        <strong class="text-indigo-300">Browser Signature Detection §4</strong> — Le moteur
        <code class="text-indigo-200">browser_matcher</code> score chaque session sur 7 dimensions :
        <strong>H2 SETTINGS</strong> (0.30), <strong>WINDOW_UPDATE</strong> (0.15),
        <strong>pseudo-headers</strong> (0.15), H2 PRIORITY (0.10), HTTP headers (0.15),
        structure TLS (0.10), dictionnaire JA4 (0.05).
        Un <strong class="text-amber-300">mismatch TLS↔H2</strong> est détecté quand le JA4 (couche TLS)
        identifie Chrome mais le WINDOW_UPDATE H2 est celui de Firefox (ou vice-versa) — signature
        d'un outil qui copie le TLS sans répliquer fidèlement le H2.
        En mode <strong>DUAL_MODE</strong> (défaut), les décisions sont journalisées sans modifier le bypass —
        activer <code class="text-green-300">BROWSER_MATCHER_REPLACE=true</code> pour basculer.
    </div>

    <!-- ═══ Row 1 : Familles H2 + WINDOW_UPDATE ═══ -->
    <div class="grid grid-cols-1 lg:grid-cols-2 gap-4">

        <!-- Donut : distribution des familles H2 -->
        <div class="section-card">
            <div class="section-header">
                <span class="section-title">
                    Familles détectées — dict H2
                    <span class="relative inline-block ml-1"><button onclick="docToggle(this)" class="doc-btn">ⓘ</button><div class="doc-panel">
                        <h4>Familles H2 (dict_browser_h2)</h4>
                        <p>Correspondance du fingerprint SETTINGS H2 complet (format Akamai)
                        avec le dictionnaire <code>dict_browser_h2</code>. Une correspondance
                        exacte identifie Chrome, Firefox, Safari ou Edge avec un haut degré
                        de certitude.</p>
                        <p class="doc-source">Source : view_ai_features_1h.h2_dict_family</p>
                    </div></span>
                </span>
            </div>
            <div class="section-body">
                <div id="chart-families" style="height:320px"></div>
            </div>
        </div>

        <!-- Bar : WINDOW_UPDATE signals -->
        <div class="section-card">
            <div class="section-header">
                <span class="section-title">
                    Signal WINDOW_UPDATE H2
                    <span class="relative inline-block ml-1"><button onclick="docToggle(this)" class="doc-btn">ⓘ</button><div class="doc-panel">
                        <h4>Valeur WINDOW_UPDATE par famille</h4>
                        <p>La valeur du frame WINDOW_UPDATE est l'empreinte H2 la plus fiable car
                        chaque navigateur utilise une valeur distincte et constante :</p>
                        <ul class="list-disc ml-4 mt-1">
                            <li>Chrome : 15 663 105</li>
                            <li>Firefox : 12 517 377</li>
                            <li>Safari : 10 485 760</li>
                            <li>curl/httpx : absent (0)</li>
                        </ul>
                        <p class="doc-source">Source : view_ai_features_1h.h2_window_*</p>
                    </div></span>
                </span>
            </div>
            <div class="section-body">
                <div id="chart-wu-signals" style="height:320px"></div>
            </div>
        </div>

    </div>

    <!-- ═══ Row 2 : Mismatch par famille + table suspects ═══ -->
    <div class="grid grid-cols-1 lg:grid-cols-2 gap-4">

        <!-- Bar : mismatch par famille JA4 -->
        <div class="section-card">
            <div class="section-header">
                <span class="section-title">
                    Mismatch TLS↔H2 par famille JA4
                    <span class="relative inline-block ml-1"><button onclick="docToggle(this)" class="doc-btn">ⓘ</button><div class="doc-panel">
                        <h4>Incohérences cross-layer</h4>
                        <p>Proportion de sessions dont la famille JA4 (TLS) contredit la famille H2.
                        Un taux élevé pour "Chromium" indique des outils qui imitent Chrome TLS
                        sans reproduire le comportement H2.</p>
                        <p><strong>Exemples de mismatch :</strong></p>
                        <ul class="list-disc ml-4 mt-1">
                            <li>JA4=Chromium + WU=12517377 → Firefox H2</li>
                            <li>JA4=Firefox + WU=15663105 → Chrome H2</li>
                            <li>JA4=Navigateur + WU absent → outil sans H2</li>
                        </ul>
                        <p class="doc-source">Source : view_ai_features_1h.tls_h2_family_mismatch</p>
                    </div></span>
                </span>
            </div>
            <div class="section-body">
                <div id="chart-mismatch" style="height:320px"></div>
            </div>
        </div>

        <!-- Sessions suspectes avec mismatch -->
        <div class="section-card">
            <div class="section-header">
                <span class="section-title">
                    Sessions suspectes (mismatch confirmé)
                    <span class="relative inline-block ml-1"><button onclick="docToggle(this)" class="doc-btn">ⓘ</button><div class="doc-panel">
                        <h4>Imposteurs potentiels</h4>
                        <p>Sessions dont le JA4 (TLS) et les SETTINGS H2 identifient des familles
                        différentes — signal fort d'un outil qui émule le TLS d'un navigateur
                        mais trahit son origine via le H2.</p>
                        <p><strong>Action :</strong> Investiguer l'IP, vérifier le JA4 dans la page
                        Détections, ou ajouter à la liste de blocage.</p>
                    </div></span>
                </span>
            </div>
            <div class="section-body p-0 overflow-x-auto">
                <table class="w-full text-xs text-left" id="tbl-suspects">
                    <thead class="text-gray-500 border-b border-gray-800">
                        <tr>
                            <th class="px-3 py-2 font-medium">IP</th>
                            <th class="px-3 py-2 font-medium">JA4 famille</th>
                            <th class="px-3 py-2 font-medium">H2 famille</th>
                            <th class="px-3 py-2 font-medium">SETTINGS</th>
                            <th class="px-3 py-2 font-medium">WU value</th>
                            <th class="px-3 py-2 font-medium">Pseudo-order</th>
                            <th class="px-3 py-2 font-medium text-right">Hits</th>
                            <th class="px-3 py-2 font-medium text-right">Cohérence</th>
                        </tr>
                    </thead>
                    <tbody id="tbl-suspects-body" class="divide-y divide-gray-800/50">
                        <tr><td colspan="8" class="px-3 py-6 text-center text-gray-600">Chargement…</td></tr>
                    </tbody>
                </table>
            </div>
        </div>

    </div>

    <!-- ═══ Row 3 : Pseudo-header orders ═══ -->
    <div class="section-card">
        <div class="section-header">
            <span class="section-title">
                Distribution des ordres pseudo-headers H2
                <span class="relative inline-block ml-1"><button onclick="docToggle(this)" class="doc-btn">ⓘ</button><div class="doc-panel">
                    <h4>Ordre des pseudo-headers HTTP/2</h4>
                    <p>L'ordre des pseudo-headers (<code>:method</code>, <code>:authority</code>,
                    <code>:scheme</code>, <code>:path</code>) est spécifique à chaque navigateur :</p>
                    <ul class="list-disc ml-4 mt-1">
                        <li><strong>m,a,s,p</strong> — Chrome et Safari</li>
                        <li><strong>m,p,s,a</strong> — Firefox</li>
                    </ul>
                    <p>Un ordre non répertorié indique un outil ou une version rare.</p>
                    <p class="doc-source">Source : view_ai_features_1h (h2_order_chromesafari, h2_order_firefox)</p>
                </div></span>
            </span>
        </div>
        <div class="section-body">
            <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
                <div class="bg-gray-900 rounded-lg px-4 py-3 border border-gray-800">
                    <div class="text-xs text-gray-500 mb-1">Chrome / Safari — <code>m,a,s,p</code></div>
                    <div class="text-2xl font-bold text-indigo-400" id="pseudo-chromesafari">—</div>
                    <div class="text-[10px] text-gray-600 mt-1">sessions</div>
                </div>
                <div class="bg-gray-900 rounded-lg px-4 py-3 border border-gray-800">
                    <div class="text-xs text-gray-500 mb-1">Firefox — <code>m,p,s,a</code></div>
                    <div class="text-2xl font-bold text-orange-400" id="pseudo-firefox">—</div>
                    <div class="text-[10px] text-gray-600 mt-1">sessions</div>
                </div>
                <div class="bg-gray-900 rounded-lg px-4 py-3 border border-gray-800">
                    <div class="text-xs text-gray-500 mb-1">H2 présent — ordre non répertorié</div>
                    <div class="text-2xl font-bold text-gray-400" id="pseudo-other">—</div>
                    <div class="text-[10px] text-gray-600 mt-1">sessions (outil probable)</div>
                </div>
                <div class="bg-gray-900 rounded-lg px-4 py-3 border border-gray-800">
                    <div class="text-xs text-gray-500 mb-1">H2 PRIORITY frames présents</div>
                    <div class="text-2xl font-bold text-yellow-400" id="pseudo-priority">—</div>
                    <div class="text-[10px] text-gray-600 mt-1">sessions (Firefox ancien)</div>
                </div>
            </div>
        </div>
    </div>

    <!-- ═══ Row 4 : Base de signatures H2 ═══ -->
    <div class="section-card" id="section-sig-h2">
        <div class="section-header flex items-center justify-between">
            <span class="section-title">
                Base de signatures H2
                <span class="relative inline-block ml-1"><button onclick="docToggle(this)" class="doc-btn">ⓘ</button><div class="doc-panel">
                    <h4>Table <code>browser_h2_signatures</code></h4>
                    <p>Source des fingerprints HTTP/2 (format Akamai) utilisés par
                    <code>dict_browser_h2</code>. Le dictionnaire est rechargé automatiquement
                    après chaque ajout ou suppression.</p>
                    <p>Format : <code>SETTINGS|WINDOW_UPDATE|PRIORITY|PSEUDO_ORDER</code><br>
                    Exemple Chrome : <code>1:65536,2:0,4:6291456,6:262144|15663105|0|m,a,s,p</code></p>
                    <p class="doc-source">Source : ja4_processing.browser_h2_signatures (migration 06)</p>
                </div></span>
            </span>
            <button onclick="toggleAddForm()" class="text-xs bg-indigo-700 hover:bg-indigo-600 text-white px-3 py-1 rounded font-medium transition-colors">+ Ajouter</button>
        </div>

        <!-- Formulaire d'ajout (masqué par défaut) -->
        <div id="form-add-sig" class="hidden px-4 py-3 bg-gray-950 border-b border-gray-800">
            <div class="grid grid-cols-1 md:grid-cols-4 gap-3 text-xs">
                <div class="md:col-span-2">
                    <label class="text-gray-400 block mb-1">H2 Fingerprint (format Akamai)</label>
                    <input id="inp-fp" type="text" placeholder="1:65536,2:0,4:6291456,6:262144|15663105|0|m,a,s,p"
                        class="w-full bg-gray-900 border border-gray-700 rounded px-2 py-1.5 text-gray-200 font-mono text-[11px] focus:outline-none focus:border-indigo-500">
                </div>
                <div>
                    <label class="text-gray-400 block mb-1">Famille</label>
                    <select id="inp-family" class="w-full bg-gray-900 border border-gray-700 rounded px-2 py-1.5 text-gray-200 focus:outline-none focus:border-indigo-500">
                        <option value="Chrome">Chrome</option>
                        <option value="Firefox">Firefox</option>
                        <option value="Safari">Safari</option>
                        <option value="Edge">Edge</option>
                        <option value="Other">Other</option>
                    </select>
                </div>
                <div>
                    <label class="text-gray-400 block mb-1">Confidence (0–1)</label>
                    <input id="inp-conf" type="number" step="0.05" min="0" max="1" value="1.0"
                        class="w-full bg-gray-900 border border-gray-700 rounded px-2 py-1.5 text-gray-200 focus:outline-none focus:border-indigo-500">
                </div>
                <div class="md:col-span-3">
                    <label class="text-gray-400 block mb-1">Notes</label>
                    <input id="inp-notes" type="text" placeholder="ex: Chrome 143 beta"
                        class="w-full bg-gray-900 border border-gray-700 rounded px-2 py-1.5 text-gray-200 focus:outline-none focus:border-indigo-500">
                </div>
                <div class="flex items-end">
                    <button onclick="submitAdd()" class="w-full bg-emerald-700 hover:bg-emerald-600 text-white rounded px-3 py-1.5 text-xs font-medium transition-colors">Enregistrer</button>
                </div>
            </div>
            <div id="add-status" class="mt-2 text-xs hidden"></div>
        </div>

        <div class="section-body overflow-x-auto">
            <table class="w-full text-xs text-gray-400">
                <thead class="border-b border-gray-800 text-left">
                    <tr>
                        <th class="px-3 py-2 font-medium">Fingerprint H2 (format Akamai)</th>
                        <th class="px-3 py-2 font-medium w-24">Famille</th>
                        <th class="px-3 py-2 font-medium w-24 text-center">Confidence</th>
                        <th class="px-3 py-2 font-medium">Notes</th>
                        <th class="px-3 py-2 font-medium w-16 text-center">Action</th>
                    </tr>
                </thead>
                <tbody id="tbl-sigs-body" class="divide-y divide-gray-800/50">
                    <tr><td colspan="5" class="px-3 py-6 text-center text-gray-600">Chargement…</td></tr>
                </tbody>
            </table>
        </div>
    </div>

    <!-- ═══ Row 5 : Règles de scoring Python (browser_matcher) ═══ -->
    <div class="section-card">
        <div class="section-header">
            <span class="section-title">
                Règles de scoring — <code>browser_matcher.py</code>
                <span class="relative inline-block ml-1"><button onclick="docToggle(this)" class="doc-btn">ⓘ</button><div class="doc-panel">
                    <h4>Dimensions du scoring navigateur</h4>
                    <p>Le browser_matcher calcule un score 0–1 par famille en agrégeant 7 dimensions.
                    Ces règles sont définies dans <code>bot_detector/browser_signatures.py</code>.</p>
                    <p class="doc-source">Modification : éditer le fichier Python et redéployer bot-detector</p>
                </div></span>
            </span>
        </div>
        <div class="section-body">
            <div class="overflow-x-auto">
                <table class="w-full text-xs text-gray-400">
                    <thead class="border-b border-gray-800 text-left">
                        <tr>
                            <th class="px-3 py-2 font-medium">Dimension</th>
                            <th class="px-3 py-2 font-medium text-right w-16">Poids</th>
                            <th class="px-3 py-2 font-medium">Chrome</th>
                            <th class="px-3 py-2 font-medium">Firefox</th>
                            <th class="px-3 py-2 font-medium">Safari</th>
                        </tr>
                    </thead>
                    <tbody class="divide-y divide-gray-800/50 font-mono text-[11px]">
                        <tr>
                            <td class="px-3 py-2 text-gray-300 font-sans text-xs">H2 SETTINGS exact</td>
                            <td class="px-3 py-2 text-right text-amber-400 font-sans">0.30</td>
                            <td class="px-3 py-2">1:65536,2:0,4:6291456,6:262144</td>
                            <td class="px-3 py-2">1:65536,4:131072,5:16384</td>
                            <td class="px-3 py-2">1:4096,3:100,4:65535</td>
                        </tr>
                        <tr>
                            <td class="px-3 py-2 text-gray-300 font-sans text-xs">H2 WINDOW_UPDATE</td>
                            <td class="px-3 py-2 text-right text-amber-400 font-sans">0.15</td>
                            <td class="px-3 py-2 text-indigo-400">15 663 105</td>
                            <td class="px-3 py-2 text-orange-400">12 517 377</td>
                            <td class="px-3 py-2 text-cyan-400">10 485 760</td>
                        </tr>
                        <tr>
                            <td class="px-3 py-2 text-gray-300 font-sans text-xs">Pseudo-header order</td>
                            <td class="px-3 py-2 text-right text-amber-400 font-sans">0.15</td>
                            <td class="px-3 py-2">m,a,s,p</td>
                            <td class="px-3 py-2">m,p,s,a</td>
                            <td class="px-3 py-2">m,a,s,p</td>
                        </tr>
                        <tr>
                            <td class="px-3 py-2 text-gray-300 font-sans text-xs">HTTP headers cohérence</td>
                            <td class="px-3 py-2 text-right text-amber-400 font-sans">0.15</td>
                            <td class="px-3 py-2 text-[10px]">Sec-CH-UA ✓ · Sec-Fetch ✓</td>
                            <td class="px-3 py-2 text-[10px]">Sec-CH-UA ✗ · Sec-Fetch ✓</td>
                            <td class="px-3 py-2 text-[10px]">Sec-CH-UA ✗ · Sec-Fetch ✗</td>
                        </tr>
                        <tr>
                            <td class="px-3 py-2 text-gray-300 font-sans text-xs">H2 PRIORITY frames</td>
                            <td class="px-3 py-2 text-right text-amber-400 font-sans">0.10</td>
                            <td class="px-3 py-2 text-gray-500">absent</td>
                            <td class="px-3 py-2 text-gray-500">absent</td>
                            <td class="px-3 py-2 text-gray-500">absent</td>
                        </tr>
                        <tr>
                            <td class="px-3 py-2 text-gray-300 font-sans text-xs">TLS structure (JA4 famille)</td>
                            <td class="px-3 py-2 text-right text-amber-400 font-sans">0.10</td>
                            <td class="px-3 py-2 text-[10px]">Chromium · Chrome · Edge + GREASE</td>
                            <td class="px-3 py-2 text-[10px]">Firefox · pas de GREASE</td>
                            <td class="px-3 py-2 text-[10px]">Safari · pas de GREASE</td>
                        </tr>
                        <tr>
                            <td class="px-3 py-2 text-gray-300 font-sans text-xs">JA4 dict lookup</td>
                            <td class="px-3 py-2 text-right text-amber-400 font-sans">0.05</td>
                            <td colspan="3" class="px-3 py-2 text-gray-500">dict_browser_ja4 — correspondance fingerprint TLS exact</td>
                        </tr>
                        <tr class="bg-gray-900/50">
                            <td class="px-3 py-2 text-gray-300 font-sans font-semibold">Seuil de bypass ML</td>
                            <td class="px-3 py-2 text-right text-emerald-400 font-sans font-semibold">—</td>
                            <td class="px-3 py-2 text-emerald-400 font-sans">≥ 0.72</td>
                            <td class="px-3 py-2 text-emerald-400 font-sans">≥ 0.68</td>
                            <td class="px-3 py-2 text-emerald-400 font-sans">≥ 0.68</td>
                        </tr>
                    </tbody>
                </table>
            </div>
            <p class="mt-3 text-[10px] text-gray-600">Mode actuel : <strong class="text-gray-400">DUAL_MODE</strong> — le matcher journalise les décisions sans modifier le scoring ML.
            Activer le bypass : variable d'environnement <code>BROWSER_MATCHER_REPLACE=true</code> dans bot-detector.</p>
        </div>
    </div>

</div>

<script>
// ─── Gestion des signatures H2 ───────────────────────────────────────────
const FAM_COLORS = {
    Chrome: 'text-indigo-400', Firefox: 'text-orange-400',
    Safari: 'text-cyan-400', Edge: 'text-purple-400', Other: 'text-gray-400',
};

function toggleAddForm() {
    const el = document.getElementById('form-add-sig');
    el.classList.toggle('hidden');
}

async function loadSignatureEntries() {
    try {
        const r = await fetch('/api/browser-signatures/entries');
        if (!r.ok) throw new Error(r.statusText);
        const data = await r.json();
        const isReadonly = data.readonly === true;
        // Masquer le bouton Ajouter si source = dict CSV (table pas encore créée)
        if (isReadonly) {
            document.querySelector('#section-sig-h2 .section-header button')?.setAttribute('disabled', 'true');
            document.querySelector('#section-sig-h2 .section-header button')?.classList.add('opacity-40', 'cursor-not-allowed');
        }
        renderSigTable(data.entries || [], isReadonly);
    } catch (e) {
        document.getElementById('tbl-sigs-body').innerHTML =
            `<tr><td colspan="5" class="px-3 py-4 text-center text-gray-600">Indisponible</td></tr>`;
    }
}

function renderSigTable(rows, readonly = false) {
    const tbody = document.getElementById('tbl-sigs-body');
    if (!rows.length) {
        tbody.innerHTML = '<tr><td colspan="5" class="px-3 py-6 text-center text-gray-600">Aucun fingerprint enregistré</td></tr>';
        return;
    }
    tbody.innerHTML = rows.map(r => {
        const fc = FAM_COLORS[r.browser_family] || 'text-gray-400';
        const conf = typeof r.confidence === 'number' ? r.confidence.toFixed(2) : (r.confidence || '—');
        const confColor = (r.confidence || 0) >= 0.95 ? 'text-emerald-400' : (r.confidence || 0) >= 0.8 ? 'text-amber-400' : 'text-red-400';
        const fpEsc = encodeURIComponent(r.h2_fingerprint);
        const actionCell = readonly
            ? '<td class="px-3 py-2 text-center text-gray-700 text-[10px]">lecture seule</td>'
            : `<td class="px-3 py-2 text-center">
                <button onclick="deleteSig('${fpEsc}')" class="text-red-500 hover:text-red-400 text-[11px] transition-colors" title="Supprimer">✕</button>
              </td>`;
        return `<tr class="hover:bg-gray-800/20 transition-colors">
            <td class="px-3 py-2 font-mono text-[11px] text-gray-300 break-all">${escHtml(r.h2_fingerprint)}</td>
            <td class="px-3 py-2 font-semibold ${fc}">${r.browser_family}</td>
            <td class="px-3 py-2 text-center font-semibold ${confColor}">${conf}</td>
            <td class="px-3 py-2 text-gray-500">${escHtml(r.notes || '')}</td>
            ${actionCell}
        </tr>`;
    }).join('');
}

async function submitAdd() {
    const fp    = document.getElementById('inp-fp').value.trim();
    const fam   = document.getElementById('inp-family').value;
    const conf  = parseFloat(document.getElementById('inp-conf').value);
    const notes = document.getElementById('inp-notes').value.trim();
    const status = document.getElementById('add-status');

    if (!fp) { showStatus(status, 'Le fingerprint est requis', 'error'); return; }

    try {
        const r = await fetch('/api/browser-signatures/entries', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ h2_fingerprint: fp, browser_family: fam, confidence: conf, notes }),
        });
        const data = await r.json();
        if (!r.ok) { showStatus(status, data.detail || 'Erreur', 'error'); return; }
        showStatus(status, '✓ Enregistré — dictionnaire rechargé', 'ok');
        document.getElementById('inp-fp').value = '';
        document.getElementById('inp-notes').value = '';
        setTimeout(() => document.getElementById('form-add-sig').classList.add('hidden'), 1500);
        await loadSignatureEntries();
    } catch (e) {
        showStatus(status, e.message, 'error');
    }
}

async function deleteSig(fpEncoded) {
    const fp = decodeURIComponent(fpEncoded);
    if (!confirm(`Supprimer ce fingerprint ?\n\n${fp}`)) return;
    try {
        const r = await fetch(`/api/browser-signatures/entries?fingerprint=${fpEncoded}`, { method: 'DELETE' });
        if (!r.ok) { alert('Erreur lors de la suppression'); return; }
        await loadSignatureEntries();
    } catch (e) { alert(e.message); }
}

function showStatus(el, msg, type) {
    el.textContent = msg;
    el.className = 'mt-2 text-xs ' + (type === 'ok' ? 'text-emerald-400' : 'text-red-400');
    el.classList.remove('hidden');
}

function escHtml(s) {
    return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
}

// ─── Chargement des données ───────────────────────────────────────────────
async function loadBrowserData() {
    let data;
    try {
        const r = await fetch('/api/browser-signatures');
        if (!r.ok) throw new Error(r.statusText);
        data = await r.json();
    } catch (e) {
        console.error('browsers API error', e);
        return;
    }

    // KPIs
    const s = data.stats || {};
    setText('kpi-total',         fmt(s.total_sessions));
    setText('kpi-h2',            fmt(s.sessions_with_h2));
    setText('kpi-matched',       fmt(s.sessions_matched));
    setText('kpi-match-rate',    (s.match_rate ?? '—') + (s.match_rate != null ? '%' : ''));
    setText('kpi-mismatch',      fmt(s.sessions_mismatch));
    setText('kpi-mismatch-rate', (s.mismatch_rate ?? '—') + (s.mismatch_rate != null ? '%' : ''));

    // Donut — familles H2
    renderFamiliesDonut(data.h2_families || []);

    // Bar — WINDOW_UPDATE signals
    renderWuBar(data.h2_window_signals || []);

    // Bar — mismatch par famille
    renderMismatchBar(data.mismatch_by_family || []);

    // Table suspects
    renderSuspects(data.top_mismatches || []);

    // Pseudo-header counters (déduire depuis mismatch_by_family stats + wu_signals)
    // On recalcule depuis les familles et les sessions_with_h2
    const totalH2 = s.sessions_with_h2 || 0;
    const wuData  = data.h2_window_signals || [];
    const chrome  = (wuData.find(r => r.signal.startsWith('Chrome')) || {}).sessions || 0;
    const firefox = (wuData.find(r => r.signal.startsWith('Firefox')) || {}).sessions || 0;
    const safari  = (wuData.find(r => r.signal.startsWith('Safari')) || {}).sessions || 0;
    const absent  = (wuData.find(r => r.signal.startsWith('Absent')) || {}).sessions || 0;
    const other   = (wuData.find(r => r.signal.startsWith('Autre')) || {}).sessions || 0;
    // h2_order_chromesafari ≈ chrome + safari (partagent m,a,s,p)
    setText('pseudo-chromesafari', fmt(chrome + safari));
    setText('pseudo-firefox',      fmt(firefox));
    setText('pseudo-other',        fmt(other));
    // Pour h2_priority_present on utilise le compteur direct de l'API
    setText('pseudo-priority', fmt(s.sessions_with_priority));
}

// ─── Graphiques ──────────────────────────────────────────────────────────

function renderFamiliesDonut(rows) {
    const chart = echarts.init(document.getElementById('chart-families'), 'dark');
    if (!rows.length) { chart.setOption({ title: { text: 'Aucune donnée H2', left: 'center', top: 'center', textStyle: { color: '#6b7280', fontSize: 13 } } }); return; }
    const COLORS = { Chrome: '#6366f1', Firefox: '#f97316', Safari: '#22d3ee', Edge: '#8b5cf6', '': '#374151' };
    const data = rows.map(r => ({
        name: r.family || 'Inconnu',
        value: r.sessions,
        itemStyle: { color: COLORS[r.family] || '#64748b' }
    }));
    chart.setOption({
        backgroundColor: 'transparent',
        tooltip: { trigger: 'item', formatter: '{b}: {c} sessions ({d}%)' },
        legend: { orient: 'vertical', right: 10, top: 'center', textStyle: { color: '#9ca3af', fontSize: 11 } },
        series: [{
            type: 'pie', radius: ['45%', '72%'], center: ['40%', '50%'],
            avoidLabelOverlap: true,
            label: { show: false },
            emphasis: { label: { show: true, fontSize: 13, fontWeight: 'bold' } },
            data
        }]
    });
    window.addEventListener('resize', () => chart.resize());
}

function renderWuBar(rows) {
    const chart = echarts.init(document.getElementById('chart-wu-signals'), 'dark');
    if (!rows.length) { chart.setOption({ title: { text: 'Aucune donnée H2', left: 'center', top: 'center', textStyle: { color: '#6b7280', fontSize: 13 } } }); return; }
    const COLORS = ['#6366f1', '#f97316', '#22d3ee', '#ef4444', '#6b7280'];
    chart.setOption({
        backgroundColor: 'transparent',
        tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
        grid: { left: '2%', right: '4%', bottom: '3%', top: '4%', containLabel: true },
        xAxis: { type: 'value', axisLine: { lineStyle: { color: '#374151' } }, axisLabel: { color: '#6b7280', fontSize: 10 } },
        yAxis: { type: 'category', data: rows.map(r => r.signal), axisLabel: { color: '#9ca3af', fontSize: 10 }, axisLine: { lineStyle: { color: '#374151' } } },
        series: [{
            type: 'bar', barMaxWidth: 32,
            data: rows.map((r, i) => ({ value: r.sessions, itemStyle: { color: COLORS[i % COLORS.length] } })),
            label: { show: true, position: 'right', color: '#9ca3af', fontSize: 10, formatter: v => fmt(v.value) }
        }]
    });
    window.addEventListener('resize', () => chart.resize());
}

function renderMismatchBar(rows) {
    const chart = echarts.init(document.getElementById('chart-mismatch'), 'dark');
    if (!rows.length) { chart.setOption({ title: { text: 'Aucune donnée de mismatch', left: 'center', top: 'center', textStyle: { color: '#6b7280', fontSize: 13 } } }); return; }
    chart.setOption({
        backgroundColor: 'transparent',
        tooltip: {
            trigger: 'axis', axisPointer: { type: 'shadow' },
            formatter: params => {
                const r = rows[params[0].dataIndex];
                return `${r.ja4_family}<br/>Mismatch : ${fmt(r.mismatches)} / ${fmt(r.total)} (${r.mismatch_pct}%)`;
            }
        },
        grid: { left: '2%', right: '4%', bottom: '3%', top: '4%', containLabel: true },
        xAxis: { type: 'category', data: rows.map(r => r.ja4_family), axisLabel: { color: '#9ca3af', fontSize: 10 }, axisLine: { lineStyle: { color: '#374151' } } },
        yAxis: [
            { type: 'value', name: 'Sessions', nameTextStyle: { color: '#6b7280', fontSize: 10 }, axisLabel: { color: '#6b7280', fontSize: 10 }, axisLine: { lineStyle: { color: '#374151' } } },
            { type: 'value', name: '% mismatch', max: 100, nameTextStyle: { color: '#ef4444', fontSize: 10 }, axisLabel: { color: '#ef4444', fontSize: 10, formatter: '{value}%' }, axisLine: { lineStyle: { color: '#374151' } } }
        ],
        series: [
            { name: 'Total', type: 'bar', barMaxWidth: 40, itemStyle: { color: '#374151' }, data: rows.map(r => r.total) },
            { name: 'Mismatch', type: 'bar', barMaxWidth: 40, itemStyle: { color: '#ef4444' }, data: rows.map(r => r.mismatches) },
            { name: '% mismatch', type: 'line', yAxisIndex: 1, symbol: 'circle', symbolSize: 6, lineStyle: { color: '#fbbf24' }, itemStyle: { color: '#fbbf24' }, data: rows.map(r => r.mismatch_pct) }
        ],
        legend: { textStyle: { color: '#9ca3af', fontSize: 10 }, top: 0 }
    });
    window.addEventListener('resize', () => chart.resize());
}

function renderSuspects(rows) {
    const tbody = document.getElementById('tbl-suspects-body');
    if (!rows.length) {
        tbody.innerHTML = '<tr><td colspan="8" class="px-3 py-6 text-center text-gray-600">Aucun mismatch détecté dans les 24 dernières heures</td></tr>';
        return;
    }
    const WU_LABELS = {
        15663105: '<span class="text-indigo-400">Chrome</span>',
        12517377: '<span class="text-orange-400">Firefox</span>',
        10485760: '<span class="text-cyan-400">Safari</span>',
        1073676289: '<span class="text-gray-400">go net/http</span>',
        0: '<span class="text-red-400">absent</span>',
    };
    // Noms courts des paramètres SETTINGS (ID → label)
    const SETTINGS_NAMES = {
        h2_header_table_size: '1',
        h2_enable_push: '2',
        h2_max_concurrent_streams: '3',
        h2_initial_window_size: '4',
        h2_max_frame_size: '5',
        h2_max_header_list_size: '6',
        h2_enable_connect_protocol: '8',
    };
    function fmtSettings(r) {
        const pairs = Object.entries(SETTINGS_NAMES)
            .filter(([col]) => r[col] != null && r[col] !== -1)
            .map(([col, id]) => `${id}:${r[col]}`);
        if (!pairs.length) return '<span class="text-gray-600">—</span>';
        return `<span class="font-mono text-[10px] text-emerald-300">${escHtml(pairs.join(', '))}</span>`;
    }
    tbody.innerHTML = rows.map(r => {
        const wuLabel = WU_LABELS[r.wu_value] || `<span class="text-gray-400">${r.wu_value}</span>`;
        const coh = r.coherence != null ? r.coherence.toFixed(2) : '—';
        const cohColor = r.coherence < 0.4 ? 'text-red-400' : r.coherence < 0.6 ? 'text-amber-400' : 'text-emerald-400';
        const h2fam = r.h2_dict_family || '<span class="text-gray-600">inconnu</span>';
        return `<tr class="hover:bg-gray-800/30 transition-colors">
            <td class="px-3 py-2 font-mono text-indigo-300"><a href="/ip/${r.ip}" class="hover:underline">${r.ip}</a></td>
            <td class="px-3 py-2 text-gray-300">${r.ja4_family || '—'}</td>
            <td class="px-3 py-2 text-amber-300">${h2fam}</td>
            <td class="px-3 py-2">${fmtSettings(r)}</td>
            <td class="px-3 py-2">${wuLabel}</td>
            <td class="px-3 py-2 font-mono text-gray-400">${r.pseudo_order || '—'}</td>
            <td class="px-3 py-2 text-right text-gray-300">${fmt(r.hits)}</td>
            <td class="px-3 py-2 text-right font-semibold ${cohColor}">${coh}</td>
        </tr>`;
    }).join('');
}

// ─── Utils ────────────────────────────────────────────────────────────────
function setText(id, val) { const el = document.getElementById(id); if (el) el.textContent = val ?? '—'; }
function fmt(n) { return n == null ? '—' : Number(n).toLocaleString('fr-FR'); }

loadBrowserData();
loadSignatureEntries();
</script>
{% endblock %}
