// ═══ INITIAL PROMPT ═══
const INITIAL_PROMPT = `Bạn là trợ lý tư vấn thời trang của Canifa. Hãy trả lời câu hỏi của khách hàng một cách thân thiện và chuyên nghiệp.

Hướng dẫn:
- Luôn chào hỏi thân thiện, xưng "bạn"
- Khi khách hỏi về sản phẩm, gọi tool data_retrieval_tool để tìm kiếm
- KHÔNG được bịa sản phẩm, giá, khuyến mãi không có thật
- Nếu khách chưa rõ nhu cầu, hỏi thêm để tư vấn chính xác
- Trả lời bằng tiếng Việt, ngôn ngữ tự nhiên`;

const DEFAULT_TESTS = [
  {id:1, input:"Tôi muốn mua áo đi làm, không biết chọn màu gì?", expect_keywords:["màu","công sở","phong cách"], expect_question:true},
  {id:2, input:"Shop có váy dự tiệc không?", expect_keywords:["dạ","có","màu"], expect_question:true},
  {id:3, input:"Ngân sách 500k mua được gì?", expect_keywords:["500","sản phẩm","gợi ý"], expect_question:false},
  {id:4, input:"Áo khoác nào hợp mùa đông?", expect_keywords:["ấm","chất liệu","kiểu"], expect_question:true},
  {id:5, input:"Tôi cao 1m55 nặng 50kg mặc size gì?", expect_keywords:["size","S","M"], expect_question:false},
];

// ═══ STATE ═══
let running = false, paused = false;
let promptVer = 1;
let currPrompt = INITIAL_PROMPT;
let promptHistory = [{v:1, prompt:INITIAL_PROMPT, avgScore:null, changes:[]}];
let testCases = [...DEFAULT_TESTS];
let results = {}; // id → {output, total, metrics, reasoning, issues, streaming}
let activeId = null, selectedId = null;
let scoreHistory = [];
let latestMet = null, prevMet = null;
let logs = [];
let iterations = 0;
let currentTab = 'tests';
let proposal = null;

// ═══ HELPERS ═══
function ts() { return new Date().toLocaleTimeString('vi-VN',{hour:'2-digit',minute:'2-digit',second:'2-digit'}) }
function pillClass(v) { return v >= 0.75 ? 'good' : v >= 0.5 ? 'mid' : 'bad' }
function pillColor(v) { return v >= 0.75 ? '#059669' : v >= 0.5 ? '#D97706' : '#DC2626' }
function sleep(ms) { return new Promise(r => setTimeout(r, ms)) }

// ═══ LOGGING ═══
function addLog(msg, type='info') {
  logs.push({msg, type, t:ts()});
  if (logs.length > 120) logs = logs.slice(-100);
  if (currentTab === 'log') renderLog();
}

// ═══ RENDER ═══
function renderTests() {
  const el = document.getElementById('tab-tests');
  el.innerHTML = testCases.map(tc => {
    const r = results[tc.id];
    const scored = r?.total != null;
    let cls = 'tc';
    if (activeId === tc.id) cls += ' active';
    else if (scored) cls += ' scored-' + pillClass(r.total);
    return `<div class="${cls}" onclick="selectTest(${tc.id})">
      <div style="display:flex;justify-content:space-between;align-items:flex-start;gap:8px">
        <p class="tc-input" style="flex:1;font-weight:${activeId===tc.id?600:400}">${tc.input}</p>
        <div style="display:flex;gap:4px;align-items:center;flex-shrink:0">
          ${scored ? `<span class="pill ${pillClass(r.total)}">${(r.total*100).toFixed(0)}%</span>` : ''}
          ${activeId===tc.id ? '<div style="width:8px;height:8px;border-radius:50%;background:#9A5D0B;animation:pulse 1.5s infinite"></div>' : ''}
        </div>
      </div>
      ${r?.output ? `<p style="font-size:11px;color:#92400E;opacity:.6;margin-top:4px;line-height:1.4;font-style:italic;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical">→ ${r.output.slice(0,80)}...</p>` : ''}
      <div style="display:flex;justify-content:space-between;align-items:center;margin-top:6px">
        <div>${tc.expect_keywords.map(k=>`<span class="tc-kw">${k}</span>`).join('')}${tc.expect_question?'<span class="tc-kw" style="background:rgba(180,83,9,.1);color:#9A5D0B">?</span>':''}</div>
        <div class="tc-actions">
          <button onclick="event.stopPropagation();deleteTC(${tc.id})">x</button>
        </div>
      </div>
    </div>`;
  }).join('') + `<button onclick="addNewTC()" style="width:100%;padding:8px;background:none;border:1.5px dashed rgba(165,142,112,.3);border-radius:9px;font-size:12px;color:#92400E;opacity:.5;transition:all .15s" onmouseover="this.style.opacity=1" onmouseout="this.style.opacity=.5">+ Add test case</button>`;
  document.getElementById('tc-count').textContent = testCases.length;
}

