// PromptOS app — AI Generator, Prompt Builder, Testing Lab
function FieldLabel({ children }) {
  return <span style={{ fontSize: 12, fontWeight: 700, color: T.mut }}>{children}</span>;
}

// metode prompting yang didukung generator
const posGenMethods = [
  ['role', 'Role-based', 'Persona ahli + tugas jelas'],
  ['rag', 'RAG', 'Jawab berdasar dokumen/sumber (grounded)'],
  ['cot', 'Chain-of-Thought', 'Paksa penalaran bertahap'],
  ['fewshot', 'Few-shot', 'Beri contoh input → output'],
  ['react', 'ReAct', 'Reasoning + memakai tools'],
];

function GenInput({ value, onChange, placeholder, rows }) {
  const style = {
    fontFamily: T.font, fontSize: 13, color: T.text, background: T.card2, lineHeight: 1.55,
    border: `1px solid ${T.line}`, borderRadius: 9, padding: '10px 12px', outline: 'none',
    width: '100%', boxSizing: 'border-box', resize: 'vertical',
  };
  return rows
    ? <textarea value={value} onChange={(e) => onChange(e.target.value)} placeholder={placeholder} rows={rows} style={style} />
    : <input value={value} onChange={(e) => onChange(e.target.value)} placeholder={placeholder} style={style} />;
}

function GenChips({ options, onPick }) {
  return (
    <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
      {options.map((o) => (
        <button key={o} onClick={() => onPick(o)} style={{
          fontFamily: T.font, fontSize: 11, fontWeight: 600, color: T.mut, cursor: 'pointer',
          background: T.card2, border: `1px solid ${T.line}`, borderRadius: 99, padding: '4px 10px',
        }}>{o}</button>
      ))}
    </div>
  );
}

