// PromptOS app — Library, Favorites, Trash, Agents, Insights, Knowledge Graph
function LibraryGrid({ items, favs, toggleFav, deletePrompt, editPrompt, openPrompt, empty }) {
  if (items.length === 0) {
    return (
      <div style={{
        margin: 22, padding: '80px 20px', textAlign: 'center', color: T.dim, fontSize: 13.5,
        border: `1.5px dashed ${T.line2}`, borderRadius: 14,
      }}>{empty}</div>
    );
  }
  return (
    <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(250px, 1fr))', gap: 14 }}>
      {items.map((p) => (
        <TrendCard key={p.id} p={p} fav={favs.has(p.id)} toggleFav={toggleFav}
          onDelete={deletePrompt && (() => deletePrompt(p.id))}
          onEdit={editPrompt && (() => editPrompt(p))}
          onOpen={openPrompt && (() => openPrompt(p))} />
      ))}
    </div>
  );
}

// dropdown single-select dengan ikon (kategori / koleksi)
function IconSelect({ value, onChange, options = [], placeholder = 'Pilih…', allowAdd, onAdd }) {
  const [open, setOpen] = React.useState(false);
  const [newName, setNewName] = React.useState('');
  const ref = React.useRef(null);
  React.useEffect(() => {
    const close = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', close);
    return () => document.removeEventListener('mousedown', close);
  }, []);
  const dot = (o, size) => (o.icon
    ? <span style={{ color: o.color || T.vio, display: 'flex', flex: '0 0 auto' }}><IconGlyph name={o.icon} size={size} color="currentColor" /></span>
    : <span style={{ width: 9, height: 9, borderRadius: 5, background: o.color || T.line2, flex: '0 0 auto', display: 'inline-block' }}></span>);
  let cur = options.find((o) => o.value === value);
  if (!cur && value && value !== 'None') cur = { value, label: value, icon: 'tag', color: T.vio };
  const addNow = () => { const n = newName.trim(); if (n && onAdd) { onAdd(n); } setNewName(''); setOpen(false); };
  return (
    <div ref={ref} style={{ position: 'relative' }}>
      <div onClick={() => setOpen((o) => !o)} style={{
        background: T.card2, border: `1px solid ${open ? T.line2 : T.line}`, borderRadius: 9,
        padding: '10px 12px', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 9,
      }}>
        {cur ? <>{dot(cur, 17)}<span style={{ fontSize: 13, color: T.text, fontWeight: 600 }}>{cur.label}</span></>
             : <span style={{ fontSize: 13, color: T.dim }}>{placeholder}</span>}
        <span style={{ marginLeft: 'auto', color: T.dim, fontSize: 10 }}>▾</span>
      </div>
      {open && (
        <div style={{
          position: 'absolute', top: '100%', left: 0, right: 0, zIndex: 40, marginTop: 4,
          background: T.card, border: `1px solid ${T.line2}`, borderRadius: 10,
          boxShadow: '0 12px 30px rgba(0,0,0,0.4)', padding: 6, maxHeight: 280, overflowY: 'auto',
        }}>
          {options.map((o) => {
            const on = o.value === value;
            return (
              <div key={o.value} onClick={() => { onChange(o.value); setOpen(false); }} style={{
                display: 'flex', alignItems: 'center', gap: 9, padding: '8px 9px', borderRadius: 8, cursor: 'pointer',
                background: on ? T.vioSoft : 'transparent',
              }}>
                {dot(o, 18)}
                <span style={{ fontSize: 13, color: T.text, fontWeight: on ? 700 : 500 }}>{o.label}</span>
                <span style={{ marginLeft: 'auto', color: on ? T.vio : 'transparent', fontSize: 13, fontWeight: 800 }}>✓</span>
              </div>
            );
          })}
          {allowAdd && (
            <div style={{ display: 'flex', gap: 6, padding: '6px 4px 2px', borderTop: `1px solid ${T.line}`, marginTop: 4 }}>
              <input value={newName} onChange={(e) => setNewName(e.target.value)}
                onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); addNow(); } }}
                placeholder="+ Kategori baru…" style={{
                  flex: 1, fontFamily: T.font, fontSize: 12.5, color: T.text, background: T.card2,
                  border: `1px solid ${T.line}`, borderRadius: 7, padding: '7px 9px', outline: 'none',
                }} />
              <GhostBtn small onClick={addNow}>Tambah</GhostBtn>
            </div>
          )}
        </div>
      )}
    </div>
  );
}