function renderVersions() {
  const el = document.getElementById('tab-versions');
  let html = '';
  if (scoreHistory.length >= 2) {
    html += `<div style="padding:12px;background:rgba(255,251,235,.5);border-radius:10px;border:1px solid rgba(165,142,112,.15);margin-bottom:10px">
      <div class="section-label" style="margin-bottom:8px">Score Trend</div>
      ${renderSparkSVG(scoreHistory, 270, 50)}
    </div>`;
  }
  [...promptHistory].reverse().forEach(h => {
    const isCur = h.v === promptVer;
    html += `<div class="ver ${isCur?'current':''}" onclick="${running?'':'restoreVersion('+h.v+')'}">
      <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:4px">
        <span style="font-size:12px;font-weight:700;color:${isCur?'#9A5D0B':'#374151'}">v${h.v}</span>
        <div style="display:flex;gap:6px;align-items:center">
          ${h.avgScore!=null?`<span class="pill ${pillClass(h.avgScore)}">${(h.avgScore*100).toFixed(0)}%</span>`:''}
          ${isCur?'<span style="font-size:9px;background:rgba(180,83,9,.12);color:#9A5D0B;padding:1px 7px;border-radius:100px;font-weight:700">CURRENT</span>':''}
        </div>
      </div>
      ${h.changes?.slice(0,2).map(c=>`<div style="font-size:10.5px;color:#92400E;opacity:.6;line-height:1.4">→ ${c.slice(0,55)}${c.length>55?'...':''}</div>`).join('')||''}
      ${h.v===1?'<div style="font-size:10.5px;color:rgba(165,142,112,.4)">Initial prompt</div>':''}
    </div>`;
  });
  el.innerHTML = html;
}

function renderLog() {
  const el = document.getElementById('tab-log');
  if (!logs.length) { el.innerHTML = '<p style="font-size:11px;color:rgba(165,142,112,.4);text-align:center;padding:20px 0">No logs yet</p>'; return; }
  const colors = {
    system:{bg:'rgba(255,251,235,.6)',dot:'#D97706',text:'#78350F'},
    run:{bg:'rgba(180,83,9,.05)',dot:'#B45309',text:'#92400E'},
    score:{bg:'rgba(236,253,245,.5)',dot:'#059669',text:'#065F46'},
    optimize:{bg:'rgba(245,243,255,.5)',dot:'#7C3AED',text:'#4C1D95'},
    pass:{bg:'rgba(236,253,245,.5)',dot:'#059669',text:'#065F46'},
    fail:{bg:'rgba(254,242,242,.5)',dot:'#EF4444',text:'#991B1B'},
    info:{bg:'rgba(255,251,235,.3)',dot:'#92400E',text:'#475569'}
  };
  el.innerHTML = logs.map(l => {
    const c = colors[l.type] || colors.info;
    return `<div class="log-item" style="background:${c.bg}"><div class="log-dot" style="background:${c.dot}"></div><div class="log-msg" style="color:${c.text}">${l.msg}</div><span class="log-time">${l.t}</span></div>`;
  }).join('');
  el.scrollTop = el.scrollHeight;
}