function Generator({ limits = { genMethods: ['role', 'rag', 'cot', 'fewshot', 'react'] }, onUpgrade, addPrompt, cols = [], categories = [], setRoute }) {
  const allowed = limits.genMethods || ['role', 'rag', 'cot', 'fewshot', 'react'];
  const [saveOpen, setSaveOpen] = React.useState(false);
  const [saved, setSaved] = React.useState(false);
  const [method, setMethod] = React.useState('role');
  const [idea, setIdea] = React.useState('Konten Instagram edukatif tentang AI untuk pemula');
  const [role, setRole] = React.useState('Kamu adalah social media strategist berpengalaman 5+ tahun');
  const [task, setTask] = React.useState('Buat rencana konten carousel yang edukatif dan menarik');
  const [context, setContext] = React.useState('');
  const [audience, setAudience] = React.useState('Profesional muda 22–35 tahun yang baru mengenal AI');
  const [fmt, setFmt] = React.useState('Carousel 5 slide + caption + hashtag');
  const [tone, setTone] = React.useState('Friendly, modern, mudah dipahami');
  const [constraints, setConstraints] = React.useState('');
  const [sources, setSources] = React.useState('');   // RAG
  const [examples, setExamples] = React.useState('');  // Few-shot
  const [tools, setTools] = React.useState('search(query), calculator(expr), browse(url)'); // ReAct
  const [creativity, setCreativity] = React.useState(50);
  const [detail, setDetail] = React.useState('Balanced');
  const [out, setOut] = React.useState('');
  const [busy, setBusy] = React.useState(false);

  const build = () => {
    // creativity → tingkat, instruksi, dan saran temperature (granular sesuai posisi slider)
    const crTier = creativity < 20 ? 'Sangat presisi' : creativity < 40 ? 'Presisi' : creativity <= 60 ? 'Seimbang' : creativity < 80 ? 'Kreatif' : 'Sangat kreatif';
    const crInstr = creativity < 20 ? 'Tetap sangat faktual dan konservatif; hindari spekulasi maupun hiperbola.'
      : creativity < 40 ? 'Utamakan akurasi; variasi gaya minimal.'
      : creativity <= 60 ? 'Seimbangkan akurasi dengan sentuhan kreatif.'
      : creativity < 80 ? 'Eksploratif; tawarkan beberapa angle segar.'
      : 'Sangat berani & out-of-the-box; usulkan ide tak terduga.';
    const temp = (creativity / 100 * 1.2).toFixed(1);
    // detail level → instruksi + target panjang
    const dt = {
      Concise: { instr: 'Ringkas dan langsung ke inti, tanpa basa-basi.', len: 'Target sangat ringkas (~150 kata).' },
      Balanced: { instr: 'Cukup menyeluruh namun tetap padat.', len: 'Target sedang (~300–400 kata).' },
      Detailed: { instr: 'Sangat rinci; sertakan contoh, langkah, dan penjelasan tiap bagian.', len: 'Target panjang & lengkap (600+ kata).' },
    }[detail];
    const tail =
`# AUDIENCE
${audience || '—'}

# OUTPUT FORMAT
${fmt || '—'}

# TONE & STYLE
${tone || '—'} — ${crInstr}

# CREATIVITY
${crTier} (${creativity}%) · saran temperature ≈ ${temp}

# DETAIL
${dt.instr} ${dt.len}

# CONSTRAINTS
${constraints || '— (tidak ada batasan khusus)'}`;

    const head =
`# ROLE
${role}

# TASK
${task} — tentang: "${idea}".`;

    if (method === 'rag') {
      return `${head}

# RETRIEVED CONTEXT
Jawab HANYA berdasarkan sumber di bawah. Bila informasi tidak ada, katakan "Tidak ditemukan di sumber." Jangan mengarang.
"""
${sources || '[TEMPEL DOKUMEN / HASIL RETRIEVAL DI SINI]'}
"""

# GROUNDING & CITATION
- Sitir tiap klaim dengan [n] sesuai sumber.
- Pisahkan fakta (dari sumber) dan inferensi (analisis Anda).

${tail}`;
    }
    if (method === 'cot') {
      return `${head}

# REASONING (Chain-of-Thought)
Pikirkan langkah demi langkah sebelum menjawab: uraikan asumsi, pertimbangkan opsi, lalu simpulkan.
Tutup dengan bagian "Jawaban Akhir:" yang ringkas dan dapat ditindaklanjuti.

# CONTEXT
${context || '—'}

${tail}`;
    }
    if (method === 'fewshot') {
      return `${head}

# EXAMPLES (Few-shot)
${examples || 'Input: <contoh masukan>\nOutput: <contoh keluaran ideal>\n\nInput: <contoh masukan 2>\nOutput: <contoh keluaran 2>'}

Ikuti pola, gaya, dan struktur contoh di atas untuk input baru.

# CONTEXT
${context || '—'}

${tail}`;
    }
    if (method === 'react') {
      return `${head}

# TOOLS AVAILABLE
${tools || 'search(query), calculator(expr)'}

# REASONING FORMAT (ReAct)
Ulangi siklus berikut hingga cukup informasi:
Thought: alasan langkah berikutnya
Action: panggil salah satu tool di atas
Observation: hasil dari tool
… lalu akhiri dengan:
Final Answer: jawaban final untuk pengguna.

${tail}`;
    }
    // role-based (default)
    return `${head}

# CONTEXT
${context || '—'}

${tail}

(Berikan ringkasan pendekatan sebelum output final.)`;
  };

  const generate = () => {
    setBusy(true); setOut('');
    setTimeout(() => { setBusy(false); setOut(build()); }, 700);
  };
  // simpan hasil generate ke Library (lewat PromptModal untuk kustomisasi)
  const methodLabel = posGenMethods.find((x) => x[0] === method)[1];
  const saveInitial = { t: idea, d: task, m: 'ChatGPT', cat: '', body: out };
  const onSaveGenerated = (v) => {
    if (!addPrompt) return;
    addPrompt({ id: 'u' + Date.now(), ...v, r: 5.0, u: '0', hue: Math.floor(Math.random() * 360), img: v.img || (typeof posCatCovers !== 'undefined' ? posCatCovers[v.cat] : '') });
    setSaved(true); setTimeout(() => setSaved(false), 2500);
  };

  const seg = (val) => ({
    fontFamily: T.font, fontSize: 12, fontWeight: 600, cursor: 'pointer', flex: 1, padding: '7px 0',
    borderRadius: 7, border: 'none', textAlign: 'center',
    background: detail === val ? T.grad : 'transparent', color: detail === val ? '#fff' : T.mut,
  });

  return (
    <div style={{ padding: 22, display: 'grid', gridTemplateColumns: '440px 1fr', gap: 20, alignItems: 'start' }}>
      <Card style={{ padding: 20, display: 'flex', flexDirection: 'column', gap: 13 }}>
        <div>
          <div style={{ fontSize: 18, fontWeight: 800, color: T.text }}>AI Prompt Generator</div>
          <div style={{ fontSize: 12.5, color: T.mut, marginTop: 4, lineHeight: 1.5 }}>Pilih metode, sesuaikan tiap bagian, lalu hasilkan prompt yang terstruktur.</div>
        </div>

        <FieldLabel>Metode prompting</FieldLabel>
        <div style={{ display: 'flex', gap: 7, flexWrap: 'wrap' }}>
          {posGenMethods.map(([id, label]) => {
            const locked = !allowed.includes(id);
            return (
              <button key={id} title={locked ? 'Tersedia di paket Pro' : posGenMethods.find((x) => x[0] === id)[2]}
                onClick={() => { if (locked) { onUpgrade && onUpgrade({ feature: 'Metode ' + label, plan: 'Pro', desc: 'Metode prompting lanjutan (RAG, Chain-of-Thought, Few-shot, ReAct) tersedia di paket Pro.' }); } else setMethod(id); }} style={{
                  fontFamily: T.font, fontSize: 12, fontWeight: 700, cursor: 'pointer',
                  color: method === id ? '#fff' : (locked ? T.dim : T.mut), borderRadius: 99, padding: '7px 13px',
                  background: method === id ? T.grad : T.card2, opacity: locked ? 0.6 : 1,
                  border: `1px solid ${method === id ? 'transparent' : T.line}`,
                }}>{locked ? '🔒 ' : ''}{label}</button>
            );
          })}
        </div>
        <span style={{ fontSize: 11.5, color: T.dim, marginTop: -4 }}>
          {allowed.length < posGenMethods.length
            ? 'Metode lanjutan (RAG, CoT, Few-shot, ReAct) tersedia di paket Pro.'
            : posGenMethods.find((x) => x[0] === method)[2]}
        </span>

        <FieldLabel>Goal / Ide</FieldLabel>
        <GenInput value={idea} onChange={setIdea} rows={2} placeholder="Apa yang ingin Anda capai?" />

        <FieldLabel>Role / Persona</FieldLabel>
        <GenInput value={role} onChange={setRole} placeholder="Kamu adalah …" />
        <GenChips options={['Senior marketing strategist', 'Expert copywriter', 'Data analyst', 'Software architect']}
          onPick={(o) => setRole('Kamu adalah ' + o.toLowerCase())} />

        <FieldLabel>Task</FieldLabel>
        <GenInput value={task} onChange={setTask} rows={2} placeholder="Tugas spesifik yang harus dikerjakan" />

        {method === 'rag' && (<>
          <FieldLabel>Sumber / Dokumen (RAG)</FieldLabel>
          <GenInput value={sources} onChange={setSources} rows={3} placeholder="Tempel dokumen, artikel, atau hasil retrieval…" />
        </>)}
        {method === 'fewshot' && (<>
          <FieldLabel>Contoh (Few-shot)</FieldLabel>
          <GenInput value={examples} onChange={setExamples} rows={4} placeholder={'Input: …\nOutput: …'} />
        </>)}
        {method === 'react' && (<>
          <FieldLabel>Tools tersedia (ReAct)</FieldLabel>
          <GenInput value={tools} onChange={setTools} rows={2} placeholder="search(query), calculator(expr), …" />
        </>)}
        {(method === 'role' || method === 'cot') && (<>
          <FieldLabel>Context (opsional)</FieldLabel>
          <GenInput value={context} onChange={setContext} rows={2} placeholder="Latar / informasi pendukung" />
        </>)}

        <FieldLabel>Audience</FieldLabel>
        <GenInput value={audience} onChange={setAudience} placeholder="Untuk siapa hasil ini?" />

        <FieldLabel>Output format</FieldLabel>
        <GenInput value={fmt} onChange={setFmt} placeholder="Bentuk keluaran yang diinginkan" />
        <GenChips options={['Tabel', 'Bullet list', 'Carousel 5 slide', 'JSON', 'Step-by-step']} onPick={setFmt} />

        <FieldLabel>Tone</FieldLabel>
        <GenInput value={tone} onChange={setTone} placeholder="Gaya bahasa" />
        <GenChips options={['Professional', 'Friendly', 'Playful', 'Authoritative', 'Minimal']} onPick={setTone} />

        <FieldLabel>Constraints (opsional)</FieldLabel>
        <GenInput value={constraints} onChange={setConstraints} rows={2} placeholder="mis. maks 120 kata, hindari jargon" />

        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
          <FieldLabel>Creativity</FieldLabel>
          <span style={{ fontSize: 11, color: T.vio, fontWeight: 700 }}>{creativity < 33 ? 'Precise' : creativity > 66 ? 'Creative' : 'Balanced'}</span>
        </div>
        <input type="range" min="0" max="100" value={creativity}
          onChange={(e) => setCreativity(Number(e.target.value))}
          style={{ width: '100%', accentColor: T.vio }} />

        <FieldLabel>Detail level</FieldLabel>
        <div style={{ display: 'flex', gap: 4, background: T.card2, border: `1px solid ${T.line}`, borderRadius: 9, padding: 4 }}>
          {['Concise', 'Balanced', 'Detailed'].map((v) => (
            <button key={v} onClick={() => setDetail(v)} style={seg(v)}>{v}</button>
          ))}
        </div>

        <VioBtn onClick={generate} style={{ marginTop: 4 }}>{busy ? '…' : '✦ Generate Prompt'}</VioBtn>
      </Card>

      <Card style={{ padding: 20, minHeight: 460, display: 'flex', flexDirection: 'column', gap: 12, position: 'sticky', top: 22 }}>
        <SectionTitle right={out && (
          <span style={{ display: 'inline-flex', alignItems: 'center', gap: 8 }}>
            <span style={{ fontSize: 12, fontWeight: 700, color: T.vio, background: T.vioSoft, border: `1px solid ${T.vio}44`, borderRadius: 99, padding: '7px 12px', lineHeight: 1.2, whiteSpace: 'nowrap' }}>{methodLabel}</span>
            <GhostBtn small onClick={() => navigator.clipboard && navigator.clipboard.writeText(out)}>⧉ Copy</GhostBtn>
            {addPrompt && <VioBtn small onClick={() => setSaveOpen(true)}>{saved ? '✓ Tersimpan' : '＋ Simpan ke Library'}</VioBtn>}
          </span>
        )}>Generated Prompt</SectionTitle>
        {busy && (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
            {[80, 95, 60, 88, 45, 70].map((w, i) => (
              <span key={i} className="pos-shimmer" style={{ height: 13, width: `${w}%`, borderRadius: 6 }}></span>
            ))}
          </div>
        )}
        {!busy && !out && (
          <div style={{
            flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center',
            color: T.dim, fontSize: 13, border: `1.5px dashed ${T.line2}`, borderRadius: 12, textAlign: 'center', padding: 20,
          }}>Pilih metode & isi bagian di kiri, lalu klik Generate ✦</div>
        )}
        {out && (
          <pre style={{
            margin: 0, whiteSpace: 'pre-wrap', fontFamily: T.font, fontSize: 13, lineHeight: 1.7,
            color: T.text, background: T.card2, border: `1px solid ${T.line}`, borderRadius: 12, padding: 18,
          }}>{out}</pre>
        )}
      </Card>
      {saveOpen && typeof PromptModal !== 'undefined' && (
        <PromptModal initial={saveInitial} forceNew cols={cols} categories={categories} catOptions={categories.map((c) => c.name)}
          onSave={onSaveGenerated} onClose={() => setSaveOpen(false)} />
      )}
    </div>
  );
}