// dropdown multi-pilih model AI dengan logo
function ModelMultiSelect({ value = [], onChange }) {
  const [open, setOpen] = React.useState(false);
  const ref = React.useRef(null);
  React.useEffect(() => {
    const close = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', close);
    return () => document.removeEventListener('mousedown', close);
  }, []);
  const toggle = (n) => {
    const has = value.includes(n);
    const next = has ? value.filter((x) => x !== n) : [...value, n];
    if (next.length) onChange(next); // minimal 1 model
  };
  return (
    <div ref={ref} style={{ position: 'relative' }}>
      <div onClick={() => setOpen((o) => !o)} style={{
        background: T.card2, border: `1px solid ${open ? T.line2 : T.line}`, borderRadius: 9,
        padding: '7px 10px', minHeight: 42, cursor: 'pointer',
        display: 'flex', alignItems: 'center', gap: 6, flexWrap: 'wrap',
      }}>
        {value.length ? value.map((n) => (
          <span key={n} style={{ display: 'inline-flex', alignItems: 'center', gap: 6, background: T.card, border: `1px solid ${T.line}`, borderRadius: 7, padding: '3px 7px 3px 4px', fontSize: 12, color: T.text, fontWeight: 600 }}>
            <MChip name={n} size={16} />{n}
            <span onClick={(e) => { e.stopPropagation(); toggle(n); }} title="Hapus" style={{ color: T.dim, fontSize: 13, lineHeight: 1, cursor: 'pointer' }}>×</span>
          </span>
        )) : <span style={{ fontSize: 13, color: T.dim }}>Pilih model AI…</span>}
        <span style={{ marginLeft: 'auto', color: T.dim, fontSize: 10 }}>▾</span>
      </div>
      {open && (
        <div style={{
          position: 'absolute', top: '100%', left: 0, right: 0, zIndex: 40, marginTop: 4,
          background: T.card, border: `1px solid ${T.line2}`, borderRadius: 10,
          boxShadow: '0 12px 30px rgba(0,0,0,0.4)', padding: 6, maxHeight: 260, overflowY: 'auto',
        }}>
          {posModelList.map((m) => {
            const on = value.includes(m.n);
            return (
              <div key={m.n} onClick={() => toggle(m.n)} style={{
                display: 'flex', alignItems: 'center', gap: 9, padding: '8px 9px', borderRadius: 8, cursor: 'pointer',
                background: on ? T.vioSoft : 'transparent',
              }}>
                <MChip name={m.n} size={20} />
                <span style={{ fontSize: 13, color: T.text, fontWeight: on ? 700 : 500 }}>{m.n}</span>
                <span style={{ marginLeft: 'auto', color: on ? T.vio : T.line2, fontSize: 13, fontWeight: 800 }}>{on ? '✓' : ''}</span>
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

function PromptModal({ initial, cols = [], defaultCol, catOptions = [], categories = [], onSave, onClose, forceNew }) {
  const isEdit = !!initial && !forceNew; // forceNew: prefill tapi tetap mode "prompt baru" (mis. dari AI Generator)
  // opsi kategori (dengan ikon/warna) + kategori kustom yang sudah ada
  const [extraCats, setExtraCats] = React.useState([]);
  const catMeta = {}; categories.forEach((c) => { catMeta[c.name] = c; });
  const catNames = [...new Set(['Marketing', 'Image Generation', 'Coding', 'Research', 'Video', ...categories.map((c) => c.name), ...catOptions, ...extraCats])];
  const catSelectOptions = catNames.map((n) => ({ value: n, label: n, icon: (catMeta[n] && catMeta[n].icon) || 'tag', color: (catMeta[n] && catMeta[n].color) || T.vio }));
  const colSelectOptions = [{ value: 'None', label: 'Tidak ada koleksi', icon: null, color: T.line2 }, ...cols.map(([n, color, icon]) => ({ value: n, label: n, icon: icon || 'folder', color: color || T.vio }))];
  const [t, setT] = React.useState(initial ? initial.t : '');
  const [d, setD] = React.useState(initial ? initial.d : '');
  const [models, setModels] = React.useState(() => {
    if (initial && Array.isArray(initial.models) && initial.models.length) return initial.models;
    return initial && initial.m ? [initial.m] : ['ChatGPT'];
  });
  const [cat, setCat] = React.useState(initial ? initial.cat : 'Marketing');
  const [body, setBody] = React.useState(initial ? initial.body || '' : '');
  const [img, setImg] = React.useState(initial ? initial.img || '' : '');
  const [col, setCol] = React.useState(initial ? initial.col || 'None' : defaultCol || 'None');
  // daftar contoh output (bisa lebih dari satu untuk menunjukkan konsistensi)
  const [outputs, setOutputs] = React.useState(() => {
    if (initial && Array.isArray(initial.outputs) && initial.outputs.length) {
      return initial.outputs.map((o) => ({ img: o.img || '', text: o.text || '' }));
    }
    if (initial && (initial.outImg || initial.sample)) {
      return [{ img: initial.outImg || '', text: initial.sample || '' }];
    }
    return [];
  });
  const [upIdx, setUpIdx] = React.useState(-1);

  const addOutput = () => setOutputs((o) => [...o, { img: '', text: '' }]);
  const rmOutput = (i) => setOutputs((o) => o.filter((_, j) => j !== i));
  const setOut = (i, key, val) => setOutputs((o) => o.map((x, j) => (j === i ? { ...x, [key]: val } : x)));
  const uploadTo = (i, file) => {
    if (!file) return;
    setUpIdx(i);
    const reader = new FileReader();
    reader.onload = () => {
      posAuthFetch('/upload', { method: 'POST', body: JSON.stringify({ dataUrl: reader.result }) })
        .then((r) => r.json())
        .then((d) => { if (d.url) setOut(i, 'img', d.url); else alert(d.error || 'Upload gagal'); })
        .catch(() => alert('Upload gagal'))
        .finally(() => setUpIdx(-1));
    };
    reader.readAsDataURL(file);
  };

  const inputStyle = {
    fontFamily: T.font, fontSize: 13, color: T.text, background: T.card2,
    border: `1px solid ${T.line}`, borderRadius: 9, padding: '11px 12px', outline: 'none',
    width: '100%', boxSizing: 'border-box', lineHeight: 1.55,
  };
  const taStyle = (min) => ({ ...inputStyle, resize: 'vertical', minHeight: min, display: 'block' });

  const save = () => {
    if (!t.trim()) return;
    const clean = outputs.filter((o) => (o.img || (o.text || '').trim()));
    // outputs jadi sumber tunggal; legacy sample/outImg dikosongkan
    onSave({ t: t.trim(), d: d.trim() || 'Custom prompt', m: models[0] || 'ChatGPT', models, cat, body, outputs: clean, sample: '', outImg: '', img, col: col === 'None' ? '' : col });
    onClose();
  };

  return (
    <div onClick={onClose} style={{
      position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.6)', zIndex: 50,
      display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 20,
    }}>
      <div onClick={(e) => e.stopPropagation()} style={{
        width: 500, maxHeight: '90vh', background: T.card, border: `1px solid ${T.line2}`,
        borderRadius: 16, display: 'flex', flexDirection: 'column', overflow: 'hidden',
      }}>
        <div style={{ padding: '18px 22px', borderBottom: `1px solid ${T.line}`, fontSize: 17, fontWeight: 800, color: T.text }}>
          {isEdit ? 'Edit Prompt' : 'New Prompt'}
        </div>
        <div style={{ padding: 22, overflowY: 'auto', display: 'flex', flexDirection: 'column', gap: 12, flex: 1, minHeight: 0 }}>
          <FieldLabel>Title</FieldLabel>
          <input autoFocus value={t} onChange={(e) => setT(e.target.value)}
            placeholder="cth: Email Subject Line Writer" style={inputStyle} />
          <FieldLabel>Description</FieldLabel>
          <textarea value={d} onChange={(e) => setD(e.target.value)}
            placeholder="Deskripsi singkat prompt ini…" style={taStyle(54)} />
          <FieldLabel>Prompt</FieldLabel>
          <textarea value={body} onChange={(e) => setBody(e.target.value)}
            placeholder="Tulis isi prompt lengkap di sini…" style={taStyle(120)} />

          <FieldLabel>Example outputs</FieldLabel>
          <span style={{ fontSize: 11, color: T.dim, marginTop: -4 }}>Tambahkan beberapa contoh untuk menunjukkan prompt ini konsisten.</span>
          {outputs.map((o, i) => (
            <div key={i} style={{ border: `1px solid ${T.line}`, borderRadius: 11, padding: 12, background: T.card2, display: 'flex', flexDirection: 'column', gap: 9 }}>
              <div style={{ display: 'flex', alignItems: 'center' }}>
                <span style={{ fontSize: 11.5, fontWeight: 700, color: T.mut }}>Output {i + 1}</span>
                <button onClick={() => rmOutput(i)} title="Hapus output" style={{
                  marginLeft: 'auto', background: 'transparent', border: 'none', color: T.dim, cursor: 'pointer', fontSize: 15,
                }}>×</button>
              </div>
              {o.img ? (
                <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
                  <img src={o.img} alt="" style={{ width: 84, height: 56, objectFit: 'cover', borderRadius: 8, border: `1px solid ${T.line2}` }} />
                  <span style={{ fontSize: 11, color: T.dim, flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{o.img}</span>
                  <GhostBtn small onClick={() => setOut(i, 'img', '')}>Ganti</GhostBtn>
                </div>
              ) : (
                <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
                  <label style={{
                    fontFamily: T.font, fontSize: 12, fontWeight: 600, color: T.text, cursor: 'pointer', flex: '0 0 auto',
                    background: T.card, border: `1px solid ${T.line2}`, borderRadius: 9, padding: '8px 12px',
                  }}>
                    {upIdx === i ? 'Mengunggah…' : '⬆ Upload'}
                    <input type="file" accept="image/*" style={{ display: 'none' }}
                      onChange={(e) => uploadTo(i, e.target.files && e.target.files[0])} />
                  </label>
                  <input value={o.img} onChange={(e) => setOut(i, 'img', e.target.value)}
                    placeholder="atau URL gambar" style={{ ...inputStyle, fontSize: 12 }} />
                </div>
              )}
              <textarea value={o.text} onChange={(e) => setOut(i, 'text', e.target.value)}
                placeholder="Teks hasil (opsional) — mis. caption / isi dokumen / prompt akhir" style={taStyle(56)} />
            </div>
          ))}
          <GhostBtn small onClick={addOutput} style={{ alignSelf: 'flex-start' }}>+ Tambah output</GhostBtn>

          <FieldLabel>AI Model (bisa lebih dari satu)</FieldLabel>
          <ModelMultiSelect value={models} onChange={setModels} />
          <FieldLabel>Category</FieldLabel>
          <IconSelect value={cat} onChange={setCat} options={catSelectOptions} placeholder="Pilih kategori…"
            allowAdd onAdd={(n) => { setExtraCats((e) => [...new Set([...e, n])]); setCat(n); }} />
          <FieldLabel>Collection</FieldLabel>
          <IconSelect value={col} onChange={setCol} options={colSelectOptions} placeholder="Pilih koleksi…" />
          <FieldLabel>Cover image</FieldLabel>
          <div style={{ display: 'flex', gap: 7, flexWrap: 'wrap' }}>
            {posCoverOptions.map((src) => (
              <img key={src} src={src} alt="" onClick={() => setImg(src)} style={{
                width: 64, height: 38, objectFit: 'cover', borderRadius: 7, cursor: 'pointer',
                border: `2px solid ${img === src ? T.vio : 'transparent'}`,
                opacity: img && img !== src ? 0.55 : 1,
              }} />
            ))}
          </div>
          <input value={img} onChange={(e) => setImg(e.target.value)}
            placeholder="…atau tempel URL gambar custom" style={{ ...inputStyle, fontSize: 12 }} />
        </div>
        <div style={{ padding: '14px 22px', borderTop: `1px solid ${T.line}`, display: 'flex', gap: 10, justifyContent: 'flex-end' }}>
          <GhostBtn small onClick={onClose}>Cancel</GhostBtn>
          <VioBtn small onClick={save}>{isEdit ? 'Save changes' : 'Save prompt'}</VioBtn>
        </div>
      </div>
    </div>
  );
}

// kategori → jenis tampilan output
const posOutKind = {
  'Image Generation': { type: 'image', label: 'Image', file: 'ai-render.png' },
  'Video': { type: 'video', label: 'Video', file: 'ai-clip.mp4' },
  'Coding': { type: 'code', label: 'Code', file: 'output.txt' },
  'Marketing': { type: 'doc', label: 'Document', file: 'draft.md' },
  'Research': { type: 'doc', label: 'Document', file: 'report.md' },
};

function OutCopy({ text }) {
  const [c, setC] = React.useState(false);
  return (
    <GhostBtn small onClick={() => { if (navigator.clipboard) navigator.clipboard.writeText(text); setC(true); setTimeout(() => setC(false), 1400); }}>
      {c ? '✓ Copied' : 'Copy'}
    </GhostBtn>
  );
}

// Empty-state saat prompt belum punya contoh hasil
function OutputEmpty({ p, canEdit, onEdit }) {
  const kind = (posOutKind[p.cat] || { type: 'doc', label: 'Document' });
  const hint = {
    image: 'kartu opsi gambar', video: 'preview video', code: 'kartu kode', doc: 'kartu dokumen',
  }[kind.type] || 'kartu dokumen';
  return (
    <div style={{
      border: `1.5px dashed ${T.line2}`, borderRadius: 12, padding: '28px 20px', textAlign: 'center',
      display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 10,
    }}>
      <span style={{ fontSize: 13, color: T.dim, lineHeight: 1.55, maxWidth: 360 }}>
        Belum ada contoh hasil. Tambahkan satu untuk menampilkannya sebagai <b style={{ color: T.mut }}>{hint}</b> ({kind.label}).
      </span>
      {canEdit
        ? <VioBtn small onClick={onEdit}>✎ Tambah contoh hasil</VioBtn>
        : <span style={{ fontSize: 11.5, color: T.dim }}>Minta editor untuk menambahkannya.</span>}
    </div>
  );
}

// kartu media (gambar/video) dengan label opsi
function MediaTile({ src, hue, video, label, height = 132 }) {
  return (
    <div style={{ position: 'relative', borderRadius: 12, overflow: 'hidden' }}>
      {src ? <img src={src} alt="" style={{ width: '100%', display: 'block', height, objectFit: 'cover' }} />
           : <Cover hue={hue} label="" height={height} />}
      {video && (
        <span style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
          <span style={{
            width: 48, height: 48, borderRadius: 24, background: 'rgba(8,8,14,0.6)',
            border: '2px solid rgba(255,255,255,0.85)', display: 'flex', alignItems: 'center', justifyContent: 'center',
          }}>
            <span style={{ width: 0, height: 0, marginLeft: 3, borderTop: '8px solid transparent', borderBottom: '8px solid transparent', borderLeft: '13px solid #fff' }}></span>
          </span>
        </span>
      )}
      {label && <span style={{
        position: 'absolute', left: 8, bottom: 8, fontSize: 10.5, fontWeight: 700, color: '#fff',
        background: 'rgba(8,8,14,0.7)', borderRadius: 6, padding: '3px 8px',
      }}>{label}</span>}
    </div>
  );
}

// Render contoh hasil sesuai jenisnya; mendukung banyak output (konsistensi)
function OutputPreview({ p }) {
  const kind = (posOutKind[p.cat] || { type: 'doc', label: 'Document', file: 'output.md' });
  // satukan: utamakan outputs[], fallback ke legacy sample/outImg
  let items = (Array.isArray(p.outputs) && p.outputs.length) ? p.outputs : [];
  if (!items.length && (p.sample || p.outImg)) items = [{ img: p.outImg || '', text: p.sample || '' }];
  if (!items.length) return null;

  const isMedia = kind.type === 'image' || kind.type === 'video';
  const video = kind.type === 'video';

  if (isMedia) {
    const imgs = items.filter((o) => o.img);
    const texts = items.map((o) => o.text).filter((x) => x && x.trim());
    if (imgs.length) {
      const cols = imgs.length === 1 ? 1 : (imgs.length === 2 ? 2 : 3);
      return (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
          <div style={{ display: 'grid', gridTemplateColumns: `repeat(${cols}, 1fr)`, gap: 10 }}>
            {imgs.map((o, i) => (
              <MediaTile key={i} src={o.img} video={video} label={`Output ${i + 1}`} height={imgs.length === 1 ? 260 : 150} />
            ))}
          </div>
          <span style={{ fontSize: 11.5, color: T.dim }}>
            {imgs.length} {video ? 'klip' : 'gambar'} hasil{imgs.length > 1 ? ' — menunjukkan konsistensi prompt' : ''}
          </span>
          {texts.length > 0 && (
            <details style={{ fontSize: 12.5, color: T.mut }}>
              <summary style={{ cursor: 'pointer', color: T.vio, fontWeight: 600, fontSize: 12 }}>Lihat teks hasil ({texts.length})</summary>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginTop: 10 }}>
                {texts.map((tx, i) => (
                  <pre key={i} style={{
                    margin: 0, whiteSpace: 'pre-wrap', fontFamily: T.font, fontSize: 12.5, lineHeight: 1.65,
                    color: T.mut, background: T.card2, border: `1px solid ${T.line}`, borderRadius: 10, padding: 14,
                  }}>{tx}</pre>
                ))}
              </div>
            </details>
          )}
        </div>
      );
    }
    // belum ada gambar nyata → mock variasi (image) atau satu preview (video)
    const hues = video ? [p.hue] : [p.hue, (p.hue + 24) % 360, (p.hue + 336) % 360];
    return (
      <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
        <div style={{ display: 'grid', gridTemplateColumns: `repeat(${hues.length}, 1fr)`, gap: 10 }}>
          {hues.map((h, i) => (
            <MediaTile key={i} hue={h} video={video} label={video ? '' : `Option ${i + 1}`} height={video ? 210 : 132} />
          ))}
        </div>
        <span style={{ fontSize: 11.5, color: T.dim }}>{video ? 'Preview video AI · 1 continuous shot' : `${hues.length} variasi gambar dihasilkan · 4:5`}</span>
        {texts.map((tx, i) => (
          <pre key={i} style={{
            margin: 0, whiteSpace: 'pre-wrap', fontFamily: T.font, fontSize: 12.5, lineHeight: 1.65,
            color: T.mut, background: T.card2, border: `1px solid ${T.line}`, borderRadius: 10, padding: 14,
          }}>{tx}</pre>
        ))}
      </div>
    );
  }

  // dokumen / kode — satu kartu bergaya file per output
  const mono = kind.type === 'code';
  const docs = items.map((o) => o.text).filter((x) => x && x.trim());
  if (!docs.length) return null;
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
      {docs.map((tx, i) => (
        <div key={i} style={{ border: `1px solid ${T.line2}`, borderRadius: 12, overflow: 'hidden', background: T.card2 }}>
          <div style={{
            display: 'flex', alignItems: 'center', gap: 9, padding: '9px 13px',
            borderBottom: `1px solid ${T.line}`, background: T.card,
          }}>
            <span style={{ display: 'inline-flex', gap: 5 }}>
              {['#ff5f57', '#febc2e', '#28c840'].map((c) => (
                <span key={c} style={{ width: 9, height: 9, borderRadius: 5, background: c }}></span>
              ))}
            </span>
            <span style={{ fontSize: 11.5, color: T.mut, fontFamily: 'monospace' }}>
              {docs.length > 1 ? `${kind.file.replace('.', '-' + (i + 1) + '.')}` : kind.file}
            </span>
            <span style={{ marginLeft: 'auto' }}><OutCopy text={tx} /></span>
          </div>
          <pre style={{
            margin: 0, whiteSpace: 'pre-wrap', fontSize: mono ? 12.5 : 13, lineHeight: 1.7, padding: 16,
            fontFamily: mono ? 'ui-monospace, SFMono-Regular, Menlo, monospace' : T.font,
            color: mono ? T.text : T.mut,
          }}>{tx}</pre>
        </div>
      ))}
      {docs.length > 1 && <span style={{ fontSize: 11.5, color: T.dim }}>{docs.length} contoh hasil — menunjukkan konsistensi prompt</span>}
    </div>
  );
}

function PromptDetail({ p, fav, toggleFav, onBack, onEdit, onDelete, setRoute, cap = { edit: true } }) {
  const [copied, setCopied] = React.useState(false);
  const body = p.body || `${p.d}.\n\n(Isi prompt belum ditulis — klik Edit untuk menambahkannya.)`;
  const copy = () => {
    if (navigator.clipboard) navigator.clipboard.writeText(body);
    setCopied(true);
    setTimeout(() => setCopied(false), 1500);
  };
  const openBuilder = () => {
    localStorage.setItem('pos.builderPreset', JSON.stringify([{ k: 'Task', txt: body }]));
    setRoute && setRoute('builder');
  };
  return (
    <div style={{ padding: 22, display: 'flex', flexDirection: 'column', gap: 16, maxWidth: 1100 }}>
      <div>
        <GhostBtn small onClick={onBack}>← Back to Library</GhostBtn>
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 300px', gap: 18, alignItems: 'start' }}>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 16, minWidth: 0 }}>
          <Card style={{ padding: 12, position: 'relative' }}>
            <Cover hue={p.hue} img={p.img} height={240} />
            {cap.edit && <button onClick={onEdit} title="Ganti cover" style={{
              position: 'absolute', right: 22, bottom: 22, display: 'inline-flex', alignItems: 'center', gap: 6,
              background: 'rgba(8,8,14,0.78)', border: 'none', borderRadius: 8, cursor: 'pointer',
              color: '#fff', fontSize: 11.5, fontWeight: 700, fontFamily: T.font, padding: '7px 11px',
            }}>🖼 Change cover</button>}
          </Card>
          <div>
            <div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
              <span style={{ fontSize: 22, fontWeight: 800, color: T.text, letterSpacing: '-0.01em' }}>{p.t}</span>
              <Badge color={T.vio}>{p.cat}</Badge>
              {p.col && <Badge color="#60a5fa">▣ {p.col}</Badge>}
            </div>
            <div style={{ fontSize: 13, color: T.mut, marginTop: 6, lineHeight: 1.55 }}>{p.d}</div>
          </div>
          <Card style={{ padding: 18 }}>
            <SectionTitle right={<GhostBtn small onClick={copy}>{copied ? '✓ Copied' : 'Copy prompt'}</GhostBtn>}>Prompt</SectionTitle>
            <pre style={{
              margin: 0, whiteSpace: 'pre-wrap', fontFamily: T.font, fontSize: 13.5, lineHeight: 1.7,
              color: T.text, background: T.card2, border: `1px solid ${T.line}`, borderRadius: 12, padding: 16,
            }}>{body}</pre>
          </Card>
          <Card style={{ padding: 18 }}>
            <SectionTitle right={<Badge color="#60a5fa">{(posOutKind[p.cat] || { label: 'Document' }).label}</Badge>}>
              <span style={{ display: 'inline-flex', alignItems: 'center', gap: 8 }}>
                <span style={{ width: 7, height: 7, borderRadius: 4, background: ((p.outputs && p.outputs.length) || p.sample || p.outImg) ? T.green : T.dim, flex: '0 0 auto' }}></span>
                Example Output
              </span>
            </SectionTitle>
            {((p.outputs && p.outputs.length) || p.sample || p.outImg)
              ? <OutputPreview p={p} />
              : <OutputEmpty p={p} canEdit={cap.edit} onEdit={onEdit} />}
          </Card>
        </div>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
          <Card style={{ padding: 16, display: 'flex', flexDirection: 'column', gap: 12 }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
              {(() => {
                const ms = (p.models && p.models.length ? p.models : [p.m]).filter(Boolean);
                return <>
                  <span style={{ display: 'inline-flex' }}>{ms.slice(0, 4).map((n, i) => (
                    <span key={n} style={{ marginLeft: i ? -8 : 0 }}><MChip name={n} size={30} /></span>
                  ))}</span>
                  <span style={{ display: 'flex', flexDirection: 'column', minWidth: 0 }}>
                    <span style={{ fontSize: 13.5, fontWeight: 700, color: T.text, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{ms.join(', ')}</span>
                    <span style={{ fontSize: 10.5, color: T.dim }}>Optimized for</span>
                  </span>
                </>;
              })()}
              <button onClick={() => toggleFav(p.id)} title="Favorite" style={{
                marginLeft: 'auto', width: 32, height: 32, borderRadius: 9, cursor: 'pointer',
                background: T.card2, border: `1px solid ${T.line}`,
                color: fav ? T.amber : T.dim, fontSize: 15,
              }}>{fav ? '★' : '☆'}</button>
            </div>
            <div style={{ display: 'flex', gap: 10 }}>
              <span style={{
                flex: 1, display: 'flex', flexDirection: 'column', gap: 4, alignItems: 'center',
                background: T.card2, border: `1px solid ${T.line}`, borderRadius: 10, padding: '10px 8px',
              }}>
                <Stars r={p.r} />
                <span style={{ fontSize: 10, color: T.dim }}>Rating</span>
              </span>
              <span style={{
                flex: 1, display: 'flex', flexDirection: 'column', gap: 4, alignItems: 'center',
                background: T.card2, border: `1px solid ${T.line}`, borderRadius: 10, padding: '10px 8px',
              }}>
                <span style={{ fontSize: 13, fontWeight: 700, color: T.text }}>{p.u}</span>
                <span style={{ fontSize: 10, color: T.dim }}>Uses</span>
              </span>
            </div>
          </Card>
          <Card style={{ padding: 16, display: 'flex', flexDirection: 'column', gap: 8 }}>
            {cap.edit && <VioBtn small onClick={onEdit}>✎ Edit prompt</VioBtn>}
            <GhostBtn small onClick={openBuilder}>Open in Builder</GhostBtn>
            <GhostBtn small onClick={() => setRoute && setRoute('lab')}>Test in Lab</GhostBtn>
            {cap.edit && <GhostBtn small style={{ color: '#f87171', borderColor: 'rgba(248,113,113,0.4)' }}
              onClick={() => { onDelete(p.id); onBack(); }}>Move to trash</GhostBtn>}
          </Card>
        </div>
      </div>
    </div>
  );
}

function Library({ lib, favs, toggleFav, query, addPrompt, updatePrompt, deletePrompt, setRoute, cols = [], categories = [], activeCol, clearCol, cap = { edit: true }, limits = {}, onUpgrade, openId, onConsumeOpen }) {
  const promptCap = limits.prompts || Infinity;
  const atPromptLimit = lib.length >= promptCap;
  const [cat, setCat] = React.useState('All');
  const [modal, setModal] = React.useState(null); // null | 'new' | prompt yang diedit
  const [viewId, setViewId] = React.useState(null); // id prompt yang detailnya sedang dibuka
  // buka detail saat diminta dari luar (dropdown search)
  React.useEffect(() => {
    if (openId) { setViewId(openId); onConsumeOpen && onConsumeOpen(); }
  }, [openId]);
  const savePrompt = (v) => {
    if (modal === 'new') {
      addPrompt({
        id: 'u' + Date.now(), ...v,
        r: 5.0, u: '0', hue: Math.floor(Math.random() * 360), img: v.img || posCatCovers[v.cat],
      });
    } else {
      // pakai cover pilihan user; kalau tak diubah dan kategori berganti, ikuti cover kategori
      const img = v.img !== (modal.img || '') ? v.img : (v.cat !== modal.cat ? posCatCovers[v.cat] : modal.img);
      updatePrompt(modal.id, { ...v, img });
    }
  };
  // kategori dinamis: gabungan kategori bawaan + kategori (kustom) yang ada di Library
  const baseCats = ['Image Generation', 'Marketing', 'Coding', 'Research', 'Video'];
  const catOptions = [...new Set([...baseCats, ...categories.map((c) => c.name), ...lib.map((p) => p.cat).filter(Boolean)])];
  const cats = ['All', ...catOptions];
  const catMetaL = {}; categories.forEach((c) => { catMetaL[c.name] = c; });
  const filterOptions = [{ value: 'All', label: 'Semua kategori', icon: 'grid', color: T.mut },
    ...catOptions.map((n) => ({ value: n, label: n, icon: (catMetaL[n] && catMetaL[n].icon) || 'tag', color: (catMetaL[n] && catMetaL[n].color) || T.vio }))];
  const q = (query || '').toLowerCase();
  const items = lib.filter((p) =>
    (cat === 'All' || p.cat === cat) &&
    (!activeCol || p.col === activeCol) &&
    (!q || (p.t + p.d + p.m + p.cat).toLowerCase().includes(q))
  );

  const viewed = viewId && lib.find((p) => p.id === viewId);
  if (viewed) {
    return (
      <React.Fragment>
        <PromptDetail p={viewed} fav={favs.has(viewed.id)} toggleFav={toggleFav}
          onBack={() => setViewId(null)} onEdit={() => setModal(viewed)}
          onDelete={deletePrompt} setRoute={setRoute} cap={cap} />
        {modal && <PromptModal initial={modal === 'new' ? null : modal} cols={cols} defaultCol={activeCol} catOptions={catOptions} categories={categories}
          onSave={savePrompt} onClose={() => setModal(null)} />}
      </React.Fragment>
    );
  }

  return (
    <div style={{ padding: 22, display: 'flex', flexDirection: 'column', gap: 16 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
        <div>
          <div style={{ fontSize: 20, fontWeight: 800, color: T.text }}>Prompt Library</div>
          <div style={{ fontSize: 12.5, color: T.mut, marginTop: 3 }}>
            {query ? `Hasil pencarian untuk “${query}”` : 'Simpan ribuan prompt dengan kategori dan tag pintar.'}
          </div>
        </div>
        {activeCol && (
          <span style={{
            display: 'inline-flex', alignItems: 'center', gap: 8,
            background: T.vioSoft, border: `1px solid ${T.vio}44`, borderRadius: 99,
            padding: '6px 13px', fontSize: 12, fontWeight: 700, color: T.vio,
          }}>
            ▣ {activeCol}
            <span onClick={clearCol} title="Tampilkan semua prompt"
              style={{ cursor: 'pointer', fontSize: 14, lineHeight: 1 }}>×</span>
          </span>
        )}
        {cap.edit && (
          <span style={{ marginLeft: 'auto', display: 'inline-flex', alignItems: 'center', gap: 10 }}>
            {promptCap !== Infinity && (
              <span style={{ fontSize: 11.5, color: atPromptLimit ? '#f87171' : T.dim, fontWeight: 600 }}>{lib.length}/{promptCap} prompt</span>
            )}
            {atPromptLimit
              ? <VioBtn small onClick={() => onUpgrade && onUpgrade({ feature: 'Prompt tak terbatas', plan: 'Pro', desc: `Paket Anda dibatasi ${promptCap} prompt. Upgrade ke Pro untuk menyimpan tanpa batas.` })}>🔒 Upgrade untuk tambah</VioBtn>
              : <VioBtn small onClick={() => setModal('new')}>+ New prompt</VioBtn>}
          </span>
        )}
      </div>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
        <span style={{ fontSize: 12.5, color: T.mut, fontWeight: 600 }}>Kategori</span>
        <span style={{ width: 220 }}><IconSelect value={cat} onChange={setCat} options={filterOptions} /></span>
        {cat !== 'All' && (
          <button onClick={() => setCat('All')} title="Reset filter" style={{
            fontFamily: T.font, fontSize: 12, fontWeight: 600, cursor: 'pointer', color: T.mut,
            background: 'transparent', border: 'none', padding: '4px 6px',
          }}>× reset</button>
        )}
        <span style={{ marginLeft: 'auto', fontSize: 11.5, color: T.dim }}>{items.length} prompt</span>
      </div>
      <LibraryGrid items={items} favs={favs} toggleFav={toggleFav}
        deletePrompt={cap.edit ? deletePrompt : null}
        editPrompt={cap.edit ? (p) => setModal(p) : null}
        openPrompt={(p) => setViewId(p.id)}
        empty="Tidak ada prompt yang cocok dengan pencarian Anda." />
      {modal && <PromptModal initial={modal === 'new' ? null : modal} cols={cols} defaultCol={activeCol} catOptions={catOptions} categories={categories}
        onSave={savePrompt} onClose={() => setModal(null)} />}
    </div>
  );
}

function Favorites({ lib, favs, toggleFav }) {
  const items = lib.filter((p) => favs.has(p.id));
  return (
    <div style={{ padding: 22, display: 'flex', flexDirection: 'column', gap: 16 }}>
      <div style={{ fontSize: 20, fontWeight: 800, color: T.text }}>Favorites</div>
      <LibraryGrid items={items} favs={favs} toggleFav={toggleFav}
        empty="Belum ada favorit — klik ☆ pada prompt mana pun untuk menyimpannya di sini." />
    </div>
  );
}

function Trash({ trash, restorePrompt, purgePrompt, emptyTrash, cap = { edit: true } }) {
  return (
    <div style={{ padding: 22, maxWidth: 760, display: 'flex', flexDirection: 'column', gap: 16 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
        <div style={{ fontSize: 20, fontWeight: 800, color: T.text }}>Trash</div>
        {cap.edit && trash.length > 0 && (
          <GhostBtn small style={{ marginLeft: 'auto' }} onClick={emptyTrash}>Empty trash</GhostBtn>
        )}
      </div>
      {trash.length === 0 ? (
        <div style={{
          padding: '90px 20px', textAlign: 'center', color: T.dim, fontSize: 13.5,
          border: `1.5px dashed ${T.line2}`, borderRadius: 14,
        }}>Trash kosong. Prompt yang dihapus akan disimpan di sini selama 30 hari.</div>
      ) : (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
          {trash.map((p) => (
            <Card key={p.id} style={{ padding: '14px 16px', display: 'flex', alignItems: 'center', gap: 14 }}>
              <MChip name={p.m} size={26} />
              <span style={{ display: 'flex', flexDirection: 'column', gap: 3, minWidth: 0 }}>
                <span style={{ fontSize: 14, fontWeight: 700, color: T.text }}>{p.t}</span>
                <span style={{ fontSize: 12, color: T.mut }}>{p.d}</span>
              </span>
              {cap.edit && (
                <span style={{ marginLeft: 'auto', display: 'flex', gap: 8, flex: '0 0 auto' }}>
                  <GhostBtn small onClick={() => restorePrompt(p.id)}>Restore</GhostBtn>
                  <GhostBtn small style={{ color: '#f87171', borderColor: 'rgba(248,113,113,0.4)' }}
                    onClick={() => purgePrompt(p.id)}>Delete forever</GhostBtn>
                </span>
              )}
            </Card>
          ))}
        </div>
      )}
    </div>
  );
}

// langkah pipeline default tiap template (statistik run dihitung nyata dari log, bukan di-hardcode)
const posAgentMeta = {
  'Content Creator Agent': { steps: ['Research', 'Write', 'Review', 'Publish'] },
  'Research Analyst Agent': { steps: ['Collect', 'Analyze', 'Insight'] },
  'Coding Assistant Agent': { steps: ['Plan', 'Code', 'Test', 'Optimize'] },
  'Business Analyst Agent': { steps: ['Data', 'Model', 'Report'] },
};

// daftar agent default → dipakai saat localStorage kosong (tanpa angka run palsu)
const agentSlug = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
const posAgentDefaults = posAgents.map((a) => ({
  id: agentSlug(a.t), t: a.t, d: a.d, c: a.c, steps: posAgentMeta[a.t].steps, custom: false,
}));

// buat & simpan agent baru ke localStorage (dipakai modal Agents maupun Prompt Builder).
// menjaga agent default tetap ada bila storage masih kosong.
function posCreateAgent(v) {
  const id = agentSlug(v.t || 'agent') + '-' + Date.now().toString(36);
  const agent = {
    id, t: v.t, d: v.d || 'Custom AI agent', c: v.c || posSwatches[0],
    steps: v.steps && v.steps.length ? v.steps : ['Plan', 'Execute', 'Review'], custom: true,
    category: v.category, system: v.system, inputs: v.inputs, output: v.output, tools: v.tools, knowledge: v.knowledge, tone: v.tone, lang: v.lang,
  };
  let list; try { list = JSON.parse(localStorage.getItem('pos.agents')); } catch (e) {}
  if (!Array.isArray(list)) list = posAgentDefaults.slice();
  try { localStorage.setItem('pos.agents', JSON.stringify([agent, ...list])); } catch (e) {}
  let runs; try { runs = JSON.parse(localStorage.getItem('pos.agentRuns')); } catch (e) {}
  if (!runs || typeof runs !== 'object') runs = {};
  runs[id] = [];
  try { localStorage.setItem('pos.agentRuns', JSON.stringify(runs)); } catch (e) {}
  return agent;
}

function Agents({ setRoute }) {
  const [agents, setAgents] = React.useState(() => {
    try { const s = localStorage.getItem('pos.agents'); if (s) return JSON.parse(s); } catch (e) {}
    return posAgentDefaults;
  });
  const [runs, setRuns] = React.useState(() => {
    try { const s = localStorage.getItem('pos.agentRuns'); if (s) return JSON.parse(s); } catch (e) {}
    const r = {}; posAgentDefaults.forEach((a) => { r[a.id] = []; }); return r;
  });
  const [modal, setModal] = React.useState(false);      // modal New Agent
  const [logsId, setLogsId] = React.useState(null);     // id agent untuk modal Logs
  const [runningId, setRunningId] = React.useState(null);

  React.useEffect(() => { try { localStorage.setItem('pos.agents', JSON.stringify(agents)); } catch (e) {} }, [agents]);
  React.useEffect(() => { try { localStorage.setItem('pos.agentRuns', JSON.stringify(runs)); } catch (e) {} }, [runs]);

  const useTemplate = (a) => {
    // rakit blok Prompt Builder dari tiap field agent (selaras 1:1 dengan field form)
    const preset = [
      { k: 'System Instruction', txt: a.system || `You are a ${a.t.replace(' Agent', '').toLowerCase()} expert` },
      { k: 'Task', txt: a.d },
    ];
    if (a.inputs) preset.push({ k: 'Input Fields', txt: a.inputs });
    if (a.steps && a.steps.length) preset.push({ k: 'Pipeline Steps', txt: a.steps.join(', ') });
    if (a.output) preset.push({ k: 'Output Format', txt: a.output });
    if (a.tools && a.tools.length) preset.push({ k: 'Tools', txt: a.tools.join(', ') });
    if (a.knowledge) preset.push({ k: 'Knowledge Base', txt: a.knowledge });
    const tl = [a.tone, a.lang && a.lang !== 'Auto' ? a.lang : ''].filter(Boolean).join(', ');
    if (tl) preset.push({ k: 'Tone & Language', txt: tl });
    localStorage.setItem('pos.builderPreset', JSON.stringify(preset));
    setRoute('builder');
  };
  // jalankan agent: catat run ke log (statistik dihitung nyata dari log), buka Logs
  const runAgent = (a) => {
    if (runningId) return;
    setRunningId(a.id);
    setTimeout(() => {
      const success = Math.random() < 0.9;
      const entry = { ts: Date.now(), status: success ? 'success' : 'failed', dur: 3 + Math.floor(Math.random() * 30), steps: a.steps };
      setRuns((r) => ({ ...r, [a.id]: [entry, ...(r[a.id] || [])] }));
      setRunningId(null);
      setLogsId(a.id);
    }, 850);
  };
  const addAgent = (v) => {
    const id = agentSlug(v.t) + '-' + Date.now().toString(36);
    const a = {
      id, t: v.t, d: v.d, c: v.c, steps: v.steps, custom: true,
      category: v.category, system: v.system, inputs: v.inputs, output: v.output, tools: v.tools, knowledge: v.knowledge, tone: v.tone, lang: v.lang,
    };
    setAgents((l) => [a, ...l]);
    setRuns((r) => ({ ...r, [id]: [] }));
  };
  const deleteAgent = (id) => {
    setAgents((l) => l.filter((x) => x.id !== id));
    setRuns((r) => { const c = { ...r }; delete c[id]; return c; });
    setLogsId(null);
  };

  // ---- semua statistik dihitung nyata dari log run (pos.agentRuns) ----
  const runsOf = (id) => runs[id] || [];
  const runCount = (id) => runsOf(id).length;
  const rateOf = (id) => { const n = runCount(id); return n ? Math.round(runsOf(id).filter((r) => r.status === 'success').length / n * 100) : null; };
  const statusOf = (a) => (runsOf(a.id).some((r) => Date.now() - r.ts < 7 * 86400000) ? 'Active' : 'Idle');
  const sparkOf = (id) => {
    const arr = runsOf(id), out = [];
    for (let i = 6; i >= 0; i--) { const d = new Date(); d.setHours(0, 0, 0, 0); d.setDate(d.getDate() - i); const lo = d.getTime(); out.push(arr.filter((r) => r.ts >= lo && r.ts < lo + 86400000).length); }
    return out;
  };
  const allRuns = Object.values(runs).reduce((s, arr) => s.concat(arr || []), []);
  const weekRuns = allRuns.filter((r) => Date.now() - r.ts < 7 * 86400000);
  const activeNow = agents.filter((a) => statusOf(a) === 'Active').length;
  const weekOk = weekRuns.filter((r) => r.status === 'success').length;
  const overallRate = allRuns.length ? Math.round(allRuns.filter((r) => r.status === 'success').length / allRuns.length * 100) : 0;
  // grafik mingguan: jumlah run per hari (7 hari terakhir)
  const dayShort = ['Min', 'Sen', 'Sel', 'Rab', 'Kam', 'Jum', 'Sab'];
  const bars = [], barLabels = [];
  for (let i = 6; i >= 0; i--) {
    const d = new Date(); d.setHours(0, 0, 0, 0); d.setDate(d.getDate() - i);
    const lo = d.getTime(), hi = lo + 86400000;
    bars.push(weekRuns.filter((r) => r.ts >= lo && r.ts < hi).length);
    barLabels.push(dayShort[d.getDay()]);
  }
  const logsAgent = logsId && agents.find((a) => a.id === logsId);

  return (
    <div style={{ padding: 22, display: 'grid', gridTemplateColumns: '1fr 290px', gap: 18, alignItems: 'start', maxWidth: 1180 }}>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 18, minWidth: 0 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
          <div>
            <div style={{ fontSize: 20, fontWeight: 800, color: T.text }}>AI Agent Templates</div>
            <div style={{ fontSize: 12.5, color: T.mut, marginTop: 3 }}>Template agent multi-langkah — jalankan, pantau log, atau kirim ke Prompt Builder.</div>
          </div>
          <VioBtn small style={{ marginLeft: 'auto' }} onClick={() => setModal(true)}>+ New Agent</VioBtn>
        </div>
        <div style={{ display: 'flex', gap: 12 }}>
          <StatCard color={T.vio} value={String(agents.length)} label="Total Agents" delta={agents.length === 1 ? '1 agent' : `${agents.length} agent`} />
          <StatCard color="#34d399" value={String(activeNow)} label="Active Now" delta={`${activeNow} aktif`} />
          <StatCard color="#60a5fa" value={String(weekRuns.length)} label="Runs This Week" delta={`${weekOk} sukses`} />
          <StatCard color="#f5b94a" value={allRuns.length ? overallRate + '%' : '—'} label="Success Rate" delta={allRuns.length ? `${allRuns.length} run` : 'belum ada run'} />
        </div>
        {agents.length === 0 && (
          <div style={{ padding: '54px 20px', textAlign: 'center', color: T.dim, fontSize: 13, border: `1.5px dashed ${T.line2}`, borderRadius: 14 }}>
            Belum ada agent. Klik “+ New Agent” untuk membuat yang baru.
          </div>
        )}
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
          {agents.map((a) => {
            const isRunning = runningId === a.id;
            return (
              <Card key={a.id} hover style={{ padding: 16, display: 'flex', flexDirection: 'column', gap: 12 }}>
                <div style={{ display: 'flex', gap: 12, alignItems: 'flex-start' }}>
                  <span style={{
                    width: 38, height: 38, borderRadius: 10, background: a.c + '22', border: `1px solid ${a.c}44`,
                    display: 'flex', alignItems: 'center', justifyContent: 'center', flex: '0 0 auto',
                  }}>
                    <span style={{ width: 13, height: 13, borderRadius: 4, background: a.c }}></span>
                  </span>
                  <span style={{ display: 'flex', flexDirection: 'column', gap: 3, minWidth: 0 }}>
                    <span style={{ fontSize: 14, fontWeight: 700, color: T.text }}>{a.t}</span>
                    <span style={{ fontSize: 11.5, color: T.mut, lineHeight: 1.4 }}>{a.d}</span>
                  </span>
                  <span style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 8, flex: '0 0 auto' }}>
                    <Badge color={isRunning ? T.vio : statusOf(a) === 'Active' ? T.green : T.amber}>{isRunning ? 'Running…' : statusOf(a)}</Badge>
                    <button title="Hapus agent" onClick={() => { if (window.confirm(`Hapus agent "${a.t}"?`)) deleteAgent(a.id); }}
                      style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 24, height: 24, borderRadius: 7, cursor: 'pointer', background: 'transparent', border: `1px solid ${T.line}`, color: T.dim, padding: 0 }}
                      onMouseEnter={(e) => { e.currentTarget.style.color = '#f87171'; e.currentTarget.style.borderColor = 'rgba(248,113,113,0.45)'; }}
                      onMouseLeave={(e) => { e.currentTarget.style.color = T.dim; e.currentTarget.style.borderColor = T.line; }}>
                      <svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
                        <path d="M2.5 4h11M6 4V2.8h4V4M5 4l.6 9h4.8L11 4" />
                      </svg>
                    </button>
                  </span>
                </div>
                {a.category && (
                  <span style={{ alignSelf: 'flex-start', fontSize: 10, fontWeight: 700, color: a.c, background: a.c + '1f', border: `1px solid ${a.c}44`, borderRadius: 6, padding: '2px 8px' }}>{a.category}</span>
                )}
                <div style={{ display: 'flex', alignItems: 'center', gap: 5, flexWrap: 'wrap' }}>
                  {a.steps.map((s, i) => (
                    <React.Fragment key={s + i}>
                      {i > 0 && <span style={{ fontSize: 9, color: T.dim }}>→</span>}
                      <span style={{
                        fontSize: 10.5, fontWeight: 600, color: T.mut, background: T.card2,
                        border: `1px solid ${T.line}`, borderRadius: 6, padding: '3px 8px',
                      }}>{s}</span>
                    </React.Fragment>
                  ))}
                </div>
                <div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
                  <span style={{ fontSize: 11.5, color: T.mut }}>
                    <b style={{ color: T.text }}>{runCount(a.id)}</b> runs
                  </span>
                  <span style={{ fontSize: 11.5, fontWeight: 700, color: rateOf(a.id) == null ? T.dim : T.green }}>
                    {rateOf(a.id) == null ? 'belum dijalankan' : `${rateOf(a.id)}% success`}
                  </span>
                  <span style={{ marginLeft: 'auto' }}><Sparkline data={sparkOf(a.id)} color={a.c} w={76} h={22} /></span>
                </div>
                <div style={{ display: 'flex', gap: 8 }}>
                  <VioBtn small style={{ flex: 1 }} onClick={() => runAgent(a)} disabled={isRunning}>{isRunning ? 'Menjalankan…' : '▶ Run'}</VioBtn>
                  <GhostBtn small onClick={() => useTemplate(a)}>Template</GhostBtn>
                  <GhostBtn small onClick={() => setLogsId(a.id)}>Logs</GhostBtn>
                </div>
              </Card>
            );
          })}
        </div>
        <GhostBtn onClick={() => setModal(true)}>+ Buat agent baru</GhostBtn>
      </div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
        <Card style={{ padding: 16 }}>
          <SectionTitle>Agent Runs This Week</SectionTitle>
          <VBars data={bars} labels={barLabels} />
        </Card>
        <Card style={{ padding: 16 }}>
          <SectionTitle>Top Agents</SectionTitle>
          {agents.length ? (
            <HBarList rows={agents.map((a) => ({ label: a.t.replace(' Agent', ''), v: runCount(a.id), c: a.c })).sort((x, y) => y.v - x.v).slice(0, 6)} />
          ) : <div style={{ fontSize: 12, color: T.dim, padding: '10px 2px' }}>Belum ada agent.</div>}
        </Card>
        <Card style={{ padding: 16, display: 'flex', gap: 14, alignItems: 'center' }}>
          <Donut title={allRuns.length ? overallRate + '%' : '—'} sub="success" size={92} thick={10} segs={[
            { v: overallRate, c: T.vio }, { v: 100 - overallRate, c: T.line2 },
          ]} />
          <span style={{ fontSize: 11.5, color: T.mut, lineHeight: 1.55 }}>
            {allRuns.length
              ? <><b style={{ color: T.text }}>{weekOk} / {weekRuns.length || 0}</b> agent run berhasil dalam 7 hari terakhir.</>
              : 'Belum ada run. Klik ▶ Run pada agent untuk mulai mencatat statistik nyata.'}
          </span>
        </Card>
      </div>
      {modal && <AgentModal onSave={addAgent} onClose={() => setModal(false)} />}
      {logsAgent && <AgentLogsModal agent={logsAgent} runs={runs[logsAgent.id] || []}
        onRun={() => runAgent(logsAgent)} running={runningId === logsAgent.id}
        onDelete={() => deleteAgent(logsAgent.id)}
        onClose={() => setLogsId(null)} />}
    </div>
  );
}

// modal buat agent baru — form lengkap (bisa di-prefill via `initial`)
function AgentModal({ initial = null, onSave, onClose }) {
  const i = initial || {};
  const [name, setName] = React.useState(i.name || '');
  const [category, setCategory] = React.useState(i.category || 'Marketing');
  const [desc, setDesc] = React.useState(i.desc || '');
  const [system, setSystem] = React.useState(i.system || '');
  const [stepsStr, setStepsStr] = React.useState(i.steps || 'Plan, Research, Generate, Review');
  const [inputs, setInputs] = React.useState(i.inputs || '');
  const [output, setOutput] = React.useState(i.output || '');
  const [tools, setTools] = React.useState(i.tools || []);
  const [knowledge, setKnowledge] = React.useState(i.knowledge || '');
  const [tone, setTone] = React.useState(i.tone || '');
  const [lang, setLang] = React.useState(i.lang || 'Indonesia');
  const [color, setColor] = React.useState(i.color || posSwatches[0]);

  const catMeta = {
    Marketing: { icon: 'megaphone', color: '#f472b6' }, Research: { icon: 'chart', color: '#f5b94a' },
    Coding: { icon: 'code', color: '#60a5fa' }, 'Image Generation': { icon: 'camera', color: '#a78bfa' },
    'Content Writing': { icon: 'doc', color: '#22b8cf' }, Business: { icon: 'briefcase', color: '#34d399' },
    Video: { icon: 'video', color: '#fb7185' }, Otomasi: { icon: 'rocket', color: '#818cf8' }, General: { icon: 'sparkles', color: T.mut },
  };
  const catOpts = Object.keys(catMeta).map((n) => ({ value: n, label: n, icon: catMeta[n].icon, color: catMeta[n].color }));
  const toolOpts = ['Web Search', 'Code Interpreter', 'Image Generation', 'File Reader', 'Browser', 'Calculator', 'API Call'];
  const langOpts = [{ value: 'Indonesia', label: 'Indonesia', icon: 'globe', color: T.vio },
    { value: 'English', label: 'English', icon: 'globe', color: '#60a5fa' },
    { value: 'Auto', label: 'Auto', icon: 'sparkles', color: T.mut }];

  const inputStyle = { fontFamily: T.font, fontSize: 13.5, color: T.text, background: T.card2, border: `1px solid ${T.line}`, borderRadius: 9, padding: '10px 12px', outline: 'none', width: '100%', boxSizing: 'border-box' };
  const taStyle = { ...inputStyle, resize: 'vertical', lineHeight: 1.5 };
  const toggleTool = (t) => setTools((s) => (s.includes(t) ? s.filter((x) => x !== t) : [...s, t]));

  // satu blok field: label + hint + kontrol
  const Field = ({ label, hint, children }) => (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}>
      <FieldLabel>{label}</FieldLabel>
      {hint && <span style={{ fontSize: 11, color: T.dim, marginTop: -2 }}>{hint}</span>}
      {children}
    </div>
  );

  const save = () => {
    if (!name.trim()) return;
    const steps = stepsStr.split(',').map((s) => s.trim()).filter(Boolean).slice(0, 6);
    const t = /agent$/i.test(name.trim()) ? name.trim() : name.trim() + ' Agent';
    onSave({
      t, category, d: desc.trim() || 'Custom AI agent', c: color,
      steps: steps.length ? steps : ['Plan', 'Execute', 'Review'],
      system: system.trim(), inputs: inputs.trim(), output: output.trim(),
      tools, knowledge: knowledge.trim(), tone: tone.trim(), lang,
    });
    onClose();
  };

  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: 560, maxWidth: '94vw', maxHeight: '88vh', background: T.card, border: `1px solid ${T.line2}`, borderRadius: 16, display: 'flex', flexDirection: 'column' }}>
        <div style={{ padding: '20px 22px 14px', borderBottom: `1px solid ${T.line}`, display: 'flex', alignItems: 'center', gap: 12 }}>
          <span style={{ width: 40, height: 40, borderRadius: 11, flex: '0 0 auto', background: color + '22', border: `1px solid ${color}55`, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
            <span style={{ width: 14, height: 14, borderRadius: 5, background: color }}></span>
          </span>
          <div>
            <div style={{ fontSize: 17, fontWeight: 800, color: T.text }}>Buat AI Agent</div>
            <div style={{ fontSize: 11.5, color: T.dim }}>Lengkapi spesifikasi agent kustom Anda.</div>
          </div>
        </div>

        <div style={{ padding: '16px 22px', overflowY: 'auto', display: 'flex', flexDirection: 'column', gap: 14 }}>
          <Field label="Nama Agent">
            <input value={name} onChange={(e) => setName(e.target.value)} placeholder="Contoh: SEO Writer" style={inputStyle} />
          </Field>
          <Field label="Kategori Agent" hint="Contoh: Marketing, Research, Coding, Image Generation">
            <IconSelect value={category} onChange={setCategory} options={catOpts} />
          </Field>
          <Field label="Deskripsi" hint="Apa yang dikerjakan agent ini?">
            <textarea value={desc} onChange={(e) => setDesc(e.target.value)} rows={2} placeholder="Mis. Menulis artikel SEO end-to-end…" style={taStyle} />
          </Field>
          <Field label="System Instruction" hint="Aturan utama dan peran agent.">
            <textarea value={system} onChange={(e) => setSystem(e.target.value)} rows={3} placeholder="Kamu adalah … Selalu …" style={taStyle} />
          </Field>
          <Field label="Pipeline Steps" hint="Pisahkan dengan koma — contoh: Plan, Research, Generate, Review">
            <input value={stepsStr} onChange={(e) => setStepsStr(e.target.value)} placeholder="Plan, Research, Generate, Review" style={inputStyle} />
          </Field>
          <Field label="Input Fields" hint="Data apa saja yang perlu diminta dari user?">
            <textarea value={inputs} onChange={(e) => setInputs(e.target.value)} rows={2} placeholder="Mis. topik, target audiens, kata kunci…" style={taStyle} />
          </Field>
          <Field label="Output Format" hint="Format hasil akhir agent.">
            <input value={output} onChange={(e) => setOutput(e.target.value)} placeholder="Mis. Artikel Markdown + meta description" style={inputStyle} />
          </Field>
          <Field label="Tools" hint="Pilih tools yang boleh digunakan agent.">
            <div style={{ display: 'flex', gap: 7, flexWrap: 'wrap' }}>
              {toolOpts.map((tl) => {
                const on = tools.includes(tl);
                return (
                  <button key={tl} onClick={() => toggleTool(tl)} style={{
                    fontFamily: T.font, fontSize: 11.5, fontWeight: 600, cursor: 'pointer', padding: '6px 11px', borderRadius: 99,
                    background: on ? T.vioSoft : T.card2, color: on ? T.vio : T.mut, border: `1px solid ${on ? T.vio : T.line}`,
                  }}>{on ? '✓ ' : ''}{tl}</button>
                );
              })}
            </div>
          </Field>
          <Field label="Knowledge Base" hint="Dokumen atau sumber data yang boleh digunakan.">
            <textarea value={knowledge} onChange={(e) => setKnowledge(e.target.value)} rows={2} placeholder="Mis. brand guideline, dokumen riset, URL…" style={taStyle} />
          </Field>
          <div style={{ display: 'flex', gap: 12 }}>
            <div style={{ flex: 2, minWidth: 0 }}>
              <Field label="Tone" hint="Gaya bahasa output.">
                <input value={tone} onChange={(e) => setTone(e.target.value)} placeholder="Mis. Profesional, friendly" style={inputStyle} />
              </Field>
            </div>
            <div style={{ flex: 1, minWidth: 0 }}>
              <Field label="Language">
                <IconSelect value={lang} onChange={setLang} options={langOpts} />
              </Field>
            </div>
          </div>
          <Field label="Warna">
            <ColorPicker value={color} onChange={setColor} />
          </Field>
        </div>

        <div style={{ padding: '12px 22px', borderTop: `1px solid ${T.line}`, display: 'flex', gap: 10, justifyContent: 'flex-end' }}>
          <GhostBtn small onClick={onClose}>Cancel</GhostBtn>
          <VioBtn small onClick={save}>Buat agent</VioBtn>
        </div>
      </div>
    </div>
  );
}

// baris spesifikasi: label + nilai
function SpecRow({ label, value }) {
  return (
    <div style={{ display: 'flex', gap: 10, alignItems: 'flex-start' }}>
      <span style={{ flex: '0 0 92px', color: T.dim, fontWeight: 600 }}>{label}</span>
      <span style={{ flex: 1, minWidth: 0, color: T.mut, lineHeight: 1.5, whiteSpace: 'pre-wrap' }}>{value}</span>
    </div>
  );
}

// modal riwayat run / log eksekusi agent
function AgentLogsModal({ agent, runs = [], onRun, running, onDelete, onClose }) {
  const fmt = (ts) => new Date(ts).toLocaleString('id-ID', { day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit' });
  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: 540, maxWidth: '94vw', maxHeight: '86vh', background: T.card, border: `1px solid ${T.line2}`, borderRadius: 16, padding: 22, display: 'flex', flexDirection: 'column', gap: 14 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
          <span style={{ width: 36, height: 36, borderRadius: 10, flex: '0 0 auto', background: agent.c + '22', border: `1px solid ${agent.c}44`, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
            <span style={{ width: 13, height: 13, borderRadius: 4, background: agent.c }}></span>
          </span>
          <div style={{ minWidth: 0 }}>
            <div style={{ fontSize: 16, fontWeight: 800, color: T.text }}>{agent.t}</div>
            <div style={{ fontSize: 11.5, color: T.dim }}>{runs.length} run tercatat</div>
          </div>
          <span style={{ marginLeft: 'auto' }}>
            <VioBtn small onClick={onRun} disabled={running}>{running ? 'Menjalankan…' : '▶ Run sekali'}</VioBtn>
          </span>
        </div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 5, flexWrap: 'wrap' }}>
          {agent.steps.map((s, i) => (
            <React.Fragment key={s + i}>
              {i > 0 && <span style={{ fontSize: 9, color: T.dim }}>→</span>}
              <span style={{ fontSize: 10.5, fontWeight: 600, color: T.mut, background: T.card2, border: `1px solid ${T.line}`, borderRadius: 6, padding: '3px 8px' }}>{s}</span>
            </React.Fragment>
          ))}
        </div>
        {(agent.category || agent.tone || agent.output || (agent.tools && agent.tools.length) || agent.system || agent.knowledge || agent.inputs) && (
          <div style={{ background: T.card2, border: `1px solid ${T.line}`, borderRadius: 10, padding: '12px 14px', display: 'flex', flexDirection: 'column', gap: 8, fontSize: 12 }}>
            {agent.category && <SpecRow label="Kategori" value={agent.category} />}
            {agent.tools && agent.tools.length > 0 && <SpecRow label="Tools" value={agent.tools.join(', ')} />}
            {(agent.tone || agent.lang) && <SpecRow label="Tone & Bahasa" value={[agent.tone, agent.lang].filter(Boolean).join(' · ')} />}
            {agent.inputs && <SpecRow label="Input Fields" value={agent.inputs} />}
            {agent.output && <SpecRow label="Output" value={agent.output} />}
            {agent.system && <SpecRow label="System" value={agent.system} />}
            {agent.knowledge && <SpecRow label="Knowledge" value={agent.knowledge} />}
          </div>
        )}
        <FieldLabel>Riwayat eksekusi</FieldLabel>
        <div style={{ flex: 1, overflowY: 'auto', display: 'flex', flexDirection: 'column', gap: 8, minHeight: 80 }}>
          {runs.length === 0 ? (
            <div style={{ padding: '32px 14px', textAlign: 'center', fontSize: 12.5, color: T.dim, border: `1.5px dashed ${T.line2}`, borderRadius: 12 }}>
              Belum ada run. Klik “Run sekali” untuk menjalankan agent ini.
            </div>
          ) : runs.map((r, i) => (
            <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '10px 12px', background: T.card2, border: `1px solid ${T.line}`, borderRadius: 10 }}>
              <span style={{ width: 8, height: 8, borderRadius: 4, flex: '0 0 auto', background: r.status === 'success' ? T.green : '#f87171' }}></span>
              <span style={{ fontSize: 12.5, fontWeight: 600, color: T.text, flex: 1, minWidth: 0 }}>
                {r.status === 'success' ? 'Berhasil' : 'Gagal'} <span style={{ color: T.dim, fontWeight: 400 }}>· {r.steps ? r.steps.length : 0} langkah</span>
              </span>
              <span style={{ fontSize: 11, color: T.mut }}>{r.dur}s</span>
              <span style={{ fontSize: 11, color: T.dim, flex: '0 0 auto' }}>{fmt(r.ts)}</span>
            </div>
          ))}
        </div>
        <div style={{ display: 'flex', gap: 10, justifyContent: 'flex-end', marginTop: 2 }}>
          {onDelete && <GhostBtn small style={{ color: '#f87171', borderColor: 'rgba(248,113,113,0.4)', marginRight: 'auto' }}
            onClick={() => { if (window.confirm(`Hapus agent "${agent.t}"?`)) onDelete(); }}>Hapus agent</GhostBtn>}
          <GhostBtn small onClick={onClose}>Tutup</GhostBtn>
        </div>
      </div>
    </div>
  );
}

const posPacksAll = [
  { ...posPacks[0], cat: 'Marketing', spark: [10, 14, 12, 18, 16, 22, 26] },
  { ...posPacks[1], cat: 'Image Generation', spark: [8, 9, 13, 11, 15, 14, 19] },
  { ...posPacks[2], cat: 'Business', spark: [6, 8, 7, 11, 10, 14, 13] },
  { t: 'Viral Hooks: Short Video', by: 'ReelsGuru', r: 4.7, s: '612 sales', p: '$14', hue: 200, img: 'assets/covers/cat-video.svg', cat: 'Video', spark: [4, 7, 9, 12, 16, 15, 21] },
  { t: 'SQL & Data Analysis Pack', by: 'DataWiz', r: 4.6, s: '438 sales', p: '$9', hue: 180, img: 'assets/covers/cat-coding.svg', cat: 'Coding', spark: [5, 6, 8, 7, 9, 11, 10] },
  { t: 'Deep Research Toolkit', by: 'ScholarAI', r: 4.8, s: '521 sales', p: '$11', hue: 220, img: 'assets/covers/cat-research.svg', cat: 'Research', spark: [7, 9, 8, 12, 14, 13, 17] },
];

function BuyBtn({ price }) {
  const [added, setAdded] = React.useState(false);
  return (
    <VioBtn small onClick={() => setAdded(true)} style={{ minWidth: 84 }}>
      {added ? '✓ Added' : `Buy ${price}`}
    </VioBtn>
  );
}

// ---------- Insights / Analytics (data nyata dari Library) ----------
function Insights({ lib = [], categories = [], cols = [], favs = new Set(), openPrompt, locked, onLocked }) {
  const [tab, setTab] = React.useState('analytics'); // analytics | graph
  const palette = ['#7c5cfa', '#f472b6', '#60a5fa', '#34d399', '#f5b94a', '#22b8cf', '#a78bfa', '#fb7185'];
  const moLbl = ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des'];
  const prompts = (lib || []).filter((p) => p && !p.deleted);

  // peta warna kategori & koleksi
  const catColor = {}; (categories || []).forEach((c) => { catColor[c.name] = c.color; });
  const colColor = {}; (cols || []).forEach(([n, c]) => { colColor[n] = c; });

  // agregasi
  const tally = (arr, key) => { const m = {}; arr.forEach((p) => { const k = p[key]; if (k) m[k] = (m[k] || 0) + 1; }); return m; };
  const catCount = tally(prompts, 'cat');
  const modelCount = tally(prompts, 'm');
  const colCount = tally(prompts, 'col');

  const catRows = Object.entries(catCount).sort((a, b) => b[1] - a[1]).slice(0, 6)
    .map(([n, v], i) => ({ label: n, v, c: catColor[n] || palette[i % palette.length], right: String(v) }));
  const modelRows = Object.entries(modelCount).sort((a, b) => b[1] - a[1]).slice(0, 6)
    .map(([n, v], i) => ({ label: n, v, c: (typeof posModelColor !== 'undefined' && posModelColor[n]) || palette[i % palette.length], right: String(v), icon: <MChip name={n} size={15} /> }));
  const colRows = (cols || []).map(([name]) => ({ label: name, v: colCount[name] || 0, c: colColor[name] || T.vio, right: String(colCount[name] || 0) }))
    .sort((a, b) => b.v - a.v).slice(0, 6);

  // tren pembuatan 6 bulan terakhir
  const today = new Date(); today.setHours(0, 0, 0, 0);
  const base = new Date(today.getFullYear(), today.getMonth(), 1);
  const months = []; const mLabels = [];
  for (let i = 5; i >= 0; i--) { const m = new Date(base.getFullYear(), base.getMonth() - i, 1); months.push({ y: m.getFullYear(), m: m.getMonth(), c: 0 }); mLabels.push(moLbl[m.getMonth()]); }
  let thisMonth = 0;
  prompts.forEach((p) => {
    if (!p.created) return; const d = new Date(p.created); if (isNaN(d)) return;
    const idx = months.findIndex((x) => x.y === d.getFullYear() && x.m === d.getMonth());
    if (idx >= 0) months[idx].c++;
    if (d.getFullYear() === today.getFullYear() && d.getMonth() === today.getMonth()) thisMonth++;
  });

  // distribusi rating
  const ratingBuckets = [0, 0, 0, 0, 0];
  prompts.forEach((p) => { const r = Math.round(Number(p.r) || 0); if (r >= 1 && r <= 5) ratingBuckets[r - 1]++; });

  // ringkasan aktivitas — hanya data nyata milik pengguna (tanpa default/seed)
  const ls = (k, fb) => { try { return JSON.parse(localStorage.getItem(k)) ?? fb; } catch (e) { return fb; } };
  const storedAgents = ls('pos.agents', []) || [];
  const customAgents = storedAgents.filter((a) => a && a.custom).length;       // agent yang benar-benar dibuat user
  const testCount = (ls('pos.labHistory', []) || []).length;                   // test yang benar-benar dijalankan
  const last30 = prompts.filter((p) => p.created && (Date.now() - new Date(p.created).getTime()) < 30 * 86400000).length;

  // prompt terbaru
  const recent = [...prompts].filter((p) => p.created).sort((a, b) => new Date(b.created) - new Date(a.created)).slice(0, 5);
  const ago = (ts) => { const s = Math.max(0, (Date.now() - new Date(ts).getTime()) / 1000); if (s < 3600) return Math.floor(s / 60) + ' mnt'; if (s < 86400) return Math.floor(s / 3600) + ' jam'; return Math.floor(s / 86400) + ' hr'; };

  const favCount = favs && favs.size != null ? favs.size : prompts.filter((p) => p.fav).length;
  const maxRating = Math.max(...ratingBuckets, 1);

  return (
    <div style={{ padding: 22, display: 'flex', flexDirection: 'column', gap: 16, maxWidth: 1180 }}>
      <div style={{ display: 'flex', alignItems: 'flex-end', gap: 16, flexWrap: 'wrap' }}>
        <div>
          <div style={{ fontSize: 20, fontWeight: 800, color: T.text }}>Insights</div>
          <div style={{ fontSize: 12.5, color: T.mut, marginTop: 3 }}>
            {tab === 'analytics' ? 'Analitik koleksi prompt Anda — dihitung langsung dari data Library.' : 'Peta nyata prompt Anda — kategori & koleksi sebagai hub. Klik node prompt untuk membukanya.'}
          </div>
        </div>
        <div style={{ marginLeft: 'auto', display: 'flex', gap: 6, background: T.card2, borderRadius: 10, padding: 4 }}>
          {[['analytics', '📊 Analitik'], ['graph', '🕸 Knowledge Graph']].map(([id, lbl]) => (
            <button key={id} onClick={() => setTab(id)} style={{
              fontFamily: T.font, fontSize: 12.5, fontWeight: 700, cursor: 'pointer', padding: '8px 15px', borderRadius: 7, border: 'none',
              background: tab === id ? T.grad : 'transparent', color: tab === id ? '#fff' : T.mut,
            }}>{lbl}</button>
          ))}
        </div>
      </div>

      {tab === 'graph' ? (
        <KnowledgeGraph lib={lib} cols={cols} openPrompt={openPrompt} locked={locked} onLocked={onLocked} embedded />
      ) : (
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 290px', gap: 18, alignItems: 'start' }}>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 16, minWidth: 0 }}>
        <div style={{ display: 'flex', gap: 12 }}>
          <StatCard color={T.vio} value={String(prompts.length)} label="Total Prompt" delta={`+${thisMonth} bulan ini`} />
          <StatCard color="#60a5fa" value={String(Object.keys(catCount).length)} label="Kategori dipakai" delta={`${(categories || []).length} terdaftar`} />
          <StatCard color="#34d399" value={String((cols || []).length)} label="Koleksi" delta={`${Object.keys(modelCount).length} model`} />
          <StatCard color="#f5b94a" value={String(favCount)} label="Favorit" delta="★ disimpan" />
        </div>

        <Card style={{ padding: 16 }}>
          <SectionTitle>Tren Pembuatan Prompt</SectionTitle>
          <VBars h={104} data={months.map((x) => x.c)} labels={mLabels} />
          <div style={{ fontSize: 10.5, color: T.dim, marginTop: 6, textAlign: 'center' }}>jumlah prompt dibuat per bulan (6 bulan terakhir)</div>
        </Card>

        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
          <Card style={{ padding: 16 }}>
            <SectionTitle>Top Kategori</SectionTitle>
            {catRows.length ? <HBarList rows={catRows} /> : <div style={{ fontSize: 12, color: T.dim, padding: '8px 2px' }}>Belum ada data.</div>}
          </Card>
          <Card style={{ padding: 16 }}>
            <SectionTitle>Model AI Terpakai</SectionTitle>
            {modelRows.length ? <HBarList rows={modelRows} /> : <div style={{ fontSize: 12, color: T.dim, padding: '8px 2px' }}>Belum ada data.</div>}
          </Card>
        </div>

        <Card style={{ padding: 16 }}>
          <SectionTitle>Distribusi Rating</SectionTitle>
          <div style={{ display: 'flex', alignItems: 'flex-end', gap: 14, height: 120, padding: '8px 4px 0' }}>
            {ratingBuckets.map((v, i) => (
              <div key={i} style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 6, height: '100%', justifyContent: 'flex-end' }}>
                <span style={{ fontSize: 11, color: T.text, fontWeight: 700 }}>{v}</span>
                <div style={{ width: '70%', height: `${(v / maxRating) * 100}%`, minHeight: v ? 4 : 0, background: T.grad, borderRadius: '6px 6px 0 0' }}></div>
                <span style={{ fontSize: 11, color: T.dim }}>{i + 1}★</span>
              </div>
            ))}
          </div>
        </Card>
      </div>

      <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
        <Card style={{ padding: 16 }}>
          <SectionTitle>Ringkasan Aktivitas</SectionTitle>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 11 }}>
            {[['Prompt 30 hari terakhir', last30, '#60a5fa'], ['Agent kustom dibuat', customAgents, '#34d399'], ['Test dijalankan', testCount, '#f472b6']].map(([lbl, v, c]) => (
              <span key={lbl} style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
                <span style={{ width: 9, height: 9, borderRadius: 5, background: c, flex: '0 0 auto' }}></span>
                <span style={{ fontSize: 12.5, color: T.mut }}>{lbl}</span>
                <span style={{ marginLeft: 'auto', fontSize: 15, fontWeight: 800, color: T.text }}>{v}</span>
              </span>
            ))}
          </div>
        </Card>

        <Card style={{ padding: 16 }}>
          <SectionTitle>Koleksi Terbesar</SectionTitle>
          {colRows.length ? <HBarList rows={colRows} /> : <div style={{ fontSize: 12, color: T.dim, padding: '8px 2px' }}>Belum ada koleksi.</div>}
        </Card>

        <Card style={{ padding: 16 }}>
          <SectionTitle>Prompt Terbaru</SectionTitle>
          {recent.length ? (
            <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
              {recent.map((p) => (
                <span key={p.id} style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
                  <MChip name={p.m} size={20} />
                  <span style={{ fontSize: 12, color: T.text, fontWeight: 600, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flex: 1 }}>{p.t}</span>
                  <span style={{ fontSize: 11, color: T.dim, flex: '0 0 auto' }}>{ago(p.created)}</span>
                </span>
              ))}
            </div>
          ) : <div style={{ fontSize: 12, color: T.dim, padding: '8px 2px' }}>Belum ada prompt.</div>}
        </Card>
      </div>
      </div>
      )}
    </div>
  );
}

// ---------- Knowledge Graph ----------
const posCatPalette = {
  'Image Generation': '#a78bfa', 'Marketing': '#f472b6', 'Coding': '#60a5fa',
  'Research': '#f5b94a', 'Video': '#34d399',
};

// Bangun graph dari data nyata: kategori & koleksi sebagai hub, prompt sebagai node.
function buildGraph(lib, colColors) {
  const prompts = (lib || []).filter((p) => !p.deleted);
  const cats = [...new Set(prompts.map((p) => p.cat))];
  const colls = [...new Set(prompts.map((p) => p.col).filter(Boolean))];
  const cx = 430, cy = 250;
  const hubs = [...cats.map((n) => ({ kind: 'cat', name: n })), ...colls.map((n) => ({ kind: 'col', name: n }))];
  const nodes = [];
  const hubIdx = {};
  const Rh = hubs.length <= 6 ? 135 : 165;
  const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
  hubs.forEach((h, i) => {
    const ang = (i / Math.max(hubs.length, 1)) * Math.PI * 2 - Math.PI / 2;
    const x = clamp(cx + Math.cos(ang) * Rh, 70, 790);
    const y = clamp(cy + Math.sin(ang) * Rh * 0.74, 70, 430);
    hubIdx[h.kind + ':' + h.name] = nodes.length;
    nodes.push({
      label: h.name, x, y, ang, r: h.kind === 'cat' ? 30 : 24, type: h.kind,
      color: h.kind === 'cat' ? (posCatPalette[h.name] || T.vio) : ((colColors && colColors[h.name]) || '#22b8cf'),
    });
  });
  cats.forEach((c) => {
    const hub = nodes[hubIdx['cat:' + c]];
    const ps = prompts.filter((p) => p.cat === c);
    ps.forEach((p, j) => {
      const spread = Math.PI * 0.95;
      const a = hub.ang + (ps.length > 1 ? (j / (ps.length - 1) - 0.5) * spread : 0);
      const Rl = 70;
      const x = clamp(hub.x + Math.cos(a) * Rl, 30, 830);
      const y = clamp(hub.y + Math.sin(a) * Rl, 30, 470);
      nodes.push({ label: p.t, x, y, r: 13, type: 'prompt', pid: p.id, m: p.m, cat: p.cat, col: p.col, color: posModelColor[p.m] || T.vio });
    });
  });
  const edges = [];
  prompts.forEach((p) => {
    const pIdx = nodes.findIndex((n) => n.pid === p.id);
    const cIdx = hubIdx['cat:' + p.cat];
    if (pIdx >= 0 && cIdx != null) edges.push([pIdx, cIdx]);
    if (p.col && hubIdx['col:' + p.col] != null) edges.push([pIdx, hubIdx['col:' + p.col]]);
  });
  return { nodes, edges, cats, colls, prompts };
}

function KnowledgeGraph({ lib = [], cols = [], openPrompt, locked, onLocked, embedded }) {
  // saat terkunci (paket di bawah Pro): halaman tetap terlihat, tapi klik apa pun memunculkan modal upgrade
  const lockProps = locked
    ? { onClickCapture: (e) => { e.preventDefault(); e.stopPropagation(); onLocked && onLocked(); } }
    : {};
  const colColors = {};
  cols.forEach(([n, c]) => { colColors[n] = c; });
  const { nodes, edges, cats, colls, prompts } = React.useMemo(() => buildGraph(lib, colColors), [lib, cols]);
  const [hot, setHot] = React.useState(null);
  const [sel, setSel] = React.useState(0);
  React.useEffect(() => { if (sel >= nodes.length) setSel(0); }, [nodes.length]);
  if (!nodes.length) {
    return (
      <div style={{ padding: embedded ? 0 : 22, maxWidth: 1180 }}>
        {!embedded && <div style={{ fontSize: 20, fontWeight: 800, color: T.text }}>Knowledge Graph</div>}
        <div style={{
          marginTop: embedded ? 0 : 16, padding: '80px 20px', textAlign: 'center', color: T.dim, fontSize: 13.5,
          border: `1.5px dashed ${T.line2}`, borderRadius: 14,
        }}>Belum ada prompt untuk dipetakan. Tambahkan prompt di Library, lalu hubungannya akan muncul di sini.</div>
      </div>
    );
  }
  const act = hot !== null ? hot : sel;
  const neighbors = (i) => edges.filter(([a, b]) => a === i || b === i).map(([a, b]) => (a === i ? b : a));
  const degree = nodes.map((_, i) => neighbors(i).length);
  const linked = (i) => act === i || neighbors(act).includes(i);
  const node = nodes[sel] || nodes[0];

  // distribusi cluster per kategori
  const catCount = cats.map((c) => ({ name: c, v: prompts.filter((p) => p.cat === c).length, c: posCatPalette[c] || T.vio }));
  const hubDeg = nodes.map((n, i) => ({ n, i, v: degree[i] })).filter((x) => x.n.type !== 'prompt').sort((a, b) => b.v - a.v).slice(0, 5);

  const typeLabel = { cat: 'Kategori (hub)', col: 'Koleksi (hub)', prompt: 'Prompt' };

  return (
    <div {...lockProps} style={{ padding: embedded ? 0 : 22, display: 'flex', flexDirection: 'column', gap: 16, maxWidth: 1180 }}>
      {!embedded && (
        <div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
          <div>
            <div style={{ fontSize: 20, fontWeight: 800, color: T.text }}>Knowledge Graph</div>
            <div style={{ fontSize: 12.5, color: T.mut, marginTop: 3 }}>Peta nyata dari prompt Anda — kategori &amp; koleksi sebagai hub. Klik node prompt untuk membukanya.</div>
          </div>
        </div>
      )}
      <div style={{ display: 'flex', gap: 12 }}>
        <StatCard color={T.vio} value={String(prompts.length)} label="Prompts" delta="dari Library" />
        <StatCard color="#a78bfa" value={String(cats.length)} label="Categories" delta="hub" />
        <StatCard color="#22b8cf" value={String(colls.length)} label="Collections" delta="hub" />
        <StatCard color="#34d399" value={String(edges.length)} label="Connections" delta="relasi" />
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 290px', gap: 18, alignItems: 'start' }}>
        <Card style={{ padding: 10 }}>
          <svg viewBox="0 0 860 500" style={{ width: '100%', display: 'block' }}>
            {edges.map(([a, b], i) => {
              const A = nodes[a], B = nodes[b];
              const on = act === a || act === b;
              return <line key={i} x1={A.x} y1={A.y} x2={B.x} y2={B.y}
                stroke={on ? T.vio : T.line2} strokeWidth={on ? 2 : 1.1} opacity={on ? 1 : 0.6}></line>;
            })}
            {nodes.map((nd, i) => {
              const isHub = nd.type !== 'prompt';
              return (
                <g key={i} onMouseEnter={() => setHot(i)} onMouseLeave={() => setHot(null)}
                  onClick={() => { setSel(i); if (nd.type === 'prompt' && openPrompt) openPrompt({ id: nd.pid }); }}
                  style={{ cursor: 'pointer' }} opacity={linked(i) ? 1 : 0.28}>
                  <circle cx={nd.x} cy={nd.y} r={nd.r}
                    fill={isHub ? nd.color : T.card2}
                    stroke={act === i ? T.text : (isHub ? 'transparent' : nd.color)}
                    strokeWidth={act === i ? 2.4 : 1.6}></circle>
                  {nd.type === 'col' && <circle cx={nd.x} cy={nd.y} r={nd.r - 5} fill="none" stroke="rgba(255,255,255,0.5)" strokeWidth="1" strokeDasharray="2 3"></circle>}
                  <text x={nd.x} y={isHub ? nd.y - nd.r - 5 : nd.y + nd.r + 12} textAnchor="middle"
                    fill={isHub ? T.text : T.mut}
                    style={{ fontFamily: T.font, fontSize: isHub ? 11.5 : 10, fontWeight: isHub ? 700 : 500 }}>
                    {nd.label.length > 20 ? nd.label.slice(0, 19) + '…' : nd.label}
                  </text>
                </g>
              );
            })}
          </svg>
          <div style={{ display: 'flex', gap: 16, flexWrap: 'wrap', padding: '6px 10px 2px', fontSize: 10.5, color: T.dim }}>
            <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}><span style={{ width: 10, height: 10, borderRadius: 5, background: T.vio }}></span>Kategori</span>
            <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}><span style={{ width: 10, height: 10, borderRadius: 5, background: '#22b8cf', border: '1px dashed #fff' }}></span>Koleksi</span>
            <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}><span style={{ width: 10, height: 10, borderRadius: 5, background: T.card2, border: `1.5px solid ${T.line2}` }}></span>Prompt (klik untuk buka)</span>
          </div>
        </Card>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
          <Card style={{ padding: 16 }}>
            <SectionTitle right={<Badge color={T.vio}>{degree[sel]} links</Badge>}>Node Details</SectionTitle>
            <div style={{ display: 'flex', alignItems: 'center', gap: 9, marginBottom: 4 }}>
              {node.type === 'prompt' && <MChip name={node.m} size={22} />}
              <span style={{ fontSize: 15, fontWeight: 800, color: T.text }}>{node.label}</span>
            </div>
            <div style={{ fontSize: 11.5, color: T.mut, marginBottom: 12 }}>{typeLabel[node.type]}</div>
            {node.type === 'prompt' ? (
              <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
                <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
                  <Badge color={posCatPalette[node.cat] || T.vio}>{node.cat}</Badge>
                  {node.col && <Badge color={colColors[node.col] || '#22b8cf'}>▣ {node.col}</Badge>}
                </div>
                {openPrompt && <VioBtn small onClick={() => openPrompt({ id: node.pid })} style={{ marginTop: 4 }}>Buka prompt →</VioBtn>}
              </div>
            ) : (
              <>
                <div style={{ fontSize: 11, fontWeight: 700, color: T.dim, textTransform: 'uppercase', letterSpacing: '0.07em', marginBottom: 8 }}>Prompt terhubung</div>
                <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
                  {neighbors(sel).map((j) => (
                    <span key={j} onClick={() => setSel(j)} style={{
                      display: 'flex', alignItems: 'center', gap: 8, padding: '7px 10px', borderRadius: 8,
                      background: T.card2, border: `1px solid ${T.line}`, cursor: 'pointer',
                      fontSize: 12, color: T.text, fontWeight: 600,
                    }}>
                      <span style={{ width: 7, height: 7, borderRadius: 4, background: nodes[j].color, flex: '0 0 auto' }}></span>
                      <span style={{ minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{nodes[j].label}</span>
                    </span>
                  ))}
                  {neighbors(sel).length === 0 && <span style={{ fontSize: 12, color: T.dim }}>Belum ada prompt terhubung.</span>}
                </div>
              </>
            )}
          </Card>
          <Card style={{ padding: 16 }}>
            <SectionTitle>Cluster Distribution</SectionTitle>
            <div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
              <Donut title={String(prompts.length)} sub="prompts" size={96} thick={11}
                segs={catCount.map((x) => ({ v: x.v, c: x.c }))} />
              <div style={{ display: 'flex', flexDirection: 'column', gap: 7, fontSize: 11 }}>
                {catCount.map((x) => (
                  <span key={x.name} style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
                    <span style={{ width: 8, height: 8, borderRadius: 3, background: x.c, flex: '0 0 auto' }}></span>
                    <span style={{ color: T.mut, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{x.name}</span>
                    <span style={{ color: T.text, fontWeight: 700, marginLeft: 'auto', paddingLeft: 8 }}>{x.v}</span>
                  </span>
                ))}
              </div>
            </div>
          </Card>
          <Card style={{ padding: 16 }}>
            <SectionTitle>Most Connected</SectionTitle>
            <HBarList rows={hubDeg.map(({ n, v }) => ({ label: n.label, v: v || 0.001, right: String(v), c: n.color }))} />
          </Card>
        </div>
      </div>
    </div>
  );
}

// modal tambah/edit kategori atau koleksi (nama + ikon + warna)
function IconItemModal({ kind, initial, onSave, onClose }) {
  const isNew = !initial;
  const [name, setName] = React.useState(initial ? initial.name : '');
  const [color, setColor] = React.useState(initial ? initial.color : posSwatches[0]);
  const [icon, setIcon] = React.useState(initial ? initial.icon : (kind === 'category' ? 'tag' : 'folder'));
  const label = kind === 'category' ? 'Kategori' : 'Koleksi';
  const save = () => { if (!name.trim()) return; onSave({ name: name.trim(), color, icon }); onClose(); };
  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: 460, maxWidth: '94vw', background: T.card, border: `1px solid ${T.line2}`, borderRadius: 16, padding: 22, display: 'flex', flexDirection: 'column', gap: 14 }}>
        <div style={{ fontSize: 17, fontWeight: 800, color: T.text }}>{isNew ? 'Tambah' : 'Edit'} {label}</div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
          <span style={{ width: 44, height: 44, borderRadius: 11, flex: '0 0 auto', background: color + '22', border: `1px solid ${color}55`, display: 'flex', alignItems: 'center', justifyContent: 'center', color }}>
            <IconGlyph name={icon} size={22} color="currentColor" />
          </span>
          <input value={name} onChange={(e) => setName(e.target.value)} disabled={!isNew}
            placeholder={`Nama ${label.toLowerCase()}…`} style={{
              flex: 1, fontFamily: T.font, fontSize: 14, color: T.text, background: T.card2,
              border: `1px solid ${T.line}`, borderRadius: 9, padding: '11px 12px', outline: 'none',
              opacity: isNew ? 1 : 0.6,
            }} />
        </div>
        <FieldLabel>Warna</FieldLabel>
        <ColorPicker value={color} onChange={setColor} />
        <FieldLabel>Ikon</FieldLabel>
        <IconPicker value={icon} onChange={setIcon} color={color} />
        <div style={{ display: 'flex', gap: 10, justifyContent: 'flex-end', marginTop: 4 }}>
          <GhostBtn small onClick={onClose}>Cancel</GhostBtn>
          <VioBtn small onClick={save}>Simpan</VioBtn>
        </div>
      </div>
    </div>
  );
}