function renderDetail() {
  const el = document.getElementById('detail-area');
  if (!selectedId || !results[selectedId]) {
    el.innerHTML = '<div class="empty-state"><p>Click test case bên trái<br>để xem kết quả chi tiết</p></div>';
    return;
  }
  const r = results[selectedId];
  const tc = testCases.find(t => t.id === selectedId);
  el.innerHTML = `<div style="animation:fadeUp .2s ease">
    <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px">
      <p style="font-size:13px;font-weight:700;color:#18181B">Test #${selectedId} — Detail</p>
      <div style="display:flex;gap:8px;align-items:center">
        ${r.total!=null?`<span class="pill lg ${pillClass(r.total)}">${(r.total*100).toFixed(0)}%</span>`:''}
        <button onclick="selectedId=null;renderDetail()" style="font-size:13px;color:rgba(165,142,112,.4);background:none;border:none">x</button>
      </div>
    </div>
    <div class="detail-input">
      <div class="section-label">Input</div>
      <p style="font-size:13px;color:#92400E;line-height:1.5">${tc?.input||''}</p>
    </div>
    <div class="detail-output">
      <div class="section-label">Output ${r.streaming?'(streaming)':''}</div>
      <p style="font-size:13px;color:#18181B;line-height:1.7;white-space:pre-wrap" ${r.streaming?'class="streaming-dot"':''}>${r.output||' '}</p>
    </div>
    ${r.metrics?`<div class="detail-metrics">
      <div class="section-label">Metric Breakdown</div>
      ${Object.entries(r.metrics).map(([k,v])=>renderMetricRow(k,v)).join('')}
    </div>`:''}
    ${r.reasoning?`<div style="margin-top:10px;padding:10px 14px;background:rgba(255,251,235,.4);border:1px solid rgba(165,142,112,.15);border-radius:9px">
      <div class="section-label">Judge Reasoning</div>
      <p style="font-size:12px;color:#374151;line-height:1.5">${r.reasoning}</p>
      ${r.issues?.length?`<div style="margin-top:6px">${r.issues.map(i=>`<span style="display:inline-block;font-size:10px;background:rgba(220,38,38,.08);color:#DC2626;padding:2px 8px;border-radius:4px;margin:2px 2px 0 0">${i}</span>`).join('')}</div>`:''}
    </div>`:''}
  </div>`;
}

function renderMetricRow(label, v, prev) {
  const c = pillColor(v);
  const delta = prev !== undefined ? v - prev : null;
  return `<div class="mrow">
    <div class="mrow-top">
      <span class="mrow-label">${label}</span>
      <div style="display:flex;align-items:center;gap:4px">
        ${delta!==null&&Math.abs(delta)>.005?`<span class="mrow-delta" style="color:${delta>0?'#059669':'#DC2626'}">${delta>0?'↑':'↓'}${Math.abs(delta*100).toFixed(0)}</span>`:''}
        <span class="mrow-val" style="color:${c}">${(v*100).toFixed(0)}%</span>
      </div>
    </div>
    <div class="mrow-bar"><div class="mrow-fill" style="width:${v*100}%;background:${c}"></div></div>
  </div>`;
}

function renderMetrics() {
  const el = document.getElementById('metrics-area');
  if (!latestMet) { el.innerHTML = '<p style="font-size:11px;color:rgba(165,142,112,.4);text-align:center;padding:10px 0">No data yet</p>'; return; }
  el.innerHTML = Object.entries(latestMet).map(([k,v]) => renderMetricRow(k, v, prevMet?.[k])).join('');
}

