/**
 * Demo Oracle — detail d'un SQL_ID.
 *
 * Lit ses donnees dans window.DemoOracleReport.sqlDetail. Les bibliotheques CDN
 * (highlight.js, sql-formatter, Chart.js) sont chargees par la vue.
 *
 * Portage de modules/oracle/assets/js/report-sql-detail.js. Trois fonctions de
 * rendu ne sont pas livrees — objets lies, statistiques d'execution, historique
 * des plans — et leurs onglets sont rendus cote serveur par
 * DemoOracleDemo::page().
 *
 * L'onglet SQL Monitoring, lui, EST servi : ses executions viennent de
 * GV$SESSION, pas de GV$SQL_MONITOR, et ne coutent donc aucune licence.
 *
 * Deux d'entre elles meritent une precision : « statistiques d'execution » et
 * « historique des plans » se calculaient a partir de donnees que la
 * demonstration sert quand meme (charges et efficacite). Il n'y avait donc rien
 * a retirer cote serveur : c'est bien le code de rendu qui manque ici, et non
 * une donnee qu'on masquerait.
 *
 * L'affichage des directives outline et le bouton « Fixer ce plan » sont
 * egalement partis ; les outlines elles-memes sont videes cote serveur.
 */
(function () {
'use strict';

var D = window.DemoOracleReport && window.DemoOracleReport.sqlDetail;
if (!D) { console.error('[SQL Detail] window.DemoOracleReport.sqlDetail manquant'); return; }

var PLANS           = D.PLANS;
var EFFICIENCY      = D.EFFICIENCY;
var SQL_TEXT        = D.SQL_TEXT;
var LOAD_HIST       = D.LOAD_HIST;
var SQL_MON         = D.SQL_MON || [];
var SQLMON_URL      = D.SQLMON_URL || '';
var TXT             = D.T || {};
var SQL_ID          = D.SQL_ID;
var CDN_CHARTJS     = D.CDN_CHARTJS;

/* ── Formatters ─────────────────────────────────────────────────────────── */
function fmtN(n) {
    if (n === null || n === undefined) return '—';
    if (n >= 1e12) return (n/1e12).toFixed(1) + 'T';
    if (n >= 1e9)  return (n/1e9).toFixed(1)  + 'G';
    if (n >= 1e6)  return (n/1e6).toFixed(1)  + 'M';
    if (n >= 1e3)  return (n/1e3).toFixed(1)  + 'K';
    return String(n);
}
function fmtB(b) {
    if (b === null || b === undefined) return '—';
    if (b >= 1073741824) return (b/1073741824).toFixed(1) + ' GB';
    if (b >= 1048576)    return (b/1048576).toFixed(1)    + ' MB';
    if (b >= 1024)       return (b/1024).toFixed(1)       + ' KB';
    return b + ' B';
}
function fmtS(s) {
    if (s === null || s === undefined) return '—';
    if (s >= 3600) return (s/3600).toFixed(1) + 'h';
    if (s >= 60)   return (s/60).toFixed(1)   + 'min';
    return s + 's';
}
function cpuPct(cost, ioc) {
    if (!cost) return 0;
    if (ioc === null || ioc === undefined) return 100;
    return Math.ceil(100 - (ioc / cost) * 100);
}
function esc(s) {
    return String(s)
        .replace(/&/g,'&amp;').replace(/</g,'&lt;')
        .replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}

/* ── Provenance des donnees historisees ─────────────────────────────────── */
/*
 * L'etiquette « AWR » etait ecrite en dur : quand sw$bash_snapper prenait le
 * relais, l'ecran annoncait AWR sur des donnees qui n'en venaient pas. La
 * source est desormais transmise par le serveur et lue ici.
 */
function srcLabel(src) {
    if (src === 'memory') return TXT.src_memory || 'Mémoire';
    if (src === 'bash')   return TXT.src_bash   || 'sw$bash';
    if (src === 'awr')    return TXT.src_awr    || 'AWR';
    return src || '?';
}
function srcColor(src) {
    if (src === 'memory') return '#22c55e';
    if (src === 'bash')   return '#a855f7';
    return '#3b82f6';
}
/* « Pas de licence » et « source presente, mais aucune donnee » ne sont pas la
   meme chose : les confondre envoie l'admin chercher une licence qu'il a. */
function noHistMsg(src) {
    if (!src) return TXT.hist_none || 'Aucune source historique : ni Diagnostic Pack, ni sw$bash_snapper.';
    return (TXT.hist_empty || 'Aucune donnée historisée pour ce SQL_ID (source : {src}).')
           .replace('{src}', srcLabel(src));
}

/* ── Tab switching ──────────────────────────────────────────────────────── */
var _rendered = {};

function showTab(name) {
    document.querySelectorAll('.sql-tab-btn').forEach(function(btn) {
        var on = btn.dataset.tab === name;
        btn.style.background  = on ? 'var(--color-primary,#4e79a7)' : '';
        btn.style.color       = on ? '#fff' : '';
        btn.style.borderColor = on ? 'var(--color-primary,#4e79a7)' : 'var(--color-border,#ddd)';
    });
    document.querySelectorAll('.sql-tab-pane').forEach(function(p) {
        p.style.display = p.id === 'sqlpane-' + name ? '' : 'none';
    });
    if (!_rendered[name]) {
        _rendered[name] = true;
        // Les trois onglets reserves n'ont pas de fonction de rendu : leur
        // panneau est deja rempli par le serveur.
        if (name === 'plans')      renderPlansTab();
        if (name === 'efficiency') renderEfficiencyTab();
        if (name === 'text')       renderSqlTextTab();
        if (name === 'loadhist')   renderLoadHistTab();
        if (name === 'sqlmon')     renderSqlmonTab();
    }
}

/* ── SQL Monitoring tab ─────────────────────────────────────────────────── */
/*
 * Les executions en cours de ce SQL_ID, chacune liant vers le moniteur.
 *
 * Sans licence, il n'existe aucun inventaire persistant : la liste ne montre
 * QUE ce qui tourne, et le dit. C'est la limite honnete de cette source — la
 * masquer laisserait croire a une base au repos.
 */
function renderSqlmonTab() {
    var el = document.getElementById('sqlpane-sqlmon-content');
    if (!el) return;

    var note = '<p style="margin:0 0 .6rem;font-size:.82rem;color:var(--color-text-muted)">'
             + esc(TXT.sqlmon_running || '') + '</p>';

    if (!SQL_MON.length) {
        el.innerHTML = note + '<p style="margin:0;color:var(--color-text-muted)">'
                     + esc(TXT.sqlmon_none || '') + '</p>';
        return;
    }

    var h = note + '<table style="width:100%;border-collapse:collapse;font-size:.82rem">'
          + '<thead><tr style="border-bottom:1px solid var(--color-border,#ddd)">'
          + '<th style="text-align:right;padding:.3rem .5rem">SQL_EXEC_ID</th>'
          + '<th style="text-align:left;padding:.3rem .5rem">' + esc(TXT.sqlmon_begin || '') + '</th>'
          + '<th style="text-align:left;padding:.3rem .5rem">' + esc(TXT.sqlmon_status || '') + '</th>'
          + '<th style="text-align:right;padding:.3rem .5rem">Elapsed</th>'
          + '<th style="text-align:right;padding:.3rem .5rem">Sessions</th>'
          + '<th style="text-align:left;padding:.3rem .5rem">User</th>'
          + '<th style="text-align:left;padding:.3rem .5rem">Module</th>'
          + '<th style="padding:.3rem .5rem"></th></tr></thead><tbody>';

    for (var i = 0; i < SQL_MON.length; i++) {
        var r = SQL_MON[i];
        var url = SQLMON_URL
                + '&sql_id='     + encodeURIComponent(r.sql_id)
                + '&exec_id='    + encodeURIComponent(r.sql_exec_id)
                + '&exec_start=' + encodeURIComponent(r.exec_start)
                + '&inst_id='    + encodeURIComponent(r.inst_id)
                + '&phv='        + encodeURIComponent(r.phv || '');
        h += '<tr style="border-bottom:1px solid var(--color-border,#eee)">'
           + '<td style="padding:.3rem .5rem;text-align:right">' + esc(r.sql_exec_id) + '</td>'
           + '<td style="padding:.3rem .5rem;font-family:monospace;font-size:.74rem">' + esc(r.exec_start) + '</td>'
           + '<td style="padding:.3rem .5rem;color:#059669">' + esc(r.status) + '</td>'
           + '<td style="padding:.3rem .5rem;text-align:right">' + fmtN(r.elapsed_s) + 's</td>'
           + '<td style="padding:.3rem .5rem;text-align:right">' + esc(r.sessions) + '</td>'
           + '<td style="padding:.3rem .5rem">' + esc(r.username) + '</td>'
           + '<td style="padding:.3rem .5rem;font-size:.76rem">' + esc(r.module) + '</td>'
           + '<td style="padding:.3rem .5rem"><a class="btn btn-sm btn-secondary" href="'
           + esc(url) + '">' + esc(TXT.sqlmon_open || '') + '</a></td></tr>';
    }
    el.innerHTML = h + '</tbody></table>';
}

/* ── Plans tab ──────────────────────────────────────────────────────────── */
function renderPlansTab() {
    var el = document.getElementById('sqlpane-plans-content');
    if (!el) return;
    if (!PLANS.length) {
        el.innerHTML = '<p style="color:var(--color-text-muted);padding:.25rem">Aucun plan d\'exécution trouvé pour ce SQL_ID.</p>';
        return;
    }
    if (window.console) console.log('[SQL Detail] xml_info par plan :', JSON.stringify(PLANS.map(function(p) {
        return { phv: p.phv, source: p.source, xml_info: p.xml_info };
    }), null, 2));
    var html = '';
    /* PHV selector */
    if (PLANS.length > 1) {
        html += '<div style="display:flex;align-items:center;gap:.35rem;flex-wrap:wrap;margin-bottom:.5rem">';
        html += '<span style="font-size:.8rem;color:var(--color-text-muted)">Plan :</span>';
        PLANS.forEach(function(plan, i) {
            var badge = plan.source === 'memory'
                ? '<span style="font-size:.65rem;background:#22c55e;color:#fff;border-radius:3px;padding:0 .25rem;margin-left:.2rem;vertical-align:middle">Actif</span>'
                : '<span style="font-size:.65rem;background:#3b82f6;color:#fff;border-radius:3px;padding:0 .25rem;margin-left:.2rem;vertical-align:middle">AWR</span>';
            html += '<button id="phvbtn-' + i + '" onclick="selectPlan(' + i + ')" '
                + 'style="font-size:.78rem;padding:.2rem .5rem;cursor:pointer;border-radius:4px;'
                + 'border:1px solid var(--color-border,#ddd);background:transparent;color:inherit">'
                + esc(plan.phv) + badge + '</button>';
        });
        html += '</div>';
    }
    html += '<div id="sql-plan-view"></div>';
    el.innerHTML = html;
    selectPlan(0);
}

window.selectPlan = function(idx) {
    PLANS.forEach(function(_, i) {
        var b = document.getElementById('phvbtn-' + i);
        if (!b) return;
        var on = (i === idx);
        b.style.background  = on ? 'var(--color-primary,#4e79a7)' : '';
        b.style.color       = on ? '#fff' : '';
        b.style.borderColor = on ? 'var(--color-primary,#4e79a7)' : 'var(--color-border,#ddd)';
    });
    var el = document.getElementById('sql-plan-view');
    if (!el) return;
    var plan = PLANS[idx];
    if (!plan) { el.innerHTML = ''; return; }

    var html = '';
    var xi = plan.xml_info;
    /* Plan header -- PHV + source badge + version */
    var srcColor = plan.source === 'memory' ? '#22c55e' : '#3b82f6';
    var srcLabel = plan.source === 'memory' ? 'En mémoire (actif)' : 'AWR (historique)';
    html += '<div style="display:flex;align-items:center;flex-wrap:wrap;gap:.45rem;margin-bottom:.5rem">';
    html += '<code style="font-size:.88rem;font-weight:700">' + esc(plan.phv) + '</code>';
    html += '<span style="font-size:.72rem;background:' + srcColor + ';color:#fff;border-radius:3px;padding:1px .35rem">' + srcLabel + '</span>';
    if (xi.db_version) {
        html += '<span style="font-size:.72rem;color:var(--color-text-muted)">Oracle ' + esc(xi.db_version) + '</span>';
    }
    html += '</div>';

    /* Indicators grid */
    html += '<div style="display:flex;flex-wrap:wrap;gap:.35rem .9rem;margin-bottom:.55rem;font-size:.8rem;'
        + 'padding:.35rem .55rem;border-radius:4px;background:var(--color-input-bg,#f8f8f8);'
        + 'border:1px solid var(--color-border,#eee)">';
    html += '<span title="Profil SQL appliqué">'
        + (xi.sql_profile ? '✅' : '☐')
        + ' <b>SQL Profile</b>'
        + (xi.sql_profile ? ' <code style="font-size:.72rem;vertical-align:middle">' + esc(xi.sql_profile) + '</code>' : '')
        + '</span>';
    html += '<span title="Plan adaptable (adaptive plan)">'
        + (xi.adaptive ? '✅' : '☐') + ' <b>Adaptable</b></span>';
    html += '<span title="Retour de cardinalité (cardinality feedback)">'
        + (xi.cardinality_feedback ? '✅' : '☐') + ' <b>Card. feedback</b></span>';
    if (xi.parallel_dop !== null && xi.parallel_dop !== undefined) {
        html += '<span title="Degré de parallélisme utilisé" style="color:#8b5cf6">'
            + '✅ <b>Parallèle</b> DOP&nbsp;' + esc(String(xi.parallel_dop)) + '</span>';
    } else {
        html += '<span title="Pas de parallélisme" style="color:var(--color-text-muted)">☐ Parallèle</span>';
    }
    if (xi.dynamic_sampling !== null && xi.dynamic_sampling !== undefined) {
        html += '<span title="Niveau d\'échantillonnage dynamique utilisé">'
            + '✅ <b>Éch. dynamique</b> niveau&nbsp;' + xi.dynamic_sampling + '</span>';
    } else {
        html += '<span title="Pas d\'échantillonnage dynamique" style="color:var(--color-text-muted)">☐ Éch. dynamique</span>';
    }
    html += '</div>';

    /* No xml data notice */
    var _hasXml = xi.sql_profile || xi.hint_usage || xi.adaptive || xi.parallel_dop !== null
        || xi.cardinality_feedback || xi.dynamic_sampling !== null || xi.bind_equiv_failure
        || (xi.peeked_binds && xi.peeked_binds.length);
    if (!_hasXml) {
        html += '<p style="font-size:.75rem;color:var(--color-text-muted);margin-bottom:.35rem">'
            + '<em>Aucune métadonnée OTHER_XML : SQL simple, sans profil appliqué, '
            + 'sans hints et sans bind peeké.</em></p>';
    }

    /* Hint usage */
    if (xi.hint_usage) {
        html += '<details style="margin-bottom:.45rem"><summary style="font-size:.78rem;cursor:pointer">💡 Utilisation des hints</summary>';
        html += '<pre style="margin:.25rem 0 0;font-size:.72rem;overflow-x:auto;max-height:160px;'
            + 'padding:.35rem .6rem;border-radius:4px;border:1px solid var(--color-border,#ddd);'
            + 'background:var(--color-input-bg,#f8f8f8);white-space:pre-wrap;word-break:break-all">'
            + esc(xi.hint_usage) + '</pre></details>';
    }

    /* Bind equivalence failure */
    if (xi.bind_equiv_failure) {
        html += '<div style="margin-bottom:.45rem;padding:.3rem .55rem;border-radius:4px;'
            + 'background:#fef9c3;border:1px solid #ca8a04;color:#713f12;font-size:.78rem">'
            + '⚠ <b>Bind equiv. failure :</b> ' + esc(xi.bind_equiv_failure) + '</div>';
    }

    /* Peeked bind variables */
    if (xi.peeked_binds && xi.peeked_binds.length) {
        html += '<details style="margin-bottom:.45rem"><summary style="font-size:.78rem;cursor:pointer">'
            + '🔍 Bind variables peeked (' + xi.peeked_binds.length + ')</summary>';
        html += '<div style="overflow-x:auto;margin-top:.25rem"><table style="border-collapse:collapse;font-size:.78rem;width:auto">';
        html += '<thead><tr style="border-bottom:1px solid var(--color-border,#ddd)">';
        ['Pos', 'Nom', 'Type', 'Valeur'].forEach(function(h) {
            html += '<th style="padding:2px 8px;text-align:left;white-space:nowrap">' + h + '</th>';
        });
        html += '</tr></thead><tbody>';
        xi.peeked_binds.forEach(function(b) {
            html += '<tr style="border-bottom:1px solid var(--color-border,#eee)">';
            html += '<td style="padding:2px 8px;text-align:right;color:var(--color-text-muted)">' + esc(String(b.pos)) + '</td>';
            html += '<td style="padding:2px 8px;font-family:monospace">' + esc(b.name) + '</td>';
            html += '<td style="padding:2px 8px;color:var(--color-text-muted)">' + esc(b.type) + '</td>';
            html += '<td style="padding:2px 8px;font-family:monospace;max-width:320px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + esc(String(b.value)) + '</td>';
            html += '</tr>';
        });
        html += '</tbody></table></div></details>';
    }

    /* Les directives outline etaient affichees ici, et le bouton « Fixer ce
       plan » juste apres. La demonstration ne les recoit pas : le serveur vide
       xml_info.outline avant l'envoi. */

    /* Plan adaptable : si xml_info.display_map est présent, on applique la
       carte de présentation Oracle (renumérotation, profondeur, skipped).
       Sinon on retombe sur l'ordre/profondeur naturels du plan. */
    var dm        = xi.display_map || {};
    var hasDm     = false;
    for (var _k in dm) { if (dm.hasOwnProperty(_k)) { hasDm = true; break; } }
    var dispSteps = plan.steps.map(function(step) {
        var d = hasDm ? dm[step.id] : null;
        return {
            step:     step,
            dispId:   d ? d.dis : step.id,
            dispDep:  d ? d.dep : step.depth,
            skipped:  d ? (d.skp === 1) : false,
            sortKey:  d ? d.dis : step.id,
        };
    });
    if (hasDm) dispSteps.sort(function(a, b) { return a.sortKey - b.sortKey; });

    /* Plan table */
    html += '<div style="overflow-x:auto"><table style="width:100%;border-collapse:collapse;font-size:.8rem">';
    html += '<thead><tr style="border-bottom:2px solid var(--color-border,#ddd)">';
    ['ID','Opération','Objet','Coût (%CPU)','Lignes','Octets','Temps'].forEach(function(h, i) {
        var align = i === 0 || i >= 3 ? 'right' : 'left';
        html += '<th style="text-align:' + align + ';padding:3px 5px;white-space:nowrap">' + h + '</th>';
    });
    html += '</tr></thead><tbody>';

    dispSteps.forEach(function(entry, si) {
        var step    = entry.step;
        var indent  = entry.dispDep * 16;
        var skipped = entry.skipped;
        var opText  = step.op + (step.opts ? ' ' + step.opts : '');
        var objText = step.owner && step.obj ? step.owner + '.' + step.obj : step.obj;
        var hasPred = step.acc || step.flt;
        var detId   = 'pred-' + idx + '-' + si;
        var hasPart = step.pst && step.pst !== '' && step.pst !== 'ROW LOCATION';

        /* Query block : part after '@' in object_alias */
        var qb = '';
        if (step.alias) {
            var atIdx = step.alias.indexOf('@');
            if (atIdx !== -1) qb = step.alias.substring(atIdx + 1);
        }

        /* skp=1 => étape non retenue par l'optimiseur adaptable => on
           grise le fond (clair) et on écrit dans un gris un peu plus
           foncé. Le titre tooltip indique le pourquoi. */
        var skipStyle = skipped
            ? 'background:#CCCCCC;color:#777777;'
            : '';
        var skipTitle = skipped
            ? ' title="Étape non retenue par le plan adaptable (skp=1)"'
            : '';

        html += '<tr style="border-bottom:1px solid var(--color-border,#eee);' + skipStyle
            + (hasPred ? 'cursor:pointer;' : '') + '"'
            + skipTitle
            + (hasPred ? ' onclick="togglePred(\'' + detId + '\')"' : '') + '>';
        html += '<td style="text-align:right;padding:3px 5px;color:var(--color-text-muted);font-variant-numeric:tabular-nums">'
            + (skipped ? '-' : '') + entry.dispId + '</td>';
        html += '<td style="padding:3px 5px;padding-left:' + (indent + 5) + 'px;font-family:monospace;font-size:.76rem;white-space:nowrap">';
        if (hasPred) html += '<span style="color:var(--color-primary,#4e79a7);margin-right:.2rem">▸</span>';
        html += esc(opText);
        if (qb) html += '<span style="font-size:.65rem;color:var(--color-text-muted);margin-left:.4rem;opacity:.75">@' + esc(qb) + '</span>';
        html += '</td>';
        html += '<td style="padding:3px 5px;font-size:.75rem;white-space:nowrap;color:var(--color-text-muted)">'
            + esc(objText);
        if (hasPart) html += ' <small style="opacity:.6">[' + esc(step.pst) + '..' + esc(step.psp) + ']</small>';
        html += '</td>';
        if (step.cost !== null) {
            var pct = cpuPct(step.cost, step.ioc);
            html += '<td style="text-align:right;padding:3px 5px;white-space:nowrap;font-family:monospace;font-size:.76rem">'
                + fmtN(step.cost)
                + '<span style="color:var(--color-text-muted);font-size:.68rem"> (' + pct + '%)</span></td>';
        } else {
            html += '<td style="text-align:right;padding:3px 5px">—</td>';
        }
        html += '<td style="text-align:right;padding:3px 5px">' + (step.rows  !== null ? fmtN(step.rows)  : '—') + '</td>';
        html += '<td style="text-align:right;padding:3px 5px">' + (step.bytes !== null ? fmtB(step.bytes) : '—') + '</td>';
        html += '<td style="text-align:right;padding:3px 5px">' + (step.tms   !== null ? fmtS(step.tms)   : '—') + '</td>';
        html += '</tr>';

        if (hasPred) {
            html += '<tr id="' + detId + '" style="display:none;' + skipStyle + 'background:var(--color-input-bg,#f8f8f8)">';
            html += '<td></td><td colspan="6" style="padding:.25rem .5rem .25rem ' + (indent + 5) + 'px;font-size:.72rem;font-family:monospace">';
            if (step.acc) html += '<div><b style="color:#3b82f6">Access :</b> ' + esc(step.acc) + '</div>';
            if (step.flt) html += '<div><b style="color:#ef4444">Filter :</b> ' + esc(step.flt) + '</div>';
            html += '</td></tr>';
        }
    });
    html += '</tbody></table></div>';
    el.innerHTML = html;
};

window.togglePred = function(id) {
    var e = document.getElementById(id);
    if (e) e.style.display = e.style.display === 'none' ? '' : 'none';
};

/* ── Efficiency tab ─────────────────────────────────────────────────────── */
function renderEfficiencyTab() {
    var el = document.getElementById('sqlpane-efficiency-content');
    if (!el) return;
    var rows = [];
    var histSrc = EFFICIENCY.hist_source || '';
    (EFFICIENCY.memory || []).forEach(function(r) { rows.push(Object.assign({ src: 'memory' }, r)); });
    (EFFICIENCY.hist   || []).forEach(function(r) { rows.push(Object.assign({ src: histSrc  }, r)); });

    if (!rows.length) {
        el.innerHTML = '<p style="color:var(--color-text-muted);padding:.25rem">'
                     + esc(noHistMsg(histSrc)) + '</p>';
        return;
    }
    var heads = ['PHV','Source','Exécutions','Elapsed s','/exec ms','CPU s','/exec ms','Buf. gets','/exec','Disk reads','Lignes','Parse calls'];
    var html  = '<div style="overflow-x:auto"><table style="width:100%;border-collapse:collapse;font-size:.8rem">';
    html += '<thead><tr style="border-bottom:2px solid var(--color-border,#ddd)">';
    heads.forEach(function(h) {
        html += '<th style="text-align:right;padding:3px 6px;white-space:nowrap">' + esc(h) + '</th>';
    });
    html += '</tr></thead><tbody>';
    rows.forEach(function(r) {
        var sc = srcColor(r.src);
        var sl = srcLabel(r.src);
        html += '<tr style="border-bottom:1px solid var(--color-border,#eee)">';
        html += '<td style="padding:3px 6px;font-family:monospace;font-size:.75rem;white-space:nowrap">' + esc(r.phv) + '</td>';
        html += '<td style="padding:3px 6px"><span style="font-size:.68rem;background:' + sc + ';color:#fff;border-radius:3px;padding:1px .3rem">' + sl + '</span></td>';
        html += '<td style="text-align:right;padding:3px 6px">' + fmtN(r.execs)       + '</td>';
        html += '<td style="text-align:right;padding:3px 6px">' + r.elapsed_s          + '</td>';
        html += '<td style="text-align:right;padding:3px 6px">' + r.etime_exec         + '</td>';
        html += '<td style="text-align:right;padding:3px 6px">' + r.cpu_s              + '</td>';
        html += '<td style="text-align:right;padding:3px 6px">' + r.cpu_exec           + '</td>';
        html += '<td style="text-align:right;padding:3px 6px">' + fmtN(r.buf_gets)     + '</td>';
        html += '<td style="text-align:right;padding:3px 6px">' + fmtN(r.bufs_exec)    + '</td>';
        html += '<td style="text-align:right;padding:3px 6px">' + fmtN(r.disk_reads)   + '</td>';
        html += '<td style="text-align:right;padding:3px 6px">' + fmtN(r.rows_proc)    + '</td>';
        html += '<td style="text-align:right;padding:3px 6px">' + fmtN(r.parse_calls)  + '</td>';
        html += '</tr>';
    });
    html += '</tbody></table></div>';
    el.innerHTML = html;
}

/* ── SQL Text tab ───────────────────────────────────────────────────────── */
function renderSqlTextTab() {
    var el = document.getElementById('sqlpane-text-content');
    if (!el) return;
    if (!SQL_TEXT.text) {
        el.innerHTML = '<p style="color:var(--color-text-muted);padding:.25rem">Texte SQL non disponible.</p>';
        return;
    }
    var sc      = SQL_TEXT.source === 'memory' ? '#22c55e' : '#3b82f6';
    var sl      = SQL_TEXT.source === 'memory' ? 'En mémoire' : 'AWR';
    var btnBase = 'font-size:.78rem;padding:.2rem .5rem;cursor:pointer;border:1px solid var(--color-border,#ddd);'
        + 'border-radius:4px;background:transparent;color:inherit';
    var html = '<div style="display:flex;align-items:center;gap:.5rem;margin-bottom:.5rem;flex-wrap:wrap">';
    html += '<span style="font-size:.72rem;background:' + sc + ';color:#fff;border-radius:3px;padding:1px .35rem">' + sl + '</span>';
    html += '<button id="sql-copy-btn" style="' + btnBase + '">📋 Copier</button>';
    html += '<button id="sql-fmt-btn"  style="' + btnBase + '">✨ Mettre en forme</button>';
    html += '</div>';
    html += '<pre id="sql-text-pre" style="margin:0;overflow-x:auto;max-height:70vh;font-size:.8rem;'
         + 'line-height:1.5;padding:.6rem .8rem;border-radius:4px;border:1px solid var(--color-border,#ddd);'
         + 'background:transparent;white-space:pre-wrap;word-break:break-all"></pre>';
    el.innerHTML = html;

    var pre         = document.getElementById('sql-text-pre');
    var copyBtn     = document.getElementById('sql-copy-btn');
    var fmtBtn      = document.getElementById('sql-fmt-btn');
    var origText    = SQL_TEXT.text;
    var fmtText     = null;   // lazy-computed formatted version
    var isFormatted = false;

    function applyHighlight(t) {
        if (window.hljs) {
            try { pre.innerHTML = hljs.highlight(t, { language: 'sql', ignoreIllegals: true }).value; }
            catch(e) { pre.textContent = t; }
        } else { pre.textContent = t; }
    }
    applyHighlight(origText);

    if (fmtBtn) fmtBtn.addEventListener('click', function() {
        if (!isFormatted) {
            /* First click : format and cache */
            if (fmtText === null) {
                if (window.sqlFormatter && typeof sqlFormatter.format === 'function') {
                    try {
                        fmtText = sqlFormatter.format(origText, {
                            language:               'plsql',
                            keywordCase:            'lower',
                            tabWidth:               4,
                            logicalOperatorNewline: 'before',
                            indentStyle:            'standard',
                            expressionWidth:        60,
                        });
                        /* Move trailing commas to next-line start (comma-first style) */
                        fmtText = fmtText.replace(/,\n( +)/g, function(_, sp) {
                            return '\n' + (sp.length >= 2 ? sp.slice(2) : '') + ', ';
                        });
                    } catch(e) { console.error('[sql-fmt]', e); fmtText = origText; }
                } else {
                    fmtBtn.textContent = '⚠ sql-formatter non chargé';
                    return;
                }
            }
            applyHighlight(fmtText);
            fmtBtn.textContent = '↩ Forme initiale';
            isFormatted = true;
        } else {
            /* Second click : restore original */
            applyHighlight(origText);
            fmtBtn.textContent = '✨ Mettre en forme';
            isFormatted = false;
        }
    });

    if (copyBtn) copyBtn.addEventListener('click', function() {
        var toCopy = isFormatted ? (fmtText || origText) : origText;
        var orig   = copyBtn.textContent;
        var done   = function() { copyBtn.textContent = '✓ Copié'; setTimeout(function() { copyBtn.textContent = orig; }, 2000); };
        if (navigator.clipboard) {
            navigator.clipboard.writeText(toCopy).then(done).catch(function() {});
        } else {
            var ta = document.createElement('textarea');
            ta.value = toCopy; ta.style.cssText = 'position:fixed;opacity:0';
            document.body.appendChild(ta); ta.select();
            try { document.execCommand('copy'); } catch(e) {}
            document.body.removeChild(ta); done();
        }
    });
}

/* ── Load History tab ───────────────────────────────────────────────────── */
function renderLoadHistTab() {
    var el = document.getElementById('sqlpane-loadhist-content');
    if (!el) return;

    var rows = LOAD_HIST && LOAD_HIST.rows;
    if (!rows || !rows.length) {
        el.innerHTML = '<p style="color:var(--color-text-muted);padding:.25rem">'
                     + esc(noHistMsg(LOAD_HIST && LOAD_HIST.source)) + '</p>';
        return;
    }

    /* -- Pivot rows -> snapOrder / phvSet / dataMap -- */
    var snapOrder = [], snapIdx = {}, phvSet = [];
    rows.forEach(function(r) {
        if (snapIdx[r.snap] === undefined) { snapIdx[r.snap] = snapOrder.length; snapOrder.push(r.snap); }
        if (phvSet.indexOf(r.phv) === -1) phvSet.push(r.phv);
    });
    var n = snapOrder.length;

    var METRICS = [
        { key:'exec', label:'Exécutions',          color:'#22c55e', div:1,   unit:'' },
        { key:'ela',  label:'Temps écoulé',         color:'#3b82f6', div:1e6, unit:'s' },
        { key:'cpu',  label:'Temps CPU',            color:'#06b6d4', div:1e6, unit:'s' },
        { key:'iow',  label:'Attente I/O',          color:'#f59e0b', div:1e6, unit:'s' },
        { key:'clw',  label:'Attente cluster',      color:'#ef4444', div:1e6, unit:'s' },
        { key:'apw',  label:'Attente applicative',  color:'#ec4899', div:1e6, unit:'s' },
        { key:'ccw',  label:'Attente concurrence',  color:'#8b5cf6', div:1e6, unit:'s' },
        { key:'lios', label:'Lectures logiques',    color:'#6366f1', div:1,   unit:'' },
        { key:'pio',  label:'Lectures physiques',   color:'#d97706', div:1,   unit:'' },
        { key:'rows', label:'Lignes traitées',      color:'#14b8a6', div:1,   unit:'' },
        { key:'prs',  label:'Parse calls',          color:'#6b7280', div:1,   unit:'' },
        { key:'fet',  label:'Fetches',              color:'#84cc16', div:1,   unit:'' },
    ];

    var PHV_PALETTE = ['#3b82f6','#ef4444','#22c55e','#f59e0b','#8b5cf6','#ec4899','#06b6d4','#d97706'];

    /* dataMap[metricKey][phv] = Float64Array(n) */
    var dataMap = {};
    rows.forEach(function(r) {
        var si = snapIdx[r.snap];
        METRICS.forEach(function(m) {
            if (!dataMap[m.key]) dataMap[m.key] = {};
            if (!dataMap[m.key][r.phv]) dataMap[m.key][r.phv] = new Array(n).fill(0);
            dataMap[m.key][r.phv][si] = (r[m.key] || 0) / m.div;
        });
    });

    /* X labels: strip year ("YYYY-MM-DD HH:MI" -> "MM-DD HH:MI") */
    var xLabels = snapOrder.map(function(s) { return s.substring(5); });

    /* -- Build HTML grid -- */
    var infoHtml = '<div style="font-size:.76rem;color:var(--color-text-muted);margin-bottom:.6rem">'
        + snapOrder.length + ' snapshots'
        + (phvSet.length > 1 ? ' · <b>' + phvSet.length + ' plans distincts</b>' : '')
        + '</div>';

    var gridHtml = '<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(340px,1fr));gap:.6rem">';
    METRICS.forEach(function(m) {
        gridHtml += '<div style="padding:.45rem .55rem;border:1px solid var(--color-border,#eee);border-radius:6px">';
        gridHtml += '<div style="font-size:.74rem;font-weight:600;margin-bottom:.2rem;color:' + m.color + '">'
            + m.label + (m.unit ? ' <span style="font-weight:400;opacity:.7">(' + m.unit + ')</span>' : '')
            + '</div>';
        gridHtml += '<div style="position:relative;height:150px"><canvas id="lhc-' + m.key + '"></canvas></div>';
        gridHtml += '</div>';
    });
    gridHtml += '</div>';
    el.innerHTML = infoHtml + gridHtml;

    /* -- Draw all charts once Chart.js is available -- */
    function drawAll() {
        METRICS.forEach(function(m) {
            var canvas = document.getElementById('lhc-' + m.key);
            if (!canvas) return;

            var datasets = phvSet.map(function(phv, pi) {
                var vals = (dataMap[m.key] && dataMap[m.key][phv]) || new Array(n).fill(0);
                var color = phvSet.length === 1 ? m.color : PHV_PALETTE[pi % PHV_PALETTE.length];
                return {
                    label: 'PHV ' + phv,
                    data: vals,
                    backgroundColor: color + 'b0',
                    borderColor: color,
                    borderWidth: 1,
                    borderRadius: 2,
                };
            });

            new window.Chart(canvas, {
                type: 'bar',
                data: { labels: xLabels, datasets: datasets },
                options: {
                    responsive: true,
                    maintainAspectRatio: false,
                    animation: false,
                    plugins: {
                        legend: {
                            display: phvSet.length > 1,
                            labels: { font: { size: 9 }, boxWidth: 10, padding: 6 }
                        },
                        tooltip: {
                            callbacks: {
                                title: function(items) { return snapOrder[items[0].dataIndex]; },
                                label: function(ctx) {
                                    var v = ctx.parsed.y;
                                    var f = v >= 1e9 ? (v/1e9).toFixed(2)+'G'
                                          : v >= 1e6 ? (v/1e6).toFixed(2)+'M'
                                          : v >= 1e3 ? (v/1e3).toFixed(1)+'K'
                                          : v.toFixed(v < 10 && v !== Math.round(v) ? 3 : 0);
                                    return (phvSet.length > 1 ? ctx.dataset.label + ': ' : '') + f + (m.unit ? ' ' + m.unit : '');
                                }
                            }
                        }
                    },
                    scales: {
                        x: {
                            ticks: { font: { size: 8 }, maxRotation: 55, autoSkip: true, maxTicksLimit: 10 },
                            grid: { display: false }
                        },
                        y: {
                            beginAtZero: true,
                            ticks: {
                                font: { size: 9 },
                                callback: function(v) {
                                    if (v >= 1e9) return (v/1e9).toFixed(1)+'G';
                                    if (v >= 1e6) return (v/1e6).toFixed(1)+'M';
                                    if (v >= 1e3) return (v/1e3).toFixed(1)+'K';
                                    return v % 1 === 0 ? v : v.toFixed(2);
                                }
                            }
                        }
                    }
                }
            });
        });
    }

    if (window.Chart) {
        drawAll();
    } else {
        var sc = document.createElement('script');
        sc.src = CDN_CHARTJS;
        sc.onload = drawAll;
        document.head.appendChild(sc);
    }
}

/* ── SQL Fix modal ─────────────────────────────────────────────────────── */
var _fixPlanIdx = -1;

document.querySelectorAll('input[name="fix_type"]').forEach(function(r) {
    r.addEventListener('change', sqlfixUpdateTypeUI);
});

/* ── Boot ───────────────────────────────────────────────────────────────── */
document.querySelectorAll('.sql-tab-btn:not([disabled])').forEach(function(btn) {
    btn.addEventListener('click', function() { showTab(btn.dataset.tab); });
});
showTab('plans');

}());
