{% extends "base.html" %}
{% block title %}{{ t(request, 'title.benchmarks') }}{% endblock %}

{% block content %}
<style>
  .bench-live-dot {
    display: inline-block; width: 10px; height: 10px;
    background: #22c55e; border-radius: 50%;
    animation: pulse-bench 1.4s infinite; margin-right: 6px;
  }
  @keyframes pulse-bench {
    0%   { box-shadow: 0 0 0 0 rgba(34,197,94,.7); }
    70%  { box-shadow: 0 0 0 8px rgba(34,197,94,0); }
    100% { box-shadow: 0 0 0 0 rgba(34,197,94,0); }
  }
  .log-box {
    background: #0d1117; color: #c9d1d9;
    font-family: 'Courier New', monospace; font-size: .78rem;
    border-radius: 8px; padding: 14px; height: 400px;
    overflow-y: auto; white-space: pre-wrap; word-break: break-word;
  }
  .log-line-ok   { color: #3fb950; }
  .log-line-fail { color: #f85149; }
  .log-line-warn { color: #d29922; }
  .score-badge {
    font-size: .78rem; padding: .3em .65em;
    border-radius: 12px; font-weight: 600;
  }
  .score-high { background: #dcfce7; color: #15803d; }
  .score-mid  { background: #fef9c3; color: #854d0e; }
  .score-low  { background: #fee2e2; color: #991b1b; }
  .check-ok   { color: #16a34a; }
  .check-fail { color: #dc2626; }
  .check-unknown { color: #6b7280; }
  /* banner visibility controlled via inline style in JS */
  .epoch-row:hover { background: var(--bs-tertiary-bg); cursor: pointer; }
</style>

{# ── System-lock banner (shown when a benchmark is running) ── #}
<div id="bench-lock-banner" class="alert alert-warning align-items-center gap-2 mb-3 py-2" role="alert" style="display:none">
  <span class="bench-live-dot"></span>
  <strong>{{ t(request, 'js.bench_running') }}</strong> — {{ t(request, 'js.bench_system_locked') }}
  <span id="bench-lock-detail" class="ms-2 small text-muted"></span>
</div>

<div class="d-flex align-items-center justify-content-between mb-3">
  <h4 class="mb-0 fw-bold">
    <i class="bi bi-speedometer2 me-2" style="color:#6ea8fe"></i>{{ t(request, 'nav.benchmarks') }}
  </h4>
  <div class="d-flex gap-2">
    <button class="btn btn-sm btn-outline-secondary" onclick="refreshAll()">
      <i class="bi bi-arrow-clockwise me-1"></i>{{ t(request, 'btn.refresh') }}
    </button>
    <button class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#triggerModal"
            id="btn-trigger-bench">
      <i class="bi bi-play-fill me-1"></i>{{ t(request, 'js.bench_start') }}
    </button>
  </div>
</div>

{# ── Service Account Card ── #}
<div class="card mb-4">
  <div class="card-header bg-warning bg-opacity-10">
    <i class="bi bi-key-fill me-2"></i>
    <strong>{{ t(request, 'lbl.sa_benchmarks') }}</strong>
    <span class="text-muted small ms-2" id="sa-key-prefix">—</span>
  </div>
  <div class="card-body">
    <p class="text-muted small mb-3">
      {{ t(request, 'msg.sa_benchmarks_desc') }}
      (<code>benchmarks/.env</code>). {{ t(request, 'msg.sa_benchmarks_note') }}
    </p>
    <div class="row g-2" id="sa-template-list">
      <div class="text-muted small">{{ t(request, 'js.loading') }}…</div>
    </div>
    <div class="mt-3 d-flex gap-2">
      <select id="sa-add-select" class="form-select form-select-sm" style="max-width:320px">
        <option value="">{{ t(request, 'lbl.sa_add_template') }}</option>
      </select>
      <button class="btn btn-sm btn-primary" onclick="saGrantTemplate()">
        <i class="bi bi-plus-lg me-1"></i>{{ t(request, 'btn.add') }}
      </button>
    </div>
    <div id="sa-msg" class="mt-2 small"></div>
  </div>
</div>

{# ── Tabs ── #}
<ul class="nav nav-tabs mb-3" id="benchTabs">
  <li class="nav-item">
    <a class="nav-link active" data-bs-toggle="tab" href="#tab-live">
      <i class="bi bi-activity me-1"></i>Live
    </a>
  </li>
  <li class="nav-item">
    <a class="nav-link" data-bs-toggle="tab" href="#tab-overnight">
      <i class="bi bi-moon-stars me-1"></i>{{ t(request, 'js.overnight_runs') }}
    </a>
  </li>
  <li class="nav-item">
    <a class="nav-link" data-bs-toggle="tab" href="#tab-single">
      <i class="bi bi-file-earmark-bar-graph me-1"></i>{{ t(request, 'js.single_runs') }}
    </a>
  </li>
  <li class="nav-item">
    <a class="nav-link" data-bs-toggle="tab" href="#tab-gaia" onclick="refreshGaiaList()">
      <i class="bi bi-robot me-1"></i>{{ t(request, 'js.gaia_runs') }}
    </a>
  </li>
  <li class="nav-item">
    <a class="nav-link" data-bs-toggle="tab" href="#tab-longmemeval" onclick="refreshLongMemEvalList()">
      <i class="bi bi-journal-text me-1"></i>{{ t(request, 'js.longmemeval_runs') }}
    </a>
  </li>
</ul>

<div class="tab-content">

  {# ── Tab: Live ── #}
  <div class="tab-pane fade show active" id="tab-live">
    <div class="row g-3 mb-3">
      <div class="col-md-4">
        <div class="card h-100">
          <div class="card-header bg-dark text-light">
            <i class="bi bi-info-circle me-1"></i>Status
          </div>
          <div class="card-body p-3">
            <div id="live-status-content">
              <div class="text-muted small">{{ t(request, 'js.loading') }}</div>
            </div>
          </div>
        </div>
      </div>
      <div class="col-md-8">
        <div class="card h-100">
          <div class="card-header bg-dark text-light d-flex justify-content-between align-items-center">
            <span><i class="bi bi-terminal me-1"></i>{{ t(request, 'js.runner_log') }}
              <span id="live-log-file" class="text-secondary small ms-2"></span>
            </span>
            <div class="form-check form-switch mb-0 d-flex align-items-center gap-2">
              <input class="form-check-input" type="checkbox" id="autoScrollToggle" checked>
              <label class="form-check-label small text-secondary" for="autoScrollToggle">Auto-Scroll</label>
            </div>
          </div>
          <div class="card-body p-0">
            <div class="log-box" id="live-log-box">
              <span class="text-muted">{{ t(request, 'js.waiting_for_log') }}</span>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>

  {# ── Tab: Overnight runs ── #}
  <div class="tab-pane fade" id="tab-overnight">
    <div id="overnight-list-content">
      <div class="text-muted small">{{ t(request, 'js.loading') }}</div>
    </div>
  </div>

  {# ── Tab: Single runs ── #}
  <div class="tab-pane fade" id="tab-single">
    <div id="single-list-content">
      <div class="text-muted small">{{ t(request, 'js.loading') }}</div>
    </div>
  </div>

  {# ── Tab: GAIA runs ── #}
  <div class="tab-pane fade" id="tab-gaia">
    <div id="gaia-list-content">
      <div class="text-muted small">{{ t(request, 'js.loading') }}</div>
    </div>
  </div>

  {# ── Tab: LongMemEval runs ── #}
  <div class="tab-pane fade" id="tab-longmemeval">
    <div id="longmemeval-list-content">
      <div class="text-muted small">{{ t(request, 'js.loading') }}</div>
    </div>
  </div>

</div>{# /tab-content #}


{# ── Result Detail Modal ── #}
<div class="modal fade" id="resultModal" tabindex="-1">
  <div class="modal-dialog modal-xl modal-dialog-scrollable">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title"><i class="bi bi-bar-chart-line me-2"></i><span id="result-modal-title">{{ t(request, 'js.result') }}</span></h5>
        <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
      </div>
      <div class="modal-body" id="result-modal-body">
        <div class="text-muted">{{ t(request, 'js.loading') }}</div>
      </div>
    </div>
  </div>
</div>


{# ── Trigger Modal ── #}
<div class="modal fade" id="triggerModal" tabindex="-1">
  <div class="modal-dialog modal-lg">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title"><i class="bi bi-play-circle me-2"></i>{{ t(request, 'js.bench_start') }}</h5>
        <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
      </div>
      <div class="modal-body">

        {# Benchmark type selector #}
        <div class="mb-3">
          <label class="form-label fw-semibold">{{ t(request, 'js.bench_type') }}</label>
          <select class="form-select" id="trigger-bench-type" onchange="onBenchTypeChange()">
            <option value="overnight">{{ t(request, 'js.bench_type_overnight') }}</option>
            <option value="gaia">{{ t(request, 'js.bench_type_gaia') }}</option>
            <option value="longmemeval">{{ t(request, 'js.bench_type_longmemeval') }}</option>
          </select>
        </div>
        <hr>

        {# ── Overnight fields ── #}
        <div id="trigger-overnight-fields">
          <div class="mb-3">
            <div class="d-flex align-items-center justify-content-between mb-2">
              <strong><i class="bi bi-clipboard-check me-1"></i>Pre-Flight Check</strong>
              <button class="btn btn-sm btn-outline-secondary" onclick="runPreflight()">
                <i class="bi bi-arrow-clockwise me-1"></i>{{ t(request, 'btn.template_verify') }}
              </button>
            </div>
            <div id="preflight-result" class="text-muted small">{{ t(request, 'js.preflight_not_checked') }}</div>
          </div>
          <div class="mb-3">
            <label class="form-label fw-semibold">Template</label>
            <input type="text" class="form-control" id="trigger-template" value="moe-m10-gremium-deep"
                   placeholder="{{ t(request, 'js.bench_template_placeholder') }}">
          </div>
          <div class="mb-3">
            <label class="form-label fw-semibold">Dataset</label>
            <select class="form-select" id="trigger-dataset">
              {% for ds in datasets %}
              <option value="{{ ds }}" {% if 'overnight' in ds %}selected{% endif %}>{{ ds }}</option>
              {% endfor %}
            </select>
          </div>
          <div class="mb-3">
            <label class="form-label fw-semibold">{{ t(request, 'js.epochs') }}</label>
            <input type="number" class="form-control" id="trigger-epochs" value="10" min="1" max="20">
          </div>
          <div class="alert alert-info small mb-0">
            <i class="bi bi-info-circle me-1"></i>
            {{ t(request, 'js.bench_info') }}
          </div>
        </div>

        {# ── LongMemEval fields ── #}
        <div id="trigger-longmemeval-fields" style="display:none">
          <div class="mb-3">
            <label class="form-label fw-semibold">Template</label>
            <input type="text" class="form-control" id="trigger-lme-template" value="moe-m10-gremium-deep"
                   placeholder="{{ t(request, 'js.bench_template_placeholder') }}">
          </div>
          <div class="mb-3">
            <label class="form-label fw-semibold">{{ t(request, 'js.longmemeval_max_q') }}</label>
            <input type="number" class="form-control" id="trigger-lme-max" value="20" min="1" max="500">
            <div class="form-text">500 = full dataset (hours)</div>
          </div>
          <div class="alert alert-info small mb-0">
            <i class="bi bi-info-circle me-1"></i>
            {{ t(request, 'js.bench_type_longmemeval') }} — 7 memory types, multi-turn sessions
          </div>
        </div>

        {# ── GAIA fields ── #}
        <div id="trigger-gaia-fields" style="display:none">
          <div class="mb-3">
            <label class="form-label fw-semibold">Template</label>
            <input type="text" class="form-control" id="trigger-gaia-template" value="moe-m10-gremium-deep"
                   placeholder="{{ t(request, 'js.bench_template_placeholder') }}">
          </div>
          <div class="mb-3">
            <label class="form-label fw-semibold">{{ t(request, 'js.gaia_levels') }}</label>
            <div class="d-flex gap-3">
              <div class="form-check">
                <input class="form-check-input" type="checkbox" id="gaia-lvl1" value="1" checked>
                <label class="form-check-label" for="gaia-lvl1">Level 1 <small class="text-muted">(53 Q)</small></label>
              </div>
              <div class="form-check">
                <input class="form-check-input" type="checkbox" id="gaia-lvl2" value="2">
                <label class="form-check-label" for="gaia-lvl2">Level 2 <small class="text-muted">(86 Q)</small></label>
              </div>
              <div class="form-check">
                <input class="form-check-input" type="checkbox" id="gaia-lvl3" value="3">
                <label class="form-check-label" for="gaia-lvl3">Level 3 <small class="text-muted">(26 Q)</small></label>
              </div>
            </div>
          </div>
          <div class="mb-3">
            <label class="form-label fw-semibold">{{ t(request, 'js.gaia_max_per_level') }}</label>
            <input type="number" class="form-control" id="trigger-gaia-max" value="10" min="1" max="165">
            <div class="form-text">0 = all questions in level</div>
          </div>
          <div class="alert alert-info small mb-0">
            <i class="bi bi-info-circle me-1"></i>
            {{ t(request, 'js.bench_type_gaia') }} — requires HF_TOKEN in benchmarks/.env
          </div>
        </div>
      </div>
      <div class="modal-footer">
        <button class="btn btn-secondary" data-bs-dismiss="modal">{{ t(request, 'btn.cancel') }}</button>
        <button class="btn btn-primary" id="btn-confirm-trigger" onclick="triggerAny()" disabled>
          <i class="bi bi-play-fill me-1"></i>{{ t(request, 'js.start_now') }}
        </button>
      </div>
    </div>
  </div>
</div>

{% endblock %}


{% block scripts %}
<script>
const CSRF = {{ csrf_token | tojson }};
let _autoRefresh = true;
let _logRunId    = null;

const _t = {
  loading:             "{{ t(request, 'js.loading') }}",
  template:            "{{ t(request, 'table.template') }}",
  started:             "{{ t(request, 'table.started') }}",
  noLog:               "{{ t(request, 'js.no_log_available') }}",
  loadError:           "{{ t(request, 'js.load_error') }}",
  noOvernightRuns:     "{{ t(request, 'js.no_overnight_runs') }}",
  noSingleRuns:        "{{ t(request, 'js.no_single_runs') }}",
  noBenchActive:       "{{ t(request, 'js.no_bench_active') }}",
  lastOvernightRun:    "{{ t(request, 'js.last_overnight_run') }}",
  running:             "{{ t(request, 'js.bench_is_running') }}",
  clearLockManual:     "{{ t(request, 'js.clear_lock_manual') }}",
  epochsLabel:         "{{ t(request, 'js.epochs') }}",
  epochsSummary:       "{{ t(request, 'js.epochs_summary') }}",
  noEvalData:          "{{ t(request, 'js.no_eval_data') }}",
  noData:              "{{ t(request, 'js.no_data') }}",
  noTestEntries:       "{{ t(request, 'js.no_test_entries') }}",
  totalDuration:       "{{ t(request, 'js.total_duration') }}",
  tests:               "{{ t(request, 'js.tests') }}",
  avgScore:            "{{ t(request, 'js.avg_score') }}",
  testId:              "{{ t(request, 'js.test_id') }}",
  category:            "{{ t(request, 'table.category') }}",
  typeLabel:           "{{ t(request, 'table.type') }}",
  score:               "{{ t(request, 'js.score') }}",
  tokens:              "{{ t(request, 'table.tokens') }}",
  duration:            "{{ t(request, 'lbl.duration') }}",
  statusLabel:         "{{ t(request, 'table.status') }}",
  statusError:         "{{ t(request, 'js.status_error') }}",
  statusTimeout:       "{{ t(request, 'js.status_timeout') }}",
  runId:               "{{ t(request, 'js.run_id') }}",
  timestamp:           "{{ t(request, 'portal.col_timestamp') }}",
  evalFile:            "{{ t(request, 'js.eval_file') }}",
  deleteLabel:         "{{ t(request, 'btn.delete') }}",
  confirmDeleteRun:    "{{ t(request, 'js.confirm_delete_run') }}",
  deleted:             "{{ t(request, 'js.deleted') }}",
  confirmClearLock:    "{{ t(request, 'js.confirm_clear_lock') }}",
  lockCleared:         "{{ t(request, 'js.lock_cleared') }}",
  benchStarted:        "{{ t(request, 'js.bench_started') }}",
  triggerWritten:      "{{ t(request, 'js.trigger_written') }}",
  fillAllFields:       "{{ t(request, 'js.fill_all_fields') }}",
  starting:            "{{ t(request, 'js.starting') }}",
  startNow:            "{{ t(request, 'js.start_now') }}",
  checking:            "{{ t(request, 'js.checking') }}",
  prefightReady:       "{{ t(request, 'js.preflight_ready') }}",
  preflightFail:       "{{ t(request, 'js.preflight_fail') }}",
  scoreDetails:        "{{ t(request, 'js.score_details') }}",
  turn:                "{{ t(request, 'js.turn') }}",
  report:              "{{ t(request, 'js.report') }}",
  epoche:              "{{ t(request, 'js.epoch') }}",
  epochsOverview:      "{{ t(request, 'js.epochs_overview') }}",
  graphEntitiesDelta:  "{{ t(request, 'js.graph_entities_delta') }}",
  cacheHitRate:        "{{ t(request, 'js.cache_hit_rate') }}",
  compressions:        "{{ t(request, 'js.compressions') }}",
  lockDetail:          "{{ t(request, 'js.lock_detail') }}",
  noGaiaRuns:          "{{ t(request, 'js.no_gaia_runs') }}",
  gaiaStarted:         "{{ t(request, 'js.gaia_started') }}",
  gaiaQuestion:        "{{ t(request, 'js.gaia_question') }}",
  gaiaExpected:        "{{ t(request, 'js.gaia_expected') }}",
  gaiaAnswer:          "{{ t(request, 'js.gaia_answer') }}",
  gaiaCorrect:         "{{ t(request, 'js.gaia_correct') }}",
  gaiaLevel:           "{{ t(request, 'js.gaia_level') }}",
  overall:             "{{ t(request, 'js.overall') }}",
  correctOf:           "{{ t(request, 'js.correct_of') }}",
  noLmeRuns:           "{{ t(request, 'js.no_longmemeval_runs') }}",
  lmeStarted:          "{{ t(request, 'js.longmemeval_started') }}",
  saAddTemplate:       "{{ t(request, 'lbl.sa_add_template') }}",
  saAdded:             "{{ t(request, 'js.sa_template_added') }}",
  saRemoved:           "{{ t(request, 'js.sa_template_removed') }}",
  error:               "{{ t(request, 'js.error') }}",
};

// ── Helpers ──────────────────────────────────────────────────────────────────

function scoreBadge(score) {
  if (score === null || score === undefined) return '<span class="text-muted small">–</span>';
  const cls = score >= 7 ? 'score-high' : score >= 5 ? 'score-mid' : 'score-low';
  return `<span class="score-badge ${cls}">${score.toFixed(1)}</span>`;
}

function fmtTs(ts) {
  if (!ts) return '–';
  // Format: 20260417-024626 → 2026-04-17 02:46
  const m = ts.match(/^(\d{4})(\d{2})(\d{2})-(\d{2})(\d{2})/);
  return m ? `${m[1]}-${m[2]}-${m[3]} ${m[4]}:${m[5]}` : ts;
}

async function apiFetch(url, opts = {}) {
  const r = await fetch(url, {
    headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': CSRF },
    ...opts
  });
  if (!r.ok) throw new Error(`HTTP ${r.status}`);
  return r.json();
}

// ── Status ───────────────────────────────────────────────────────────────────

async function refreshStatus() {
  try {
    const s = await apiFetch('/api/benchmarks/status');
    const el = document.getElementById('live-status-content');
    if (s.running) {
      document.getElementById('bench-lock-banner').style.display = 'flex'; // inline style beats !important
      document.getElementById('bench-lock-detail').textContent =
        `${_t.template}: ${s.template || '?'} | ${_t.started}: ${s.started_at ? new Date(s.started_at).toLocaleTimeString() : '?'}`;
      el.innerHTML = `
        <div class="d-flex align-items-center mb-2">
          <span class="bench-live-dot"></span>
          <strong class="text-success">${_t.running}</strong>
        </div>
        <div class="small mb-1"><i class="bi bi-collection me-1"></i><strong>${_t.template}:</strong> ${s.template || '–'}</div>
        <div class="small mb-1"><i class="bi bi-database me-1"></i><strong>${_t.runId}:</strong> ${s.run_id || '–'}</div>
        <div class="small mb-2"><i class="bi bi-clock me-1"></i><strong>${_t.started}:</strong> ${s.started_at ? new Date(s.started_at).toLocaleString() : '–'}</div>
        <button class="btn btn-sm btn-outline-danger mt-1" onclick="clearLock()">
          <i class="bi bi-x-circle me-1"></i>${_t.clearLockManual}
        </button>`;
      _logRunId = s.latest_overnight_run;
    } else {
      document.getElementById('bench-lock-banner').style.display = 'none';
      el.innerHTML = `
        <div class="d-flex align-items-center mb-2">
          <i class="bi bi-circle me-2 text-secondary"></i>
          <strong class="text-secondary">${_t.noBenchActive}</strong>
        </div>
        <div class="small text-muted">${_t.lastOvernightRun}:<br><code>${s.latest_overnight_run || '–'}</code></div>`;
      _logRunId = s.latest_overnight_run;
    }
  } catch (e) {
    document.getElementById('live-status-content').innerHTML =
      `<div class="text-danger small"><i class="bi bi-exclamation-triangle me-1"></i>${e.message}</div>`;
  }
}

// ── Live Log ─────────────────────────────────────────────────────────────────

async function refreshLiveLog() {
  const url = _logRunId ? `/api/benchmarks/live-log?run_id=${encodeURIComponent(_logRunId)}&lines=100`
                        : '/api/benchmarks/live-log?lines=100';
  try {
    const d = await apiFetch(url);
    const box = document.getElementById('live-log-box');
    const fileEl = document.getElementById('live-log-file');
    if (fileEl) fileEl.textContent = d.log_file ? `(${d.log_file})` : '';
    if (!d.lines || d.lines.length === 0) {
      box.innerHTML = `<span class="text-muted">${_t.noLog}</span>`;
      return;
    }
    box.innerHTML = d.lines.map(l => {
      let cls = '';
      if (l.match(/✓|OK|PASS/))  cls = 'log-line-ok';
      if (l.match(/✗|FAIL|ERROR|TIMEOUT/)) cls = 'log-line-fail';
      if (l.match(/WARN|RETRY|heal/i)) cls = 'log-line-warn';
      return `<div class="${cls}">${escHtml(l)}</div>`;
    }).join('');
    const auto = document.getElementById('autoScrollToggle');
    if (auto && auto.checked) box.scrollTop = box.scrollHeight;
  } catch (e) {
    document.getElementById('live-log-box').innerHTML =
      `<div class="log-line-fail">${_t.loadError}: ${escHtml(e.message)}</div>`;
  }
}

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

// ── Overnight list ───────────────────────────────────────────────────────────

async function refreshOvernightList() {
  const el = document.getElementById('overnight-list-content');
  try {
    const d = await apiFetch('/api/benchmarks/list');
    const runs = d.overnight;
    if (!runs || runs.length === 0) {
      el.innerHTML = `<div class="text-muted small">${_t.noOvernightRuns}</div>`;
      return;
    }
    let rows = runs.map(r => {
      const ts = fmtTs(r.started_at);
      const epochs = r.epochs_done > 0 ? `${r.epochs_done} ${_t.epochsLabel}` : '–';
      const report = r.has_report ? `<span class="badge bg-success">Report</span>` : '';
      const isEmpty = r.epochs_done === 0 && !r.has_report;
      return `<tr class="${isEmpty ? 'table-warning' : ''}">
        <td class="epoch-row" style="cursor:pointer" onclick="showResult('${escHtml(r.run_id)}')">
          <code class="small">${escHtml(r.run_id)}</code>
        </td>
        <td class="small">${ts}</td>
        <td class="small">${epochs}</td>
        <td>${report}</td>
        <td class="small text-muted" style="max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${escHtml(r.summary.split('\n')[0])}</td>
        <td>
          <button class="btn btn-sm btn-outline-danger py-0 px-1"
                  onclick="deleteRun('${escHtml(r.run_id)}', 'overnight')"
                  title="${_t.deleteLabel}">
            <i class="bi bi-trash3"></i>
          </button>
        </td>
      </tr>`;
    }).join('');
    el.innerHTML = `<div class="table-responsive"><table class="table table-sm table-hover align-middle" data-sortable>
      <thead><tr>
        <th>${_t.runId}</th><th>${_t.started}</th><th>${_t.epochsLabel}</th><th data-nosort>${_t.report}</th><th data-nosort>Summary</th><th data-nosort></th>
      </tr></thead>
      <tbody>${rows}</tbody>
    </table></div>`;
    el.querySelectorAll('table[data-sortable]').forEach(t => window.moeSort?.init(t));
  } catch (e) {
    el.innerHTML = `<div class="text-danger small">${_t.loadError}: ${escHtml(e.message)}</div>`;
  }
}

// ── Single runs ──────────────────────────────────────────────────────────────

async function refreshSingleList() {
  const el = document.getElementById('single-list-content');
  try {
    const d = await apiFetch('/api/benchmarks/list');
    const runs = d.single;
    if (!runs || runs.length === 0) {
      el.innerHTML = `<div class="text-muted small">${_t.noSingleRuns}</div>`;
      return;
    }
    let rows = runs.map(r => {
      const ts = fmtTs(r.timestamp);
      const isEmpty = r.avg_score == null && !r.eval_file;
      return `<tr class="${isEmpty ? 'table-warning' : ''}">
        <td class="epoch-row small" style="cursor:pointer" onclick="showResult('${escHtml(r.run_id)}')">
          <code>${escHtml(r.template || r.run_id)}</code>
        </td>
        <td class="small">${ts}</td>
        <td>${scoreBadge(r.avg_score)}</td>
        <td class="small text-muted">${escHtml(r.eval_file || '–')}</td>
        <td>
          <button class="btn btn-sm btn-outline-danger py-0 px-1"
                  onclick="deleteRun('${escHtml(r.run_id)}', 'single')"
                  title="${_t.deleteLabel}">
            <i class="bi bi-trash3"></i>
          </button>
        </td>
      </tr>`;
    }).join('');
    el.innerHTML = `<div class="table-responsive"><table class="table table-sm table-hover align-middle" data-sortable>
      <thead><tr>
        <th>${_t.template}</th><th>${_t.timestamp}</th><th>Ø ${_t.score}</th><th data-nosort>${_t.evalFile}</th><th data-nosort></th>
      </tr></thead>
      <tbody>${rows}</tbody>
    </table></div>`;
    el.querySelectorAll('table[data-sortable]').forEach(t => window.moeSort?.init(t));
  } catch (e) {
    el.innerHTML = `<div class="text-danger small">${_t.loadError}: ${escHtml(e.message)}</div>`;
  }
}

// ── Result Detail Modal ───────────────────────────────────────────────────────

async function showResult(runId) {
  document.getElementById('result-modal-title').textContent = runId;
  document.getElementById('result-modal-body').innerHTML = `<div class="text-muted">${_t.loading}</div>`;
  const modal = new bootstrap.Modal(document.getElementById('resultModal'));
  modal.show();
  try {
    const d = await apiFetch(`/api/benchmarks/result/${encodeURIComponent(runId)}`);
    let html = '';
    if (d.type === 'overnight') {
      if (d.summary) {
        html += `<pre class="bg-light p-2 rounded small mb-3">${escHtml(d.summary)}</pre>`;
      }
      if (d.report && d.report.per_epoch) {
        html += `<h6>${_t.epochsOverview}</h6>`;
        html += `<div class="table-responsive"><table class="table table-sm table-bordered align-middle" data-sortable>
          <thead><tr>
            <th>${_t.epoche}</th><th>Ø ${_t.score}</th><th>${_t.tokens}</th><th>${_t.duration} (s)</th>
            <th>${_t.graphEntitiesDelta}</th><th>${_t.cacheHitRate}</th><th>${_t.compressions}</th>
          </tr></thead><tbody>`;
        for (const ep of d.report.per_epoch) {
          html += `<tr>
            <td>${ep.epoch}</td>
            <td>${scoreBadge(ep.avg_score)}</td>
            <td>${ep.total_tokens != null ? ep.total_tokens.toLocaleString() : '–'}</td>
            <td>${ep.total_duration_s != null ? ep.total_duration_s : '–'}</td>
            <td>${ep.graph_entities_delta != null ? ep.graph_entities_delta : '–'}</td>
            <td>${ep.cache_hit_rate != null ? (ep.cache_hit_rate*100).toFixed(1)+'%' : '–'}</td>
            <td>${ep.compressions != null ? ep.compressions : '–'}</td>
          </tr>`;
        }
        html += '</tbody></table></div>';
      } else if (d.epochs && d.epochs.length > 0) {
        html += `<h6>${_t.epochsSummary}</h6>`;
        html += `<div class="table-responsive"><table class="table table-sm table-bordered align-middle" data-sortable>
          <thead><tr><th>${_t.epoche}</th><th>Ø ${_t.score}</th><th>${_t.tests}</th></tr></thead><tbody>`;
        for (const ep of d.epochs) {
          html += `<tr><td>${ep.epoch}</td><td>${scoreBadge(ep.avg_score)}</td><td>${ep.tests}</td></tr>`;
        }
        html += '</tbody></table></div>';
      } else {
        html += `<div class="text-muted small">${_t.noEvalData}</div>`;
      }
    } else if (d.type === 'single') {
      // Meta info
      if (d.meta && Object.keys(d.meta).length) {
        const m = d.meta;
        html += `<div class="d-flex flex-wrap gap-3 mb-3 small text-muted">
          ${m.template ? `<span><i class="bi bi-collection me-1"></i><strong>${escHtml(m.template)}</strong></span>` : ''}
          ${m.timestamp ? `<span><i class="bi bi-clock me-1"></i>${fmtTs(m.timestamp)}</span>` : ''}
          ${m.dataset ? `<span><i class="bi bi-database me-1"></i>${escHtml(m.dataset)}</span>` : ''}
          ${m.api_base ? `<span><i class="bi bi-hdd-network me-1"></i>${escHtml(m.api_base)}</span>` : ''}
        </div>`;
      }

      // Build a merged view: run results + eval scores
      const runResults = d.results || [];
      const evalMap = {};
      if (d.eval) {
        for (const e of d.eval) evalMap[e.test_id] = e;
      }

      if (runResults.length === 0) {
        html += `<div class="text-muted small">${_t.noTestEntries}</div>`;
      } else {
        // Summary stats
        const evalScores = Object.values(evalMap).map(e => e.score).filter(s => s != null);
        const avgScore = evalScores.length
          ? evalScores.reduce((a,b) => a+b, 0) / evalScores.length : null;
        const totalTokens = runResults.reduce((sum, r) =>
          sum + r.turns.reduce((s, t) => s + (t.completion_tokens || 0), 0), 0);
        const totalDur = runResults.reduce((sum, r) =>
          sum + r.turns.reduce((s, t) => s + (t.wall_clock_s || 0), 0), 0);

        html += `<div class="row g-2 mb-3">
          <div class="col-auto"><div class="border rounded px-3 py-2 text-center">
            <div class="fw-bold">${avgScore != null ? scoreBadge(avgScore) : '–'}</div>
            <div class="small text-muted">Ø ${_t.score}</div>
          </div></div>
          <div class="col-auto"><div class="border rounded px-3 py-2 text-center">
            <div class="fw-bold">${runResults.length}</div>
            <div class="small text-muted">${_t.tests}</div>
          </div></div>
          <div class="col-auto"><div class="border rounded px-3 py-2 text-center">
            <div class="fw-bold">${totalTokens.toLocaleString()}</div>
            <div class="small text-muted">${_t.tokens}</div>
          </div></div>
          <div class="col-auto"><div class="border rounded px-3 py-2 text-center">
            <div class="fw-bold">${Math.round(totalDur)}s</div>
            <div class="small text-muted">${_t.totalDuration}</div>
          </div></div>
        </div>`;

        html += `<div class="table-responsive"><table class="table table-sm table-hover align-middle" style="font-size:.83rem" data-sortable>
          <thead><tr>
            <th>${_t.testId}</th><th>${_t.category}</th><th>${_t.typeLabel}</th>
            <th>${_t.score}</th><th>${_t.tokens}</th><th>${_t.duration}</th><th>${_t.statusLabel}</th>
          </tr></thead><tbody>`;

        for (const r of runResults) {
          const ev = evalMap[r.test_id] || {};
          const score = ev.score != null ? ev.score : (r.score != null ? r.score : null);
          const tokens = r.turns.reduce((s, t) => s + (t.completion_tokens || 0), 0);
          const dur = r.turns.reduce((s, t) => s + (t.wall_clock_s || 0), 0);
          const hasError = r.turns.some(t => t.error);
          const statusBadge = hasError
            ? `<span class="badge bg-danger">${_t.statusError}</span>`
            : tokens === 0
              ? `<span class="badge bg-warning text-dark">${_t.statusTimeout}</span>`
              : '<span class="badge bg-success">OK</span>';

          // Score detail tooltip
          let scoreDetail = '';
          if (ev.score_details) {
            const det = ev.score_details;
            const parts = [];
            if (det.deterministic) parts.push(`Det: ${det.deterministic.score?.toFixed(1)}`);
            if (det.llm_judge) parts.push(`LLM: ${det.llm_judge.score?.toFixed(1)}`);
            scoreDetail = parts.join(' | ');
          }

          html += `<tr data-bs-toggle="collapse" data-bs-target="#detail-${escHtml(r.test_id).replace(/[^a-z0-9]/gi,'_')}"
                      style="cursor:pointer">
            <td><code class="small">${escHtml(r.test_id || '–')}</code></td>
            <td><span class="badge bg-secondary">${escHtml(r.category || '–')}</span></td>
            <td class="text-muted">${escHtml(r.test_type || '–')}</td>
            <td>${scoreBadge(score)}${scoreDetail ? `<span class="text-muted ms-1" style="font-size:.72rem">${escHtml(scoreDetail)}</span>` : ''}</td>
            <td>${tokens.toLocaleString()}</td>
            <td>${Math.round(dur)}s</td>
            <td>${statusBadge}</td>
          </tr>
          <tr class="collapse" id="detail-${escHtml(r.test_id).replace(/[^a-z0-9]/gi,'_')}">
            <td colspan="7" class="p-0">
              <div class="bg-light p-3" style="font-size:.8rem">
                ${ev.score_details ? `<div class="mb-2"><strong>${_t.scoreDetails}:</strong> <code>${escHtml(JSON.stringify(ev.score_details, null, 1).substring(0,300))}</code></div>` : ''}
                ${r.turns.map(t => `
                  <div class="mb-2 border-start border-2 ps-2 ${t.error ? 'border-danger' : 'border-success'}">
                    <div class="fw-semibold text-muted">${_t.turn} ${t.turn} — ${Math.round(t.wall_clock_s || 0)}s — ${(t.completion_tokens||0)} ${_t.tokens}</div>
                    ${t.error ? `<div class="text-danger small">Error: ${escHtml(t.error)}</div>` : ''}
                    <div class="small mt-1" style="white-space:pre-wrap;max-height:120px;overflow:auto">${escHtml((t.response||'').substring(0,400))}${(t.response||'').length > 400 ? '…' : ''}</div>
                  </div>`).join('')}
              </div>
            </td>
          </tr>`;
        }
        html += '</tbody></table></div>';
      }
    }
    const modalBody = document.getElementById('result-modal-body');
    modalBody.innerHTML = html || `<div class="text-muted">${_t.noData}</div>`;
    modalBody.querySelectorAll('table[data-sortable]').forEach(t => window.moeSort?.init(t));
  } catch (e) {
    document.getElementById('result-modal-body').innerHTML =
      `<div class="text-danger">${_t.loadError}: ${escHtml(e.message)}</div>`;
  }
}

// ── Pre-flight ───────────────────────────────────────────────────────────────

async function runPreflight() {
  const el = document.getElementById('preflight-result');
  el.innerHTML = `<span class="text-muted">${_t.checking}…</span>`;
  try {
    const d = await apiFetch('/api/benchmarks/preflight', { method: 'POST', body: '{}' });
    const rows = d.checks.map(c => {
      const icon = c.ok === true
        ? '<i class="bi bi-check-circle-fill check-ok"></i>'
        : c.ok === false
          ? '<i class="bi bi-x-circle-fill check-fail"></i>'
          : '<i class="bi bi-dash-circle check-unknown"></i>';
      return `<div class="d-flex align-items-start gap-2 mb-1">
        ${icon}
        <span class="small"><strong>${escHtml(c.name)}</strong>
          <span class="text-muted ms-1">${escHtml(c.detail)}</span></span>
      </div>`;
    }).join('');
    const ready = d.ready;
    const trigger = document.getElementById('btn-confirm-trigger');
    if (trigger) trigger.disabled = !ready;
    el.innerHTML = rows + (ready
      ? `<div class="alert alert-success mt-2 mb-0 py-1 small">✓ ${_t.prefightReady}</div>`
      : `<div class="alert alert-warning mt-2 mb-0 py-1 small">⚠ ${_t.preflightFail}</div>`);
  } catch (e) {
    el.innerHTML = `<div class="text-danger small">${_t.loadError}: ${escHtml(e.message)}</div>`;
  }
}

// ── Trigger ──────────────────────────────────────────────────────────────────

async function triggerBenchmark() {
  const template = document.getElementById('trigger-template').value.trim();
  const dataset  = document.getElementById('trigger-dataset').value;
  const epochs   = parseInt(document.getElementById('trigger-epochs').value, 10);
  if (!template || !dataset || isNaN(epochs)) {
    alert(_t.fillAllFields);
    return;
  }
  const btn = document.getElementById('btn-confirm-trigger');
  btn.disabled = true;
  btn.innerHTML = `<span class="spinner-border spinner-border-sm me-1"></span>${_t.starting}…`;
  try {
    const d = await apiFetch('/api/benchmarks/trigger', {
      method: 'POST',
      body: JSON.stringify({ template, dataset, epochs }),
    });
    bootstrap.Modal.getInstance(document.getElementById('triggerModal')).hide();
    if (d.status === 'started') {
      showToast(`${_t.benchStarted} (PID ${d.pid})`, 'success');
    } else {
      showToast(_t.triggerWritten, 'info');
    }
    await refreshAll();
  } catch (e) {
    showToast(`Error: ${e.message}`, 'danger');
  } finally {
    btn.disabled = false;
    btn.innerHTML = `<i class="bi bi-play-fill me-1"></i>${_t.startNow}`;
  }
}

// ── Bench type toggle ────────────────────────────────────────────────────────

function onBenchTypeChange() {
  const t = document.getElementById('trigger-bench-type').value;
  document.getElementById('trigger-overnight-fields').style.display    = t === 'overnight'    ? '' : 'none';
  document.getElementById('trigger-gaia-fields').style.display         = t === 'gaia'         ? '' : 'none';
  document.getElementById('trigger-longmemeval-fields').style.display  = t === 'longmemeval'  ? '' : 'none';
  const btn = document.getElementById('btn-confirm-trigger');
  btn.disabled = (t === 'overnight');  // overnight requires preflight; others can start directly
}

// ── Unified trigger ──────────────────────────────────────────────────────────

async function triggerAny() {
  const type = document.getElementById('trigger-bench-type').value;
  if (type === 'gaia') await triggerGaia();
  else if (type === 'longmemeval') await triggerLongMemEval();
  else await triggerBenchmark();
}

// ── GAIA trigger ─────────────────────────────────────────────────────────────

async function triggerGaia() {
  const template = document.getElementById('trigger-gaia-template').value.trim();
  const maxPerLvl = parseInt(document.getElementById('trigger-gaia-max').value, 10);
  const levels = [1,2,3].filter(n => document.getElementById(`gaia-lvl${n}`)?.checked);
  if (!template || !levels.length || isNaN(maxPerLvl)) {
    alert(_t.fillAllFields);
    return;
  }
  const btn = document.getElementById('btn-confirm-trigger');
  btn.disabled = true;
  btn.innerHTML = `<span class="spinner-border spinner-border-sm me-1"></span>${_t.starting}…`;
  try {
    const d = await apiFetch('/api/benchmarks/trigger-gaia', {
      method: 'POST',
      body: JSON.stringify({ template, levels, max_per_level: maxPerLvl }),
    });
    bootstrap.Modal.getInstance(document.getElementById('triggerModal')).hide();
    showToast(`${_t.gaiaStarted} (PID ${d.pid})`, 'success');
    await refreshAll();
  } catch (e) {
    showToast(`Error: ${e.message}`, 'danger');
    btn.disabled = false;
  } finally {
    btn.innerHTML = `<i class="bi bi-play-fill me-1"></i>${_t.startNow}`;
  }
}

// ── GAIA list ────────────────────────────────────────────────────────────────

async function refreshGaiaList() {
  const el = document.getElementById('gaia-list-content');
  if (!el) return;
  try {
    const data = await apiFetch('/api/benchmarks/list');
    const runs = data.gaia || [];
    if (!runs.length) {
      el.innerHTML = `<div class="text-muted small">${_t.noGaiaRuns}</div>`;
      return;
    }
    const rows = runs.map(r => {
      const pct = r.overall_pct ?? 0;
      const cls = pct >= 50 ? 'score-high' : pct >= 25 ? 'score-mid' : 'score-low';
      const lvls = Object.entries(r.by_level || {}).map(([l, s]) =>
        `<span class="badge bg-secondary me-1">L${l}: ${s.correct}/${s.total}</span>`
      ).join('');
      return `<tr>
        <td><code class="small">${escHtml(r.timestamp || '–')}</code></td>
        <td><span class="badge bg-dark text-light">${escHtml(r.template)}</span></td>
        <td>${lvls}</td>
        <td><span class="score-badge ${cls}">${pct.toFixed(1)}%</span>
            <small class="text-muted ms-1">${r.total_correct} ${_t.correctOf} ${r.total_questions}</small></td>
        <td>
          <button class="btn btn-sm btn-outline-secondary" onclick="showGaiaResult('${escHtml(r.filename)}')">
            <i class="bi bi-eye me-1"></i>${_t.scoreDetails}
          </button>
        </td>
      </tr>`;
    }).join('');
    el.innerHTML = `
      <table class="table table-sm table-hover align-middle" data-sortable>
        <thead><tr>
          <th>${_t.timestamp}</th>
          <th>Template</th>
          <th data-nosort>${_t.gaiaLevel}</th>
          <th>${_t.overall}</th>
          <th data-nosort></th>
        </tr></thead>
        <tbody>${rows}</tbody>
      </table>`;
    el.querySelectorAll('table[data-sortable]').forEach(t => window.moeSort?.init(t));
  } catch (e) {
    el.innerHTML = `<div class="text-danger small">${_t.loadError}: ${escHtml(e.message)}</div>`;
  }
}

// ── GAIA result detail ───────────────────────────────────────────────────────

async function showGaiaResult(filename) {
  document.getElementById('result-modal-title').textContent = filename;
  document.getElementById('result-modal-body').innerHTML = `<div class="text-muted">${_t.loading}</div>`;
  const modal = new bootstrap.Modal(document.getElementById('resultModal'));
  modal.show();
  try {
    const d = await apiFetch(`/api/benchmarks/result-gaia/${encodeURIComponent(filename)}`);
    const results = d.results || [];
    const rows = results.map(r => {
      const icon = r.correct
        ? '<i class="bi bi-check-circle-fill check-ok"></i>'
        : '<i class="bi bi-x-circle-fill check-fail"></i>';
      const q = (r.question || '').substring(0, 120) + ((r.question || '').length > 120 ? '…' : '');
      return `<tr>
        <td class="text-center">${icon}</td>
        <td class="text-center"><span class="badge bg-secondary">L${r.level}</span></td>
        <td class="small">${escHtml(q)}</td>
        <td class="small text-success">${escHtml(r.expected_answer || '–')}</td>
        <td class="small text-muted">${escHtml((r.model_answer || '').substring(0, 100))}${(r.model_answer || '').length > 100 ? '…' : ''}</td>
        <td class="text-muted small">${r.duration_s ? r.duration_s.toFixed(1)+'s' : '–'}</td>
      </tr>`;
    }).join('');
    const lvlSummary = Object.entries(d.by_level || {}).map(([l,s]) =>
      `<span class="badge bg-dark me-2">Level ${l}: ${s.correct}/${s.total} = ${s.pct}%</span>`
    ).join('');
    document.getElementById('result-modal-body').innerHTML = `
      <div class="mb-3">
        <strong>${_t.overall}:</strong>
        <span class="score-badge ${d.overall_pct >= 50 ? 'score-high' : d.overall_pct >= 25 ? 'score-mid' : 'score-low'} ms-2">
          ${d.overall_pct}%
        </span>
        <span class="text-muted ms-2 small">${d.total_correct} ${_t.correctOf} ${d.total_questions}</span>
      </div>
      <div class="mb-3">${lvlSummary}</div>
      <div class="table-responsive">
        <table class="table table-sm table-hover align-middle" data-sortable>
          <thead><tr>
            <th>${_t.gaiaCorrect}</th>
            <th>${_t.gaiaLevel}</th>
            <th data-nosort>${_t.gaiaQuestion}</th>
            <th data-nosort>${_t.gaiaExpected}</th>
            <th data-nosort>${_t.gaiaAnswer}</th>
            <th>${_t.duration}</th>
          </tr></thead>
          <tbody>${rows}</tbody>
        </table>
      </div>`;
    document.getElementById('result-modal-body').querySelectorAll('table[data-sortable]').forEach(t => window.moeSort?.init(t));
  } catch (e) {
    document.getElementById('result-modal-body').innerHTML =
      `<div class="text-danger">${_t.loadError}: ${escHtml(e.message)}</div>`;
  }
}

// ── LongMemEval trigger ──────────────────────────────────────────────────────

async function triggerLongMemEval() {
  const template = document.getElementById('trigger-lme-template').value.trim();
  const maxQ = parseInt(document.getElementById('trigger-lme-max').value, 10);
  if (!template || isNaN(maxQ)) { alert(_t.fillAllFields); return; }
  const btn = document.getElementById('btn-confirm-trigger');
  btn.disabled = true;
  btn.innerHTML = `<span class="spinner-border spinner-border-sm me-1"></span>${_t.starting}…`;
  try {
    const d = await apiFetch('/api/benchmarks/trigger-longmemeval', {
      method: 'POST',
      body: JSON.stringify({ template, max_q: maxQ }),
    });
    bootstrap.Modal.getInstance(document.getElementById('triggerModal')).hide();
    showToast(`${_t.lmeStarted} (PID ${d.pid})`, 'success');
    await refreshAll();
  } catch (e) {
    showToast(`Error: ${e.message}`, 'danger');
    btn.disabled = false;
  } finally {
    btn.innerHTML = `<i class="bi bi-play-fill me-1"></i>${_t.startNow}`;
  }
}

// ── LongMemEval list ─────────────────────────────────────────────────────────

async function refreshLongMemEvalList() {
  const el = document.getElementById('longmemeval-list-content');
  if (!el) return;
  try {
    const data = await apiFetch('/api/benchmarks/list');
    const runs = data.longmemeval || [];
    if (!runs.length) {
      el.innerHTML = `<div class="text-muted small">${_t.noLmeRuns}</div>`;
      return;
    }
    const rows = runs.map(r => {
      const pct = r.average_score_pct ?? 0;
      const cls = pct >= 50 ? 'score-high' : pct >= 25 ? 'score-mid' : 'score-low';
      const types = Object.entries(r.by_type || {}).map(([tp, s]) =>
        `<span class="badge bg-secondary me-1" title="${escHtml(tp)}">${escHtml(tp.substring(0,12))}: ${s.passed}/${Object.keys(s).length > 0 ? (s.avg_pct+'%') : '?'}</span>`
      ).join('');
      return `<tr>
        <td><code class="small">${escHtml(r.timestamp || '–')}</code></td>
        <td><span class="badge bg-dark text-light">${escHtml(r.template)}</span></td>
        <td>${r.total_questions}</td>
        <td><span class="score-badge ${cls}">${pct.toFixed(1)}%</span></td>
        <td>
          <button class="btn btn-sm btn-outline-secondary" onclick="showLmeResult('${escHtml(r.filename)}')">
            <i class="bi bi-eye me-1"></i>${_t.scoreDetails}
          </button>
        </td>
      </tr>`;
    }).join('');
    el.innerHTML = `
      <table class="table table-sm table-hover align-middle" data-sortable>
        <thead><tr>
          <th>${_t.timestamp}</th>
          <th>Template</th>
          <th>${_t.tests}</th>
          <th>${_t.avgScore}</th>
          <th data-nosort></th>
        </tr></thead>
        <tbody>${rows}</tbody>
      </table>`;
    el.querySelectorAll('table[data-sortable]').forEach(t => window.moeSort?.init(t));
  } catch (e) {
    el.innerHTML = `<div class="text-danger small">${_t.loadError}: ${escHtml(e.message)}</div>`;
  }
}

// ── LongMemEval result detail ─────────────────────────────────────────────────

async function showLmeResult(filename) {
  document.getElementById('result-modal-title').textContent = filename;
  document.getElementById('result-modal-body').innerHTML = `<div class="text-muted">${_t.loading}</div>`;
  const modal = new bootstrap.Modal(document.getElementById('resultModal'));
  modal.show();
  try {
    const d = await apiFetch(`/api/benchmarks/result-longmemeval/${encodeURIComponent(filename)}`);
    const results = d.results || [];
    const rows = results.map(r => {
      const cls = r.score_pct >= 50 ? 'check-ok' : 'check-fail';
      const icon = r.score_pct >= 50
        ? '<i class="bi bi-check-circle-fill check-ok"></i>'
        : '<i class="bi bi-x-circle-fill check-fail"></i>';
      return `<tr>
        <td class="text-center">${icon}</td>
        <td class="small"><span class="badge bg-secondary">${escHtml(r.type || '–')}</span></td>
        <td class="text-center small">${r.num_turns || '–'}</td>
        <td class="small text-success">${escHtml((r.expected || []).join(', ').substring(0, 80))}</td>
        <td class="text-center"><span class="score-badge ${r.score_pct >= 50 ? 'score-high' : 'score-low'}">${r.score_pct}%</span></td>
        <td class="text-muted small">${r.wall_clock_s ? r.wall_clock_s.toFixed(1)+'s' : '–'}</td>
      </tr>`;
    }).join('');
    const typeSummary = Object.entries(d.by_type || {}).map(([tp, s]) =>
      `<span class="badge bg-dark me-2">${escHtml(tp)}: ${s.avg_pct}% avg (${s.passed} passed)</span>`
    ).join('');
    document.getElementById('result-modal-body').innerHTML = `
      <div class="mb-3">
        <strong>${_t.avgScore}:</strong>
        <span class="score-badge ${d.average_score_pct >= 50 ? 'score-high' : d.average_score_pct >= 25 ? 'score-mid' : 'score-low'} ms-2">
          ${d.average_score_pct}%
        </span>
        <span class="text-muted ms-2 small">${d.total_questions} ${_t.tests}</span>
      </div>
      <div class="mb-3">${typeSummary}</div>
      <div class="table-responsive">
        <table class="table table-sm table-hover align-middle" data-sortable>
          <thead><tr>
            <th>OK</th><th>Type</th><th>Turns</th><th data-nosort>${_t.gaiaExpected}</th><th>${_t.score}</th><th>${_t.duration}</th>
          </tr></thead>
          <tbody>${rows}</tbody>
        </table>
      </div>`;
    document.getElementById('result-modal-body').querySelectorAll('table[data-sortable]').forEach(t => window.moeSort?.init(t));
  } catch (e) {
    document.getElementById('result-modal-body').innerHTML =
      `<div class="text-danger">${_t.loadError}: ${escHtml(e.message)}</div>`;
  }
}

// ── Delete run ───────────────────────────────────────────────────────────────

async function deleteRun(runId, runType) {
  if (!confirm(`${_t.confirmDeleteRun} "${runId}"?`)) return;
  try {
    const d = await apiFetch(`/api/benchmarks/result/${encodeURIComponent(runId)}`, { method: 'DELETE' });
    showToast(`${_t.deleted}: ${d.deleted.join(', ')}`, 'success');
    await refreshOvernightList();
    await refreshSingleList();
  } catch (e) {
    showToast(`Error: ${e.message}`, 'danger');
  }
}

// ── Lock clear ───────────────────────────────────────────────────────────────

async function clearLock() {
  if (!confirm(_t.confirmClearLock)) return;
  try {
    await apiFetch('/api/benchmarks/lock', { method: 'DELETE' });
    showToast(_t.lockCleared, 'success');
    await refreshStatus();
  } catch (e) {
    showToast(`Error: ${e.message}`, 'danger');
  }
}

// ── Toast helper ─────────────────────────────────────────────────────────────

function showToast(msg, type = 'info') {
  const id = 'toast-' + Date.now();
  const html = `<div id="${id}" class="toast align-items-center text-bg-${type} border-0 show" role="alert">
    <div class="d-flex">
      <div class="toast-body">${escHtml(msg)}</div>
      <button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
    </div>
  </div>`;
  let container = document.getElementById('toast-container');
  if (!container) {
    container = document.createElement('div');
    container.id = 'toast-container';
    container.className = 'position-fixed bottom-0 end-0 p-3';
    container.style.zIndex = '9999';
    document.body.appendChild(container);
  }
  container.insertAdjacentHTML('beforeend', html);
  setTimeout(() => document.getElementById(id)?.remove(), 4000);
}

// ── Full refresh ─────────────────────────────────────────────────────────────

async function refreshAll() {
  await Promise.all([
    refreshStatus(),
    refreshLiveLog(),
    refreshOvernightList(),
    refreshSingleList(),
    refreshGaiaList(),
    refreshLongMemEvalList(),
  ]);
}

// ── Service Account ──────────────────────────────────────────────────────────

let _saData = null;

async function loadServiceAccount() {
  const msgEl = document.getElementById('sa-msg');
  const r = await fetch('/api/service-accounts/benchmarks');
  if (!r.ok) {
    if (msgEl) msgEl.textContent = 'Service Account nicht gefunden.';
    return;
  }
  _saData = await r.json();
  const prefixEl = document.getElementById('sa-key-prefix');
  if (prefixEl) prefixEl.textContent = _saData.key_prefix;

  const list = document.getElementById('sa-template-list');
  if (list) {
    list.innerHTML = _saData.granted_templates.length
      ? ''
      : '<span class="text-muted small">Keine Templates zugewiesen.</span>';
    for (const tmplId of _saData.granted_templates) {
      const name = _saData.all_templates.find(t => t.id === tmplId)?.name || tmplId;
      list.insertAdjacentHTML('beforeend', `
        <div class="col-auto">
          <span class="badge bg-secondary d-flex align-items-center gap-1" style="font-size:.8rem">
            ${name}
            <button class="btn-close btn-close-white btn-sm ms-1" style="font-size:.6rem"
              onclick="saRevokeTemplate('${tmplId}')" title="Entfernen"></button>
          </span>
        </div>`);
    }
  }

  const sel = document.getElementById('sa-add-select');
  if (sel) {
    sel.innerHTML = `<option value="">${_t.saAddTemplate}</option>`;
    for (const t of _saData.all_templates) {
      if (!_saData.granted_templates.includes(t.id)) {
        sel.insertAdjacentHTML('beforeend', `<option value="${t.id}">${t.name}</option>`);
      }
    }
  }
}

async function saGrantTemplate() {
  const tmplId = document.getElementById('sa-add-select')?.value;
  if (!tmplId) return;
  const r = await fetch('/api/service-accounts/benchmarks/templates', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({template_id: tmplId}),
  });
  const d = await r.json();
  const msgEl = document.getElementById('sa-msg');
  if (msgEl) msgEl.textContent = d.ok ? _t.saAdded.replace('{id}', tmplId) : `${_t.error}: ${d.detail}`;
  loadServiceAccount();
}

async function saRevokeTemplate(tmplId) {
  const r = await fetch(`/api/service-accounts/benchmarks/templates/${encodeURIComponent(tmplId)}`, {method: 'DELETE'});
  const d = await r.json();
  const msgEl = document.getElementById('sa-msg');
  if (msgEl) msgEl.textContent = d.ok ? _t.saRemoved.replace('{id}', tmplId) : `${_t.error}: ${d.detail}`;
  loadServiceAccount();
}

// ── Init ─────────────────────────────────────────────────────────────────────

refreshAll();
loadServiceAccount();

// Auto-refresh every 15 seconds for live tab
setInterval(async () => {
  const liveTab = document.querySelector('#tab-live.active');
  if (liveTab) {
    await refreshStatus();
    await refreshLiveLog();
  }
}, 15000);
</script>
{% endblock %}