// ---------- Prompt Builder ----------
// blok Prompt Builder — diselaraskan dengan field form AI Agent
const posBlockDefs = {
  Role: { c: '#7c5cfa', txt: 'You are a senior marketing strategist' },
  'System Instruction': { c: '#a78bfa', txt: 'Aturan utama dan peran agent. Selalu jawab faktual dan ringkas.' },
  Task: { c: '#60a5fa', txt: 'Buat strategi konten Instagram untuk brand skincare' },
  Context: { c: '#f5b94a', txt: 'Target audience wanita 20–35 tahun yang peduli dengan skincare' },
  'Input Fields': { c: '#22d3ee', txt: 'Data dari user: topik, target audiens, kata kunci' },
  'Pipeline Steps': { c: '#34d399', txt: 'Plan, Research, Generate, Review' },
  Rules: { c: '#f97316', txt: 'Gunakan bahasa yang friendly dan mudah dipahami' },
  'Output Format': { c: '#22b8cf', txt: 'Tabel dengan kolom: Ide Konten, Tujuan, Format, Caption' },
  Tools: { c: '#818cf8', txt: 'Web Search, Code Interpreter' },
  'Knowledge Base': { c: '#fb7185', txt: 'Dokumen/sumber data yang boleh digunakan' },
  'Tone & Language': { c: '#f472b6', txt: 'Profesional, Bahasa Indonesia' },
  Examples: { c: '#f59e0b', txt: '(Berikan 3 contoh ide konten sebagai referensi)' },
};