function renderScoreGrid() {
  const el = document.getElementById('score-grid');
  el.innerHTML = testCases.map(tc => {
    const r = results[tc.id];
    const scored = r?.total != null;
    const sel = selectedId === tc.id;
    return `<div class="score-tile ${sel?'selected':''}" onclick="selectTest(${tc.id})">
      <div style="display:flex;justify-content:space-between;align-items:center">
        <span style="font-size:10px;color:#92400E;opacity:.6;font-weight:600">#${tc.id}</span>
        ${activeId===tc.id?'<div style="width:6px;height:6px;border-radius:50%;background:#9A5D0B;animation:pulse 1s infinite"></div>':''}
        ${scored&&!r.streaming?`<span style="font-size:13px;font-weight:800;color:${pillColor(r.total)}">${(r.total*100).toFixed(0)}%</span>`:''}
      </div>
      <p style="font-size:10.5px;color:#374151;margin-top:2px;line-height:1.35;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical">${tc.input.slice(0,40)}...</p>
    </div>`;
  }).join('');
}

function renderSparkSVG(scores, w, h) {
  if (scores.length < 2) return '<div class="spark-empty">no data</div>';
  const pad = 6;
  const pts = scores.map((v,i) => {
    const x = pad + (i/(scores.length-1))*(w-pad*2);
    const y = h - pad - v*(h-pad*2);
    return [x,y];
  });
  const last = scores[scores.length-1];
  const c = pillColor(last);
  return `<svg width="${w}" height="${h}">
    <defs><linearGradient id="sg" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="${c}" stop-opacity=".2"/><stop offset="100%" stop-color="${c}" stop-opacity=".03"/></linearGradient></defs>
    <polygon points="${[...pts,[w-pad,h-pad],[pad,h-pad]].map(p=>p.join(',')).join(' ')}" fill="url(#sg)"/>
    <polyline points="${pts.map(p=>p.join(',')).join(' ')}" fill="none" stroke="${c}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
    ${pts.map(([x,y],i)=>`<circle cx="${x}" cy="${y}" r="${i===pts.length-1?4:2.5}" fill="${c}"/>`).join('')}
  </svg>`;
}

function renderSparkline() {
  const el = document.getElementById('spark-container');
  el.innerHTML = scoreHistory.length >= 2 ? renderSparkSVG(scoreHistory, 260, 50) : '<div class="spark-empty">Waiting...</div>';
  const now = scoreHistory.length ? scoreHistory[scoreHistory.length-1] : null;
  document.getElementById('spark-now').textContent = now != null ? (now*100).toFixed(1)+'%' : '—';
  document.getElementById('spark-now').style.color = now != null ? pillColor(now) : '#92400E';
  document.getElementById('spark-start').textContent = scoreHistory.length ? `Round 1: ${(scoreHistory[0]*100).toFixed(0)}%` : '';
  const delta = document.getElementById('spark-delta');
  if (scoreHistory.length > 1) {
    const d = scoreHistory[scoreHistory.length-1] - scoreHistory[scoreHistory.length-2];
    delta.style.color = d > 0 ? '#059669' : '#DC2626';
    delta.textContent = `${d>0?'↑':'↓'} ${(Math.abs(d)*100).toFixed(1)}pp`;
  } else { delta.textContent = ''; }
}

function updateTopbar() {
  document.getElementById('stat-round').textContent = iterations || '—';
  document.getElementById('stat-ver').textContent = 'v'+promptVer;
  const s = scoreHistory.length ? scoreHistory[scoreHistory.length-1] : null;
  document.getElementById('stat-score').textContent = s != null ? (s*100).toFixed(1)+'%' : '—';
  document.getElementById('stat-score').style.color = s != null ? pillColor(s) : '#9A5D0B';

  const dot = document.getElementById('status-dot');
  const txt = document.getElementById('status-text');
  if (running && !paused) { dot.style.background='#059669'; dot.style.animation='pulse 1.5s infinite'; txt.textContent='Running...'; txt.style.color='#059669'; }
  else if (running && paused) { dot.style.background='#D97706'; dot.style.animation='none'; txt.textContent='Paused'; txt.style.color='#D97706'; }
  else if (iterations > 0) { dot.style.background='#059669'; dot.style.animation='none'; txt.textContent='Done'; txt.style.color='#059669'; }
  else { dot.style.background='#CBD5E1'; dot.style.animation='none'; txt.textContent='Ready'; txt.style.color='#92400E'; txt.style.opacity='.6'; }

  document.getElementById('btn-start').style.display = running ? 'none' : '';
  document.getElementById('btn-pause').style.display = running ? '' : 'none';
  document.getElementById('btn-stop').style.display = running ? '' : 'none';
  document.getElementById('btn-pause').textContent = paused ? 'Resume' : 'Pause';
  document.getElementById('btn-pause').className = 'tb-btn ' + (paused ? 'primary' : 'warn');

  document.getElementById('prompt-display').textContent = currPrompt;
  document.getElementById('prompt-ver-pill').textContent = 'v'+promptVer;
}