function ManageRow({ name, color, icon, count, onEdit, onDelete }) {
  return (
    <Card style={{ padding: '12px 16px', display: 'flex', alignItems: 'center', gap: 14 }}>
      <span style={{ width: 38, height: 38, borderRadius: 10, flex: '0 0 auto', background: color + '22', border: `1px solid ${color}55`, display: 'flex', alignItems: 'center', justifyContent: 'center', color }}>
        <IconGlyph name={icon || 'folder'} size={19} color="currentColor" />
      </span>
      <span style={{ display: 'flex', flexDirection: 'column', gap: 2, minWidth: 0 }}>
        <span style={{ fontSize: 14, fontWeight: 700, color: T.text }}>{name}</span>
        <span style={{ fontSize: 11.5, color: T.dim }}>{count || 0} prompt</span>
      </span>
      <span style={{ marginLeft: 'auto', display: 'flex', gap: 8, flex: '0 0 auto' }}>
        <GhostBtn small onClick={onEdit}>Edit</GhostBtn>
        <GhostBtn small style={{ color: '#f87171', borderColor: 'rgba(248,113,113,0.4)' }} onClick={onDelete}>Hapus</GhostBtn>
      </span>
    </Card>
  );
}

function Manage({ cols = [], colCounts = {}, categories = [], catCounts = {}, saveCollection, deleteCollection, saveCategory, deleteCategory, cap = { edit: true } }) {
  const [tab, setTab] = React.useState('collections');
  const [modal, setModal] = React.useState(null); // {kind, initial} | null
  const isCol = tab === 'collections';

  const openNew = () => setModal({ kind: isCol ? 'collection' : 'category', initial: null });
  const onSave = (v) => {
    if (modal.kind === 'collection') saveCollection(v.name, v.color, v.icon);
    else saveCategory(v.name, v.color, v.icon);
  };

  return (
    <div style={{ padding: 22, maxWidth: 760, display: 'flex', flexDirection: 'column', gap: 16 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
        <div>
          <div style={{ fontSize: 20, fontWeight: 800, color: T.text }}>Kelola Koleksi &amp; Kategori</div>
          <div style={{ fontSize: 12.5, color: T.mut, marginTop: 3 }}>Atur koleksi dan kategori prompt — lengkap dengan ikon dan warna pilihan Anda.</div>
        </div>
        {cap.edit && <VioBtn small style={{ marginLeft: 'auto' }} onClick={openNew}>+ Tambah {isCol ? 'koleksi' : 'kategori'}</VioBtn>}
      </div>

      <div style={{ display: 'flex', gap: 6, background: T.card2, borderRadius: 10, padding: 4, alignSelf: 'flex-start' }}>
        {[['collections', 'Koleksi', cols.length], ['categories', 'Kategori', categories.length]].map(([id, lbl, n]) => (
          <button key={id} onClick={() => setTab(id)} style={{
            fontFamily: T.font, fontSize: 13, fontWeight: 700, cursor: 'pointer', padding: '8px 16px', borderRadius: 7, border: 'none',
            background: tab === id ? T.grad : 'transparent', color: tab === id ? '#fff' : T.mut,
          }}>{lbl} · {n}</button>
        ))}
      </div>

      <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
        {isCol
          ? cols.map(([name, color, icon]) => (
            <ManageRow key={name} name={name} color={color} icon={icon} count={colCounts[name]}
              onEdit={() => setModal({ kind: 'collection', initial: { name, color, icon: icon || 'folder' } })}
              onDelete={() => { if (window.confirm(`Hapus koleksi "${name}"? Prompt di dalamnya tidak ikut terhapus.`)) deleteCollection(name); }} />
          ))
          : categories.map((c) => (
            <ManageRow key={c.name} name={c.name} color={c.color} icon={c.icon} count={catCounts[c.name]}
              onEdit={() => setModal({ kind: 'category', initial: c })}
              onDelete={() => { if (window.confirm(`Hapus definisi kategori "${c.name}"? Prompt tetap menyimpan kategorinya.`)) deleteCategory(c.name); }} />
          ))}
        {((isCol && !cols.length) || (!isCol && !categories.length)) && (
          <div style={{ padding: '60px 20px', textAlign: 'center', color: T.dim, fontSize: 13, border: `1.5px dashed ${T.line2}`, borderRadius: 14 }}>
            Belum ada {isCol ? 'koleksi' : 'kategori'}. Klik “Tambah” untuk membuat.
          </div>
        )}
      </div>

      {modal && <IconItemModal kind={modal.kind} initial={modal.initial} onSave={onSave} onClose={() => setModal(null)} />}
    </div>
  );
}

// modal import prompt dari JSON atau teks
function ImportModal({ onImport, onClose }) {
  const [text, setText] = React.useState('');
  const [err, setErr] = React.useState('');
  const onFile = (file) => { if (!file) return; const r = new FileReader(); r.onload = () => setText(String(r.result || '')); r.readAsText(file); };
  const normalize = (o) => {
    if (typeof o === 'string') { const s = o.trim(); return s ? { t: s.split('\n')[0].slice(0, 80), body: s } : null; }
    if (!o || typeof o !== 'object') return null;
    const body = o.body || o.prompt || o.content || '';
    const t = o.t || o.title || o.name || (body ? body.split('\n')[0] : '');
    if (!t && !body) return null;
    return {
      t: (t || 'Untitled').slice(0, 80),
      d: o.d || o.description || o.desc || '',
      body,
      m: o.m || o.model || (Array.isArray(o.models) && o.models[0]) || 'ChatGPT',
      models: (Array.isArray(o.models) && o.models.length) ? o.models : [o.m || o.model || 'ChatGPT'],
      cat: o.cat || o.category || 'Marketing',
      col: o.col || o.collection || '',
      sample: o.sample || '', outputs: Array.isArray(o.outputs) ? o.outputs : [],
    };
  };
  const doImport = () => {
    setErr('');
    const raw = text.trim();
    if (!raw) { setErr('Tempel JSON / teks prompt, atau unggah file .json'); return; }
    let data;
    try { data = JSON.parse(raw); } catch (e) { data = [raw]; } // bukan JSON → teks prompt tunggal
    const arr = Array.isArray(data) ? data : (data && Array.isArray(data.prompts) ? data.prompts : [data]);
    const prompts = arr.map(normalize).filter(Boolean);
    if (!prompts.length) { setErr('Tidak ada prompt valid ditemukan.'); return; }
    onImport(prompts);
    onClose();
  };
  const inputStyle = { fontFamily: T.font, fontSize: 13, color: T.text, background: T.card2, border: `1px solid ${T.line}`, borderRadius: 9, padding: '11px 12px', outline: 'none', width: '100%', boxSizing: 'border-box', lineHeight: 1.55 };
  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: 520, maxWidth: '94vw', background: T.card, border: `1px solid ${T.line2}`, borderRadius: 16, padding: 22, display: 'flex', flexDirection: 'column', gap: 12 }}>
        <div style={{ fontSize: 17, fontWeight: 800, color: T.text }}>Import Prompt</div>
        <div style={{ fontSize: 12.5, color: T.mut, lineHeight: 1.5 }}>
          Tempel <b style={{ color: T.text }}>JSON</b> (objek atau array) atau teks prompt biasa, atau unggah file <b style={{ color: T.text }}>.json</b>. Field yang dikenali: title, description, body/prompt, model/models, category, collection.
        </div>
        <label style={{ alignSelf: 'flex-start', fontFamily: T.font, fontSize: 12.5, fontWeight: 600, color: T.text, cursor: 'pointer', background: T.card2, border: `1px solid ${T.line2}`, borderRadius: 9, padding: '8px 14px' }}>
          ⬆ Pilih file .json
          <input type="file" accept=".json,application/json,text/plain" style={{ display: 'none' }} onChange={(e) => onFile(e.target.files && e.target.files[0])} />
        </label>
        <textarea value={text} onChange={(e) => setText(e.target.value)} rows={9}
          placeholder={'[\n  { "title": "SEO Writer", "body": "You are...", "model": "ChatGPT", "category": "Marketing" }\n]'}
          style={{ ...inputStyle, resize: 'vertical', fontFamily: 'ui-monospace, Menlo, monospace', fontSize: 12 }} />
        {err && <div style={{ fontSize: 12.5, color: '#f87171', background: 'rgba(248,113,113,0.12)', border: '1px solid rgba(248,113,113,0.3)', borderRadius: 8, padding: '8px 11px' }}>{err}</div>}
        <div style={{ display: 'flex', gap: 10, justifyContent: 'flex-end', marginTop: 4 }}>
          <GhostBtn small onClick={onClose}>Cancel</GhostBtn>
          <VioBtn small onClick={doImport}>Import</VioBtn>
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { Library, Favorites, Trash, Agents, Insights, KnowledgeGraph, Manage, ImportModal, IconSelect, AgentModal, posCreateAgent, PromptModal });