function Builder({ setRoute }) {
  const [blocks, setBlocks] = React.useState(() => {
    try {
      const preset = localStorage.getItem('pos.builderPreset');
      if (preset) {
        localStorage.removeItem('pos.builderPreset');
        const parsed = JSON.parse(preset).filter((b) => posBlockDefs[b.k]);
        if (parsed.length) return parsed;
      }
    } catch (e) { /* fall back to default blocks */ }
    return [
      { k: 'Role', txt: posBlockDefs.Role.txt },
      { k: 'Task', txt: posBlockDefs.Task.txt },
      { k: 'Context', txt: posBlockDefs.Context.txt },
    ];
  });
  const [dragIdx, setDragIdx] = React.useState(null);
  const [paletteKey, setPaletteKey] = React.useState(null); // jenis blok yang sedang di-drag dari palette
  const [copied, setCopied] = React.useState(false);
  const [agentOpen, setAgentOpen] = React.useState(false);

  // prefill form AI Agent dari blok yang sedang disusun (blok ⇄ field agent)
  const blockVal = (k) => { const b = blocks.find((x) => x.k === k); return b ? b.txt : ''; };
  const agentInitial = {
    name: '', category: 'General',
    desc: blockVal('Task'),
    system: blockVal('System Instruction') || [blockVal('Role'), blockVal('Rules')].filter(Boolean).join('\n\n'),
    inputs: blockVal('Input Fields') || blockVal('Context'),
    steps: blockVal('Pipeline Steps') || 'Plan, Research, Generate, Review',
    output: blockVal('Output Format'),
    tools: blockVal('Tools') ? blockVal('Tools').split(',').map((s) => s.trim()).filter(Boolean) : [],
    knowledge: blockVal('Knowledge Base'),
    tone: blockVal('Tone & Language'),
    lang: 'Indonesia',
  };
  const saveAgent = (v) => {
    if (window.posCreateAgent) window.posCreateAgent(v);
    setAgentOpen(false);
    if (setRoute) setRoute('agents');
  };

  const add = (k) => setBlocks((b) => [...b, { k, txt: posBlockDefs[k].txt }]);
  const insertAt = (k, i) => setBlocks((b) => { const c = [...b]; c.splice(i, 0, { k, txt: posBlockDefs[k].txt }); return c; });
  const remove = (i) => setBlocks((b) => b.filter((_, j) => j !== i));
  const edit = (i, txt) => setBlocks((b) => b.map((x, j) => (j === i ? { ...x, txt } : x)));
  // drop pada kartu ke-i: dari palette → sisipkan di posisi i; dari kartu lain → urutkan
  const handleDrop = (i) => {
    if (paletteKey) { insertAt(paletteKey, i); setPaletteKey(null); return; }
    if (dragIdx === null || dragIdx === i) return;
    setBlocks((b) => {
      const copy = [...b]; const [m] = copy.splice(dragIdx, 1); copy.splice(i, 0, m); return copy;
    });
    setDragIdx(null);
  };
  // drop di area kanvas (bukan di atas kartu): dari palette → tambah di akhir
  const dropOnCanvas = () => { if (paletteKey) { add(paletteKey); setPaletteKey(null); } };
  const result = blocks.map((b) => `${b.k.toUpperCase()}: ${b.txt}`).join('\n\n');

  return (
    <div style={{ padding: 22, display: 'grid', gridTemplateColumns: '210px 1fr 360px', gap: 18, alignItems: 'start' }}>
      <Card style={{ padding: 14, display: 'flex', flexDirection: 'column', gap: 8 }}>
        <FieldLabel>Blocks</FieldLabel>
        {Object.keys(posBlockDefs).map((k) => (
          <div key={k} draggable
            onClick={() => add(k)}
            onDragStart={() => { setPaletteKey(k); setDragIdx(null); }}
            onDragEnd={() => setPaletteKey(null)}
            style={{
              display: 'flex', alignItems: 'center', gap: 10, padding: '10px 12px', borderRadius: 9,
              background: T.card2, border: `1px solid ${paletteKey === k ? posBlockDefs[k].c : T.line}`, cursor: 'grab',
              fontSize: 13, fontWeight: 600, color: T.text, userSelect: 'none', opacity: paletteKey === k ? 0.6 : 1,
            }}>
            <span style={{ width: 10, height: 10, borderRadius: 3, background: posBlockDefs[k].c, flex: '0 0 auto' }}></span>
            {k}
            <span style={{ marginLeft: 'auto', color: T.dim, fontSize: 14 }}>+</span>
          </div>
        ))}
        <span style={{ fontSize: 11, color: T.dim, lineHeight: 1.5, marginTop: 4 }}>Klik atau drag ke kanvas untuk menambahkan · drag kartu untuk mengurutkan</span>
      </Card>
      <Card onDragOver={(e) => e.preventDefault()} onDrop={dropOnCanvas}
        style={{
          padding: 16, display: 'flex', flexDirection: 'column', gap: 10, minHeight: 420,
          border: paletteKey ? `1.5px dashed ${T.vio}` : undefined,
        }}>
        <div style={{ fontSize: 16, fontWeight: 800, color: T.text }}>Prompt Builder</div>
        <div style={{ fontSize: 12, color: T.mut, marginBottom: 4 }}>Bangun prompt sempurna dengan sistem drag & drop.</div>
        {blocks.map((b, i) => (
          <div key={i} draggable
            onDragStart={() => { setDragIdx(i); setPaletteKey(null); }}
            onDragOver={(e) => e.preventDefault()}
            onDrop={(e) => { e.stopPropagation(); handleDrop(i); }}
            style={{
              display: 'flex', gap: 12, padding: 12, borderRadius: 11, background: T.card2,
              border: `1px solid ${T.line}`, borderLeft: `3px solid ${posBlockDefs[b.k].c}`,
              opacity: dragIdx === i ? 0.5 : 1, cursor: 'grab',
            }}>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 6, flex: 1, minWidth: 0 }}>
              <span style={{ fontSize: 11, fontWeight: 800, letterSpacing: '0.06em', color: posBlockDefs[b.k].c, textTransform: 'uppercase' }}>{b.k}</span>
              <textarea value={b.txt} onChange={(e) => edit(i, e.target.value)} rows={2} style={{
                fontFamily: T.font, fontSize: 13, color: T.text, background: 'transparent',
                border: 'none', outline: 'none', resize: 'vertical', lineHeight: 1.5, width: '100%',
              }}></textarea>
            </div>
            <button onClick={() => remove(i)} style={{
              alignSelf: 'flex-start', background: 'transparent', border: 'none', color: T.dim,
              cursor: 'pointer', fontSize: 14, padding: 2,
            }}>×</button>
          </div>
        ))}
        {blocks.length === 0 && (
          <div style={{
            flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center',
            color: T.dim, fontSize: 13, border: `1.5px dashed ${T.line2}`, borderRadius: 12, minHeight: 200,
          }}>Tambahkan block dari panel kiri</div>
        )}
      </Card>
      <Card style={{ padding: 16, display: 'flex', flexDirection: 'column', gap: 12 }}>
        <SectionTitle>Preview</SectionTitle>
        <pre style={{
          margin: 0, whiteSpace: 'pre-wrap', fontFamily: T.font, fontSize: 12.5, lineHeight: 1.6,
          color: T.mut, background: T.card2, border: `1px solid ${T.line}`, borderRadius: 11,
          padding: 14, minHeight: 220,
        }}>{result || '—'}</pre>
        <VioBtn onClick={() => { navigator.clipboard && navigator.clipboard.writeText(result); setCopied(true); setTimeout(() => setCopied(false), 1500); }}>
          {copied ? '✓ Copied' : 'Generate Prompt'}
        </VioBtn>
        <GhostBtn onClick={() => setAgentOpen(true)}>🤖 Buat AI Agent dari prompt ini</GhostBtn>
        <span style={{ fontSize: 10.5, color: T.dim, textAlign: 'center', marginTop: -4 }}>Simpan susunan ini sebagai AI Agent baru.</span>
      </Card>
      {agentOpen && <AgentModal initial={agentInitial} onSave={saveAgent} onClose={() => setAgentOpen(false)} />}
    </div>
  );
}

// ---------- Testing Lab ----------
// model yang punya provider API nyata; sisanya selalu disimulasikan server
const posLabRealModels = ['ChatGPT', 'Claude', 'Gemini'];
const posLabSamples = [
  { t: 'Product Description', body: 'Tulis deskripsi produk yang persuasif untuk serum skincare alami: manfaat, bahan utama, dan ajakan beli. Maks 80 kata.' },
  { t: 'Instagram Carousel', body: 'Buat 5 slide carousel Instagram edukatif tentang AI untuk pemula, lengkap dengan caption singkat tiap slide.' },
];
const labAgo = (ts) => {
  const s = Math.max(0, (Date.now() - ts) / 1000);
  if (s < 60) return 'baru saja';
  if (s < 3600) return Math.floor(s / 60) + ' mnt lalu';
  if (s < 86400) return Math.floor(s / 3600) + ' jam lalu';
  return Math.floor(s / 86400) + ' hari lalu';
};