// ═══ TAB SWITCH ═══
function switchTab(tab) {
  currentTab = tab;
  document.querySelectorAll('.tab').forEach(t => t.classList.toggle('active', t.dataset.tab === tab));
  document.getElementById('tab-tests').style.display = tab === 'tests' ? '' : 'none';
  document.getElementById('tab-versions').style.display = tab === 'versions' ? '' : 'none';
  document.getElementById('tab-log').style.display = tab === 'log' ? '' : 'none';
  if (tab === 'tests') renderTests();
  if (tab === 'versions') renderVersions();
  if (tab === 'log') renderLog();
}

// ═══ PROMPT EDIT ═══
function startEditPrompt() {
  if (running) return;
  document.getElementById('prompt-display').style.display = 'none';
  document.getElementById('prompt-editor').style.display = '';
  document.getElementById('prompt-editor').value = currPrompt;
  document.getElementById('prompt-actions').innerHTML = `
    <button class="tb-btn primary" onclick="applyPromptEdit()" style="background:#059669">Apply</button>
    <button class="tb-btn" onclick="cancelPromptEdit()">Cancel</button>`;
}
function applyPromptEdit() {
  currPrompt = document.getElementById('prompt-editor').value;
  cancelPromptEdit();
  addLog('Prompt đã được chỉnh sửa thủ công', 'info');
}
function cancelPromptEdit() {
  document.getElementById('prompt-display').style.display = '';
  document.getElementById('prompt-editor').style.display = 'none';
  document.getElementById('prompt-actions').innerHTML = '<button class="tb-btn" onclick="startEditPrompt()" id="btn-edit-prompt">Edit</button>';
  updateTopbar();
}

// ═══ SELECT TEST ═══
function selectTest(id) {
  selectedId = selectedId === id ? null : id;
  renderDetail();
  renderTests();
  renderScoreGrid();
}

// ═══ TEST CASE MGMT ═══
function deleteTC(id) {
  testCases = testCases.filter(t => t.id !== id);
  delete results[id];
  addLog(`Test #${id} đã xoá`, 'info');
  renderTests();
  renderScoreGrid();
}
function addNewTC() {
  const input = prompt('Nhập câu hỏi test:');
  if (!input) return;
  const kw = prompt('Keywords (cách bởi dấu phẩy):') || '';
  const q = confirm('Mong bot hỏi lại khách?');
  const id = Date.now();
  testCases.push({id, input, expect_keywords: kw.split(',').map(s=>s.trim()).filter(Boolean), expect_question: q});
  addLog('Test case mới thêm', 'info');
  renderTests();
  renderScoreGrid();
}

// ═══ VERSION ═══
function restoreVersion(v) {
  const h = promptHistory.find(x => x.v === v);
  if (!h || running) return;
  currPrompt = h.prompt;
  promptVer = h.v;
  addLog(`Restored v${v}`, 'info');
  updateTopbar();
  renderVersions();
}