// modal pengaturan API key (key tak pernah ditampilkan balik — hanya status terisi/belum)
function LabKeysModal({ onClose, onSaved }) {
  const fields = [
    ['openai', 'OpenAI (ChatGPT)', 'sk-…'],
    ['anthropic', 'Anthropic (Claude)', 'sk-ant-…'],
    ['google', 'Google (Gemini)', 'AIza…'],
  ];
  const [status, setStatus] = React.useState({});
  const [vals, setVals] = React.useState({ openai: '', anthropic: '', google: '' });
  const [saving, setSaving] = React.useState(false);
  React.useEffect(() => {
    posAuthFetch('/lab/keys').then((r) => r.json()).then(setStatus).catch(() => {});
  }, []);
  const save = () => {
    setSaving(true);
    posAuthFetch('/lab/keys', { method: 'POST', body: JSON.stringify(vals) })
      .then((r) => r.json())
      .then((st) => { setStatus(st); setVals({ openai: '', anthropic: '', google: '' }); onSaved && onSaved(st); })
      .finally(() => setSaving(false));
  };
  const inputStyle = { fontFamily: T.font, fontSize: 13, color: T.text, background: T.card2, border: `1px solid ${T.line}`, borderRadius: 9, padding: '10px 12px', outline: 'none', width: '100%', boxSizing: 'border-box' };
  return (
    <div onClick={onClose} style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.6)', zIndex: 55, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 20 }}>
      <div onClick={(e) => e.stopPropagation()} style={{ width: 480, maxWidth: '94vw', background: T.card, border: `1px solid ${T.line2}`, borderRadius: 16, padding: 22, display: 'flex', flexDirection: 'column', gap: 13 }}>
        <div style={{ fontSize: 17, fontWeight: 800, color: T.text }}>API Keys Testing Lab</div>
        <div style={{ fontSize: 12, color: T.mut, lineHeight: 1.5 }}>Isi key untuk memanggil model sungguhan. Model dengan key terisi akan dijalankan <b>live</b>; sisanya otomatis disimulasikan. Key disimpan di server Anda dan tidak pernah ditampilkan kembali.</div>
        {fields.map(([k, label, ph]) => (
          <div key={k} style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
            <span style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
              <FieldLabel>{label}</FieldLabel>
              <Badge color={status[k] ? T.green : T.dim}>{status[k] ? '✓ terisi' : 'belum diisi'}</Badge>
            </span>
            <input type="password" autoComplete="off" value={vals[k]} placeholder={status[k] ? '•••••••• (kosongkan = tak diubah)' : ph}
              onChange={(e) => setVals((v) => ({ ...v, [k]: e.target.value }))} style={inputStyle} />
          </div>
        ))}
        <div style={{ display: 'flex', gap: 10, justifyContent: 'flex-end', marginTop: 4 }}>
          <GhostBtn small onClick={onClose}>Tutup</GhostBtn>
          <VioBtn small onClick={save}>{saving ? 'Menyimpan…' : 'Simpan key'}</VioBtn>
        </div>
      </div>
    </div>
  );
}