// ═══ PROPOSAL ═══
function applyProposal() {
  if (!proposal) return;
  promptVer++;
  currPrompt = proposal.new_prompt;
  promptHistory.push({v:promptVer, prompt:proposal.new_prompt, avgScore:null, changes:proposal.changes||[]});
  addLog(`Prompt v${promptVer} applied!`, 'system');
  proposal = null;
  document.getElementById('proposal-box').style.display = 'none';
  updateTopbar();
}
function dismissProposal() {
  proposal = null;
  document.getElementById('proposal-box').style.display = 'none';
}

// ═══ API CALLS ═══
async function apiRunTest(prompt, testInput) {
  const r = await fetch('/api/prompt-optimizer/run-test', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({prompt, test_input: testInput})
  });
  return r.json();
}

async function apiJudge(testInput, chatbotOutput, productIds, expectKw, expectQ) {
  const r = await fetch('/api/prompt-optimizer/judge', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({test_input: testInput, chatbot_output: chatbotOutput, product_ids: productIds, expect_keywords: expectKw, expect_question: expectQ})
  });
  return r.json();
}

async function apiOptimize(currentPrompt, testResults, version) {
  const r = await fetch('/api/prompt-optimizer/optimize', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({current_prompt: currentPrompt, test_results: testResults, version})
  });
  return r.json();
}

// ═══ MAIN LOOP ═══
async function runRound() {
  const roundResults = {};
  let allMet = {factual:0, relevance:0, tone:0, helpfulness:0, hallucination:0};

  addLog(`Round ${iterations+1} — Prompt v${promptVer} — ${testCases.length} tests`, 'system');

  for (const tc of testCases) {
    if (!running) break;
    while (paused && running) await sleep(200);

    activeId = tc.id;
    results[tc.id] = {output:'', total:null, metrics:null, streaming:true};
    renderTests(); renderScoreGrid();
    addLog(`Testing #${tc.id}: "${tc.input.slice(0,40)}..."`, 'run');

    // Step 1: Run chatbot
    let chatResult;
    try {
      chatResult = await apiRunTest(currPrompt, tc.input);
      if (chatResult.status !== 'success') throw new Error(chatResult.message || 'API error');
    } catch(e) {
      addLog(`Error: ${e.message}`, 'fail');
      results[tc.id] = {output:'Error: '+e.message, total:0, metrics:null, streaming:false};
      renderTests();
      continue;
    }

    results[tc.id] = {output: chatResult.output, total:null, metrics:null, streaming:true};
    renderTests();
    if (selectedId === tc.id) renderDetail();

    // Step 2: Judge
    addLog(`Judge đang đánh giá #${tc.id}...`, 'optimize');
    let judgeResult;
    try {
      judgeResult = await apiJudge(tc.input, chatResult.output, chatResult.product_ids || [], tc.expect_keywords, tc.expect_question);
      if (judgeResult.status !== 'success') throw new Error(judgeResult.message || 'Judge error');
    } catch(e) {
      addLog(`Judge error: ${e.message}`, 'fail');
      results[tc.id] = {...results[tc.id], total:0.5, metrics:null, streaming:false};
      roundResults[tc.id] = {input:tc.input, output:chatResult.output, score:0.5, reasoning:'Judge error', issues:[]};
      continue;
    }

    const total = judgeResult.total || 0;
    const metrics = judgeResult.metrics || {};
    results[tc.id] = {
      output: chatResult.output,
      total,
      metrics,
      reasoning: judgeResult.reasoning || '',
      issues: judgeResult.issues || [],
      streaming: false
    };

    roundResults[tc.id] = {
      input: tc.input,
      output: chatResult.output,
      score: total,
      reasoning: judgeResult.reasoning || '',
      issues: judgeResult.issues || []
    };

    Object.keys(allMet).forEach(k => {
      if (metrics[k] != null) allMet[k] += metrics[k] / testCases.length;
    });

    const icon = total >= 0.75 ? 'PASS' : total >= 0.5 ? 'WARN' : 'FAIL';
    addLog(`${icon} #${tc.id} Score: ${(total*100).toFixed(0)}%`, total >= 0.75 ? 'pass' : 'fail');

    renderTests(); renderScoreGrid();
    if (selectedId === tc.id) renderDetail();
  }

  activeId = null;
  const scores = Object.values(roundResults);
  const avg = scores.reduce((s,r) => s + r.score, 0) / Math.max(scores.length, 1);
  scoreHistory.push(parseFloat(avg.toFixed(3)));
  iterations++;

  prevMet = latestMet;
  latestMet = {...allMet};

  addLog(`Avg: ${(avg*100).toFixed(1)}% — v${promptVer}`, 'score');

  // Update version score
  promptHistory = promptHistory.map(h => h.v === promptVer ? {...h, avgScore:avg} : h);

  renderTests(); renderScoreGrid(); renderSparkline(); renderMetrics(); updateTopbar();
  if (currentTab === 'versions') renderVersions();

  return {roundResults, avg};
}