// render teks model (markdown ringan) jadi paragraf rapi — tanpa simbol mentah (#, **, ---, -)
function labInline(str, kp) {
  const nodes = []; let rest = String(str); let i = 0;
  const re = /(\*\*([^*]+)\*\*|__([^_]+)__|`([^`]+)`|\*([^*]+)\*)/;
  let m;
  while ((m = re.exec(rest))) {
    if (m.index > 0) nodes.push(rest.slice(0, m.index));
    if (m[2] != null || m[3] != null) nodes.push(<b key={kp + '-b' + (i++)} style={{ color: T.text }}>{m[2] || m[3]}</b>);
    else if (m[4] != null) nodes.push(<code key={kp + '-c' + (i++)} style={{ fontFamily: 'monospace', fontSize: 11.5, background: T.card, padding: '1px 4px', borderRadius: 4 }}>{m[4]}</code>);
    else if (m[5] != null) nodes.push(<i key={kp + '-i' + (i++)}>{m[5]}</i>);
    rest = rest.slice(m.index + m[0].length);
  }
  if (rest) nodes.push(rest);
  return nodes;
}
function LabRich({ text }) {
  const lines = String(text || '').replace(/\r/g, '').split('\n');
  const blocks = []; let para = []; let list = null;
  const flushPara = () => { if (para.length) { blocks.push({ t: 'p', c: para.join(' ') }); para = []; } };
  const flushList = () => { if (list) { blocks.push({ t: 'ul', items: list }); list = null; } };
  for (const raw of lines) {
    const line = raw.trim();
    if (!line) { flushPara(); flushList(); continue; }
    if (/^([-*_]\s?){3,}$/.test(line)) { flushPara(); flushList(); blocks.push({ t: 'hr' }); continue; }
    let m;
    if ((m = /^(#{1,6})\s+(.*)$/.exec(line))) { flushPara(); flushList(); blocks.push({ t: 'h', level: m[1].length, c: m[2] }); continue; }
    if ((m = /^[-*•]\s+(.*)$/.exec(line)) || (m = /^\d+[.)]\s+(.*)$/.exec(line))) { flushPara(); (list = list || []).push(m[1]); continue; }
    flushList(); para.push(line);
  }
  flushPara(); flushList();
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 7 }}>
      {blocks.map((b, i) => {
        if (b.t === 'hr') return <div key={i} style={{ height: 1, background: T.line, margin: '2px 0' }} />;
        if (b.t === 'h') {
          const fs = b.level <= 1 ? 14.5 : b.level === 2 ? 13.5 : 12.5;
          return <div key={i} style={{ fontSize: fs, fontWeight: 800, color: T.text, marginTop: i ? 4 : 0, lineHeight: 1.35 }}>{labInline(b.c, 'h' + i)}</div>;
        }
        if (b.t === 'ul') return (
          <ul key={i} style={{ margin: 0, paddingLeft: 18, display: 'flex', flexDirection: 'column', gap: 4 }}>
            {b.items.map((it, j) => <li key={j} style={{ fontSize: 12.5, color: T.mut, lineHeight: 1.55 }}>{labInline(it, 'l' + i + '-' + j)}</li>)}
          </ul>
        );
        return <p key={i} style={{ margin: 0, fontSize: 12.5, color: T.mut, lineHeight: 1.65 }}>{labInline(b.c, 'p' + i)}</p>;
      })}
    </div>
  );
}

// indikator "sedang berpikir" ala ChatGPT/Claude — frasa berputar sesuai jenis output
const posThinkPhrases = {
  Teks: ['Membaca prompt', 'Menganalisis konteks', 'Menyusun jawaban', 'Menulis respons'],
  Dokumen: ['Membaca prompt', 'Menyusun kerangka', 'Menulis dokumen', 'Merapikan format'],
  Gambar: ['Memahami deskripsi', 'Menyusun komposisi', 'Melukis detail', 'Merender gambar'],
  Video: ['Menyusun storyboard', 'Menyiapkan frame', 'Merangkai klip', 'Merender video'],
};
function ThinkingIndicator({ model, kind, color = T.vio }) {
  const phrases = posThinkPhrases[kind] || posThinkPhrases.Teks;
  const isMedia = kind === 'Gambar' || kind === 'Video';
  const [i, setI] = React.useState(0);
  React.useEffect(() => {
    const t = setInterval(() => setI((x) => (x + 1) % phrases.length), 1700);
    return () => clearInterval(t);
  }, [kind]);
  const head = (
    <div style={{ display: 'flex', alignItems: 'center', gap: 9 }}>
      <span style={{ display: 'inline-flex', gap: 4, alignItems: 'center' }}>
        {[0, 1, 2].map((d) => (
          <span key={d} className="pos-think-dot" style={{ width: 7, height: 7, background: color, animationDelay: `${d * 0.18}s` }}></span>
        ))}
      </span>
      <span style={{ fontSize: 13, fontWeight: 700, color: T.text }}>{model} sedang berpikir</span>
    </div>
  );
  const phrase = (
    <span key={i} style={{ fontSize: 12, color: T.mut, animation: 'posShimmer 1.2s linear', display: 'inline-block' }}>
      {phrases[i]}…
    </span>
  );
  if (isMedia) {
    return (
      <div style={{
        width: '100%', aspectRatio: '1 / 1', borderRadius: 10, border: `1px dashed ${color}55`,
        display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 12,
        background: `radial-gradient(circle at 50% 45%, ${color}1f, transparent 70%)`,
      }}>
        {head}
        {phrase}
      </div>
    );
  }
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
      {head}
      {phrase}
      <div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginTop: 2 }}>
        {[92, 75, 84].map((w, k) => <span key={k} className="pos-shimmer" style={{ height: 10, width: `${w}%`, borderRadius: 6 }}></span>)}
      </div>
    </div>
  );
}

function Lab({ limits = { labModels: Infinity }, onUpgrade, lib = [], cap = { edit: true }, categories = [] }) {
  const max = limits.labModels || Infinity;
  const upsell = () => onUpgrade && onUpgrade({ feature: 'Uji banyak model', plan: 'Pro', desc: `Paket Anda dibatasi ${max} model di Testing Lab. Upgrade untuk membandingkan lebih banyak.` });
  // sumber prompt = Library asli (fallback ke contoh bila kosong)
  const libPrompts = (lib || []).filter((p) => p && p.t);
  const source = libPrompts.length ? libPrompts : posLabSamples;
  // tebak jenis output dari kategori/judul prompt
  const guessType = (p) => {
    const s = ((p && p.cat) || '') + ' ' + ((p && p.t) || '');
    if (/video|reel|tiktok|veo|runway|kling|sora|animation|animasi/i.test(s)) return 'Video';
    if (/image|gambar|poster|logo|foto|visual|ilustrasi|art\b|design|desain|thumbnail/i.test(s)) return 'Gambar';
    if (/dokumen|document|laporan|report|surat|kontrak|proposal|essay|esai|artikel|blog|email|cv|resume/i.test(s)) return 'Dokumen';
    return 'Teks';
  };
  // kategori dari Library + metadata ikon/warna (sama seperti halaman Library)
  const catMetaL = {}; (categories || []).forEach((c) => { catMetaL[c.name] = c; });
  const catNames = Array.from(new Set(source.map((p) => p.cat).filter(Boolean)));
  const catOptions = [{ value: 'Semua', label: 'Semua kategori', icon: 'grid', color: T.mut },
    ...catNames.map((n) => ({ value: n, label: n, icon: (catMetaL[n] && catMetaL[n].icon) || 'tag', color: (catMetaL[n] && catMetaL[n].color) || T.vio }))];
  const [cat, setCat] = React.useState('Semua');
  const filtered = cat === 'Semua' ? source : source.filter((p) => p.cat === cat);
  const [selTitle, setSelTitle] = React.useState(source[0].t);
  const selPrompt = filtered.find((p) => p.t === selTitle) || filtered[0] || source[0];
  const [text, setText] = React.useState(selPrompt.body || selPrompt.t || '');
  const [outType, setOutType] = React.useState(guessType(selPrompt));
  // ganti kategori → pilih prompt pertama di kategori itu
  React.useEffect(() => {
    const f = cat === 'Semua' ? source : source.filter((p) => p.cat === cat);
    if (f.length && !f.find((p) => p.t === selTitle)) setSelTitle(f[0].t);
  }, [cat]);
  // ganti prompt → set teks & tebak jenis output
  React.useEffect(() => {
    const p = source.find((x) => x.t === selTitle);
    if (p) { setText(p.body || p.t || ''); setOutType(guessType(p)); }
  }, [selTitle]);
  // jenis output → mode server
  const mode = outType === 'Gambar' ? 'image' : outType === 'Video' ? 'video' : 'text';

  const [sel, setSel] = React.useState(() => ['ChatGPT', 'Claude', 'Gemini'].slice(0, max === Infinity ? 3 : max));
  const [state, setState] = React.useState('idle'); // idle | busy | done
  const [results, setResults] = React.useState([]);
  const [keysModal, setKeysModal] = React.useState(false);
  const [keyStatus, setKeyStatus] = React.useState({});
  const [showAll, setShowAll] = React.useState(false);
  const [copied, setCopied] = React.useState(null); // model yang baru disalin
  const [history, setHistory] = React.useState(() => {
    try { return JSON.parse(localStorage.getItem('pos.labHistory') || '[]'); } catch (e) { return []; }
  });

  React.useEffect(() => {
    posAuthFetch('/lab/keys').then((r) => r.json()).then(setKeyStatus).catch(() => {});
  }, []);

  const toggle = (n) => setSel((s) => {
    if (s.includes(n)) return s.filter((x) => x !== n);
    if (s.length >= max) { upsell(); return s; }
    return [...s, n];
  });

  const run = () => {
    if (!text.trim() || !sel.length || state === 'busy') return;
    setState('busy'); setResults([]);
    posAuthFetch('/lab/run', { method: 'POST', body: JSON.stringify({ prompt: text, models: sel, mode }) })
      .then((r) => r.json())
      .then((d) => {
        const rs = d.results || [];
        setResults(rs); setState('done');
        const entry = { t: selPrompt.t, when: Date.now(), models: sel.slice(), live: rs.some((x) => !x.simulated) };
        setHistory((h) => { const nh = [entry, ...h].slice(0, 30); try { localStorage.setItem('pos.labHistory', JSON.stringify(nh)); } catch (e) {} return nh; });
      })
      .catch(() => { setState('done'); setResults([]); });
  };

  const provider = { ChatGPT: 'openai', Claude: 'anthropic', Gemini: 'google' };
  const fastest = state === 'done' && results.length ? results.reduce((a, b) => (a.ms <= b.ms ? a : b)).model : null;
  const totalTok = results.reduce((s, r) => s + (r.tokens || 0), 0);
  const isMediaResults = results.length > 0 && results.every((r) => r.kind === 'image' || r.kind === 'video');
  const shownHistory = showAll ? history : history.slice(0, 5);

  return (
    <div style={{ padding: 22, display: 'flex', flexDirection: 'column', gap: 18, maxWidth: 1100 }}>
      <Card style={{ padding: 20, display: 'flex', flexDirection: 'column', gap: 14 }}>
        <div style={{ display: 'flex', alignItems: 'flex-start', gap: 12 }}>
          <div>
            <div style={{ fontSize: 18, fontWeight: 800, color: T.text }}>Prompt Testing Lab</div>
            <div style={{ fontSize: 12.5, color: T.mut, marginTop: 4 }}>Uji satu prompt di beberapa AI sekaligus dan bandingkan hasilnya side-by-side.</div>
          </div>
          <span style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 8 }}>
            <Badge color={(keyStatus.openai || keyStatus.anthropic || keyStatus.google) ? T.green : T.amber}>
              {(keyStatus.openai || keyStatus.anthropic || keyStatus.google) ? 'Mode Live aktif' : 'Mode Simulasi'}
            </Badge>
            {cap.edit && <GhostBtn small onClick={() => setKeysModal(true)}>⚙ API Keys</GhostBtn>}
          </span>
        </div>
        <div style={{ display: 'flex', gap: 12, flexWrap: 'wrap' }}>
          <div style={{ flex: '1 1 200px', minWidth: 0 }}>
            <FieldLabel>Pilih Kategori</FieldLabel>
            <div style={{ marginTop: 6 }}><IconSelect value={cat} onChange={setCat} options={catOptions} /></div>
          </div>
          <div style={{ flex: '1 1 240px', minWidth: 0 }}>
            <FieldLabel>Pilih Prompt {libPrompts.length ? '' : '(contoh)'}</FieldLabel>
            <div style={{ marginTop: 6 }}>
              {filtered.length
                ? <IconSelect value={selPrompt.t} onChange={setSelTitle} placeholder="Pilih prompt…"
                    options={filtered.map((p) => ({ value: p.t, label: p.t, icon: (catMetaL[p.cat] && catMetaL[p.cat].icon) || 'tag', color: (catMetaL[p.cat] && catMetaL[p.cat].color) || T.vio }))} />
                : <div style={{ fontSize: 12, color: T.dim, padding: '10px 2px' }}>Tidak ada prompt di kategori ini.</div>}
            </div>
          </div>
        </div>
        <FieldLabel>Teks prompt yang diuji</FieldLabel>
        <textarea value={text} onChange={(e) => setText(e.target.value)} rows={4} placeholder="Tulis atau ubah prompt di sini…"
          style={{ fontFamily: T.font, fontSize: 13, color: T.text, background: T.card2, border: `1px solid ${T.line}`, borderRadius: 9, padding: '11px 12px', outline: 'none', resize: 'vertical', lineHeight: 1.55 }} />
        <FieldLabel>Jenis output</FieldLabel>
        <div style={{ display: 'flex', gap: 4, background: T.card2, border: `1px solid ${T.line}`, borderRadius: 9, padding: 4, alignSelf: 'flex-start', flexWrap: 'wrap' }}>
          {[['Teks', '📝 Teks'], ['Dokumen', '📄 Dokumen'], ['Gambar', '🖼 Gambar'], ['Video', '🎬 Video']].map(([v, lbl]) => (
            <button key={v} onClick={() => setOutType(v)} style={{
              fontFamily: T.font, fontSize: 12.5, fontWeight: 700, cursor: 'pointer', padding: '7px 15px', borderRadius: 7, border: 'none',
              background: outType === v ? T.grad : 'transparent', color: outType === v ? '#fff' : T.mut,
            }}>{lbl}</button>
          ))}
        </div>
        {mode === 'image' && <span style={{ fontSize: 10.5, color: T.dim }}>ChatGPT → GPT Image 2 (live); model lain placeholder.</span>}
        {mode === 'video' && <span style={{ fontSize: 10.5, color: T.amber }}>Video belum tersedia secara live — semua hasil berupa placeholder simulasi.</span>}
        {outType === 'Dokumen' && <span style={{ fontSize: 10.5, color: T.dim }}>Output dokumen dihasilkan model teks (diformat rapi sebagai dokumen).</span>}
        <FieldLabel>Pilih AI untuk diuji{max !== Infinity ? ` (maks ${max} di paket Anda)` : ''}</FieldLabel>
        <div style={{ display: 'flex', gap: 10, flexWrap: 'wrap', alignItems: 'center' }}>
          {posModelList.slice(0, 6).map((m) => {
            const on = sel.includes(m.n);
            const live = posLabRealModels.includes(m.n) && keyStatus[provider[m.n]];
            return (
              <button key={m.n} onClick={() => toggle(m.n)} title={`${m.n}${live ? ' · live' : ' · simulasi'}`} style={{
                position: 'relative', width: 44, height: 44, borderRadius: 11, cursor: 'pointer',
                background: on ? m.c + '22' : T.card2,
                border: `1.5px solid ${on ? m.c : T.line}`,
                display: 'flex', alignItems: 'center', justifyContent: 'center',
                color: on ? m.c : T.dim, fontSize: 15, fontWeight: 800, fontFamily: T.font,
              }}>
                {posModelImages[m.n]
                  ? <img src={posModelImages[m.n]} alt={m.n} width="26" height="26" style={{ borderRadius: 8, objectFit: 'cover', background: '#fff', opacity: on ? 1 : 0.45 }} />
                  : (posModelLogos[m.n] ? <MLogo name={m.n} size={22} /> : m.a)}
                {live && <span title="Live (API terhubung)" style={{ position: 'absolute', top: -3, right: -3, width: 9, height: 9, borderRadius: 5, background: T.green, border: `1.5px solid ${T.card}` }}></span>}
              </button>
            );
          })}
        </div>
        <VioBtn onClick={run} style={{ alignSelf: 'flex-start' }}>{state === 'busy' ? 'Running…' : '▶ Run Test'}</VioBtn>
      </Card>

      {(state === 'busy' || results.length > 0) && (
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))', gap: 14 }}>
          {(state === 'busy' ? sel.map((n) => ({ model: n, version: n, busy: true })) : results).map((res) => (
            <Card key={res.model} style={{ padding: 16, display: 'flex', flexDirection: 'column', gap: 10 }}>
              <span style={{ display: 'flex', alignItems: 'center', gap: 9 }}>
                <MChip name={res.model} size={22} />
                <span style={{ fontSize: 13, fontWeight: 700, color: T.text, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{res.version}</span>
                {!res.busy && <span style={{ marginLeft: 'auto', display: 'flex', gap: 6 }}>
                  {fastest === res.model && <Badge color={T.amber}>⚡ Tercepat</Badge>}
                  <Badge color={res.simulated ? T.dim : T.green}>{res.simulated ? 'Simulasi' : 'Live'}</Badge>
                </span>}
              </span>
              {res.busy ? (
                <ThinkingIndicator model={res.model} kind={outType} color={posModelColor[res.model] || T.vio} />
              ) : res.image ? (
                <img src={res.image} alt={res.model} style={{ width: '100%', borderRadius: 10, border: `1px solid ${T.line}`, display: 'block' }} />
              ) : (
                <div style={{ maxHeight: 240, overflowY: 'auto', paddingRight: 4 }}><LabRich text={res.text} /></div>
              )}
              {!res.busy && res.error && <span style={{ fontSize: 10.5, color: '#f87171' }}>API gagal → pakai placeholder: {res.error}</span>}
              {!res.busy && (
                <span style={{ marginTop: 'auto', display: 'flex', alignItems: 'center', gap: 12 }}>
                  <span style={{ fontSize: 11, color: T.dim }}>⏱ {res.ms}s</span>
                  {(res.kind === 'image' || res.kind === 'video')
                    ? <React.Fragment>
                        <span style={{ fontSize: 11, color: T.dim }}>{res.simulated ? 'placeholder' : (res.kind === 'video' ? 'video' : '1024×1024 PNG')}</span>
                        {res.tokens ? <span style={{ fontSize: 11, color: T.dim }}>{res.tokens} tokens</span> : null}
                      </React.Fragment>
                    : <React.Fragment>
                        <span style={{ fontSize: 11, color: T.dim }}>{res.tokens} tokens</span>
                        <span style={{ fontSize: 11, color: T.dim }}>{res.words} kata</span>
                      </React.Fragment>}
                  {res.text && <GhostBtn small style={{ marginLeft: 'auto', padding: '4px 11px' }}
                    onClick={() => { if (navigator.clipboard) navigator.clipboard.writeText(res.text); setCopied(res.model); setTimeout(() => setCopied((c) => (c === res.model ? null : c)), 1600); }}>
                    {copied === res.model ? '✓ Disalin' : '⧉ Copy'}</GhostBtn>}
                </span>
              )}
            </Card>
          ))}
        </div>
      )}

      {state === 'done' && results.length > 0 && (
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))', gap: 14 }}>
          <Card style={{ padding: 16 }}>
            <SectionTitle>Response Time</SectionTitle>
            <VBars h={96} data={results.map((r) => r.ms)}
              labels={results.map((r) => (posModelList.find((m) => m.n === r.model) || {}).a || r.model[0])} />
            <div style={{ fontSize: 10.5, color: T.dim, marginTop: 6, textAlign: 'center' }}>detik per respons (lebih pendek lebih baik)</div>
          </Card>
          <Card style={{ padding: 16, display: 'flex', flexDirection: 'column' }}>
            <SectionTitle>Token Usage</SectionTitle>
            <div style={{ display: 'flex', alignItems: 'center', gap: 14, flex: 1 }}>
              <Donut size={96} thick={11} title={String(totalTok)} sub="total"
                segs={totalTok > 0 ? results.map((r) => ({ v: r.tokens || 0, c: posModelColor[r.model] || T.vio })) : [{ v: 1, c: T.line2 }]} />
              <div style={{ display: 'flex', flexDirection: 'column', gap: 7, fontSize: 11 }}>
                {results.map((r) => (
                  <span key={r.model} style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
                    <span style={{ width: 8, height: 8, borderRadius: 3, background: posModelColor[r.model] || T.vio }}></span>
                    <span style={{ color: T.mut }}>{r.model}</span>
                    <span style={{ color: T.text, fontWeight: 700, marginLeft: 'auto', paddingLeft: 10 }}>{r.tokens}</span>
                  </span>
                ))}
              </div>
            </div>
          </Card>
          <Card style={{ padding: 16 }}>
            <SectionTitle>{isMediaResults ? 'Output' : 'Output Length'}</SectionTitle>
            {isMediaResults ? (
              <div style={{ display: 'flex', flexDirection: 'column', gap: 9 }}>
                {results.map((r) => (
                  <span key={r.model} style={{ display: 'flex', alignItems: 'center', gap: 9, fontSize: 12 }}>
                    <MChip name={r.model} size={16} />
                    <span style={{ color: T.text, fontWeight: 600 }}>{r.version}</span>
                    <Badge color={r.simulated ? T.dim : T.green}>{r.simulated ? 'Simulasi' : 'Live'}</Badge>
                    <span style={{ marginLeft: 'auto', color: T.dim }}>{r.kind === 'video' ? 'video' : '1024×1024'}</span>
                  </span>
                ))}
              </div>
            ) : (
              <HBarList rows={results.map((r) => ({ label: r.model, v: r.words, right: r.words + ' kata', c: posModelColor[r.model] || T.vio, icon: <MChip name={r.model} size={15} /> }))} />
            )}
          </Card>
        </div>
      )}

      <Card style={{ padding: 16 }}>
        <SectionTitle right={history.length > 5 && <GhostBtn small onClick={() => setShowAll((v) => !v)}>{showAll ? 'Sembunyikan' : 'View all'}</GhostBtn>}>Recent Tests</SectionTitle>
        {history.length === 0 ? (
          <div style={{ padding: '24px 14px', textAlign: 'center', fontSize: 12.5, color: T.dim }}>Belum ada test. Pilih prompt & model, lalu klik “Run Test”.</div>
        ) : (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
            {shownHistory.map((h, i) => (
              <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '10px 12px', borderRadius: 10, background: T.card2, border: `1px solid ${T.line}` }}>
                <span style={{ fontSize: 12.5, fontWeight: 700, color: T.text, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flex: 1 }}>{h.t}</span>
                <span style={{ display: 'inline-flex', gap: 4, flex: '0 0 auto' }}>
                  {h.models.map((m) => <MChip key={m} name={m} size={17} />)}
                </span>
                <Badge color={h.live ? T.green : T.dim}>{h.live ? 'Live' : 'Simulasi'}</Badge>
                <span style={{ fontSize: 11, color: T.dim, flex: '0 0 auto' }}>{labAgo(h.when)}</span>
              </div>
            ))}
          </div>
        )}
      </Card>

      {max !== Infinity && sel.length >= max && (
        <GhostBtn style={{ alignSelf: 'stretch' }} onClick={upsell}>🔒 Uji lebih banyak model — upgrade paket</GhostBtn>
      )}
      {keysModal && <LabKeysModal onClose={() => setKeysModal(false)} onSaved={setKeyStatus} />}
    </div>
  );
}

Object.assign(window, { Generator, Builder, Lab, LabKeysModal, FieldLabel });