async function optimize(roundResults, avg) {
  document.getElementById('optimizing-indicator').style.display = 'inline-flex';
  addLog('Score < 75% — Optimizer đang phân tích...', 'optimize');

  const testResults = Object.values(roundResults);
  try {
    const result = await apiOptimize(currPrompt, testResults, promptVer);
    if (result.status !== 'success') throw new Error(result.message || 'Optimizer error');

    addLog(`${result.analysis?.slice(0,70)}...`, 'optimize');
    (result.changes || []).forEach(c => addLog(`  → ${c}`, 'optimize'));

    const autoApply = document.getElementById('chk-auto').checked;
    if (autoApply) {
      promptVer++;
      currPrompt = result.new_prompt;
      promptHistory.push({v:promptVer, prompt:result.new_prompt, avgScore:null, changes:result.changes||[], predicted:result.predicted_score});
      addLog(`Prompt v${promptVer} applied! Predicted: ${((result.predicted_score||0)*100).toFixed(0)}%`, 'system');
      updateTopbar();
    } else {
      proposal = result;
      document.getElementById('proposal-box').style.display = '';
      document.getElementById('proposal-analysis').textContent = result.analysis || '';
      document.getElementById('proposal-changes').innerHTML = (result.changes||[]).map(c => `<div style="font-size:11px;color:#4C1D95;line-height:1.4">→ ${c}</div>`).join('');
    }
  } catch(e) {
    addLog(`Optimizer error: ${e.message}`, 'fail');
  }

  document.getElementById('optimizing-indicator').style.display = 'none';
}

async function startLoop() {
  if (running) return;
  running = true; paused = false;
  results = {}; scoreHistory = []; iterations = 0;
  latestMet = null; prevMet = null; activeId = null; selectedId = null;
  logs = []; proposal = null;
  document.getElementById('proposal-box').style.display = 'none';

  updateTopbar();
  addLog('Optimizer khởi động', 'system');
  addLog(`${testCases.length} test cases · Target >= 75%`, 'info');
  switchTab('log');
  renderTests(); renderScoreGrid(); renderSparkline(); renderMetrics();

  let round = 0;
  while (running && round < 6) {
    round++;
    const {roundResults, avg} = await runRound();
    if (!running) break;
    if (avg >= 0.75) {
      addLog(`Target đạt! ${(avg*100).toFixed(1)}% >= 75%`, 'pass');
      break;
    }
    if (round < 6) {
      await optimize(roundResults, avg);
      await sleep(800);
    } else {
      addLog(`Max rounds. Best: ${(Math.max(...scoreHistory)*100).toFixed(1)}%`, 'system');
    }
  }
  running = false; paused = false;
  updateTopbar();
  addLog('Done', 'system');
}

function stopLoop() {
  running = false; paused = false;
  addLog('Stopped', 'info');
  updateTopbar();
}
function togglePause() {
  paused = !paused;
  addLog(paused ? 'Paused' : 'Resumed', 'info');
  updateTopbar();
}

// ═══ INIT ═══
renderTests();
renderScoreGrid();
renderSparkline();
renderMetrics();
updateTopbar();