/* AAU CRM — Hệ thống: 7 màn quản trị port từ backend CQA gốc
   (Nguồn kết nối · AI Cost · Nhật ký hoạt động · MCP · Nhật ký thông báo · Agents · Dữ liệu mẫu)
   Mỗi màn map thẳng vào endpoint backend gốc — xem caption dưới tiêu đề. */

const FX_RATE = 25400; // USD→VND, đồng bộ với System Settings › Tỷ giá

function nowMinus(min) { return new Date(Date.now() - min * 60000).toISOString(); }
function fmtTime(iso) {
  const d = new Date(iso);
  const p = n => String(n).padStart(2, '0');
  return p(d.getDate()) + '/' + p(d.getMonth() + 1) + ' ' + p(d.getHours()) + ':' + p(d.getMinutes());
}

/* ───────────────────────── 1 · NGUỒN KẾT NỐI (Channels) ───────────────────────── */
const SYNC_CHANNELS = [
  { id: 'ch_fb', ch: 'fb', name: 'AAU Academy', handle: 'fb.com/aau.academy', status: 'connected', convs: 3420, lastSync: nowMinus(5), tokenDays: 58, mode: 'Mỗi 15 phút' },
  { id: 'ch_zalo', ch: 'zalo', name: 'AAU Academy OA', handle: 'OA · 400089...', status: 'connected', convs: 1862, lastSync: nowMinus(12), tokenDays: 21, mode: 'Sau mỗi lần đồng bộ' },
  { id: 'ch_ig', ch: 'instagram', name: 'aau.academy', handle: 'ig/aau.academy', status: 'error', convs: 740, lastSync: nowMinus(2880), tokenDays: -1, mode: 'Mỗi 30 phút' },
  { id: 'ch_tiktok', ch: 'tiktok', name: 'TikTok @aauacademy', handle: 'chưa hỗ trợ ingest', status: 'disconnected', convs: 0, lastSync: null, tokenDays: null, mode: '—' },
];
const SYNC_HISTORY = [
  { t: nowMinus(5), kind: 'Tự động', added: 34, status: 'ok', dur: '4,2s' },
  { t: nowMinus(20), kind: 'Sau đồng bộ', added: 12, status: 'ok', dur: '2,8s' },
  { t: nowMinus(35), kind: 'Thủ công', added: 58, status: 'ok', dur: '6,1s' },
  { t: nowMinus(95), kind: 'Tự động', added: 0, status: 'error', dur: '30s (timeout)' },
  { t: nowMinus(140), kind: 'Tự động', added: 21, status: 'ok', dur: '3,5s' },
];
const CH_STATUS = { connected: { tone: 'success', label: 'Đã kết nối', dot: true }, error: { tone: 'critical', label: 'Lỗi token' }, disconnected: { tone: 'neutral', label: 'Chưa kết nối' } };

// Form kết nối / sửa kênh — token thật do người dùng cấp; backend chỉ đánh dấu hasToken.
// Có `channel` ⇒ chế độ SỬA (prefill + PUT); không có ⇒ tạo mới (POST).
function ConnectChannelModal({ channel, onClose, onSubmit }) {
  const editing = !!channel;
  const [ch, setCh] = useState(channel ? channel.ch : 'zalo');
  const [name, setName] = useState(channel ? channel.name || '' : '');
  const [handle, setHandle] = useState(channel ? channel.handle || '' : '');
  const [mode, setMode] = useState(channel ? channel.mode || 'Mỗi 15 phút' : 'Mỗi 15 phút');
  const [token, setToken] = useState('');
  const [busy, setBusy] = useState(false);
  const chOpts = [
    { value: 'zalo', label: 'Zalo OA' }, { value: 'fb', label: 'Facebook Messenger' },
    { value: 'instagram', label: 'Instagram' }, { value: 'tiktok', label: 'TikTok' },
  ];
  const modeOpts = ['Mỗi 15 phút', 'Mỗi 30 phút', 'Mỗi giờ', 'Sau mỗi lần đồng bộ', 'Thủ công'].map(m => ({ value: m, label: m }));
  const submit = async () => {
    if (!name.trim() || busy) return;
    setBusy(true);
    try { await onSubmit({ ch, name: name.trim(), handle: handle.trim(), mode, token: token.trim() }); onClose(); }
    finally { setBusy(false); }
  };
  return (
    <Modal title={editing ? 'Sửa kênh' : 'Kết nối kênh mới'} width={520} onClose={onClose}
      footer={<><Button variant="plain" onClick={onClose}>Hủy</Button><Button variant="primary" icon={editing ? 'edit' : 'plus'} onClick={submit} disabled={busy || !name.trim()}>{busy ? 'Đang lưu…' : (editing ? 'Lưu thay đổi' : 'Kết nối')}</Button></>}>
      <Field label="Loại kênh"><Select value={ch} onChange={setCh} options={chOpts} /></Field>
      <Field label="Tên hiển thị"><Input value={name} onChange={setName} placeholder="VD: AAU Academy OA" autoFocus /></Field>
      <Field label="Định danh (handle · OA id · page)"><Input value={handle} onChange={setHandle} placeholder="OA · 400089...  hoặc  fb.com/aau.academy" /></Field>
      <Field label="Lịch đồng bộ"><Select value={mode} onChange={setMode} options={modeOpts} /></Field>
      <Field label={editing ? 'Đổi access token (để trống nếu giữ nguyên)' : 'Access token (tùy chọn)'}><Input value={token} onChange={setToken} type="password" placeholder="Dán token nền tảng nếu có" /></Field>
      <div className="t11 subtle mt8" style={{ lineHeight: 1.5 }}>Token thật của Zalo/FB do bạn cấp. App chỉ đánh dấu <b>đã có token</b> (production chuyển cho CQA / secret store), <b>không lưu</b> chuỗi token trong DB.</div>
    </Modal>
  );
}

function ChatChannels() {
  const live = !!(window.API && window.API.enabled);
  const [sel, setSel] = useState('ch_fb');
  const [list, setList] = useState(() => SYNC_CHANNELS.map(c => ({ ...c })));
  const [modal, setModal] = useState(null); // null = đóng · {} = tạo mới · {channel} = sửa
  const [testMsg, setTestMsg] = useState(null);

  const reload = () => { if (live && window.API) window.API.channels().then(cs => { if (Array.isArray(cs)) { setList(cs); setSel(cs[0] ? cs[0].id : null); } }).catch(() => {}); };
  useEffect(() => { reload(); }, []);

  const cur = list.find(c => c.id === sel) || list[0];
  const connected = list.filter(c => c.status === 'connected').length;
  const totalConvs = list.reduce((s, c) => s + (c.convs || 0), 0);

  const sync = id => {
    if (live) window.API.syncChannel(id).then(r => { if (r && r.channel) setList(ls => ls.map(c => c.id === id ? r.channel : c)); }).catch(() => {});
    else setList(ls => ls.map(c => c.id === id ? { ...c, lastSync: new Date().toISOString(), convs: c.convs + Math.floor(Math.random() * 20) } : c));
  };
  const reauth = id => { if (live) window.API.reauthChannel(id).then(rc => setList(ls => ls.map(c => c.id === id ? rc : c))).catch(() => {}); else setList(ls => ls.map(c => c.id === id ? { ...c, status: 'connected', tokenDays: 90 } : c)); };
  const test = id => {
    setTestMsg({ level: 'pending', text: 'Đang kiểm tra…' });
    if (live) window.API.testChannel(id).then(r => setTestMsg({ level: r.level || (r.ok ? 'ok' : 'fail'), text: r.detail })).catch(e => setTestMsg({ level: 'fail', text: 'Lỗi: ' + e.message }));
    else setTestMsg({ level: 'warn', text: 'Chế độ mock — không kiểm tra kết nối thật được.' });
  };
  const purge = id => { const after = ls => { const n = ls.filter(c => c.id !== id); if (sel === id && n[0]) setSel(n[0].id); return n; }; if (live) window.API.purgeChannel(id).then(() => setList(after)).catch(() => {}); else setList(after); };
  const create = async data => {
    if (live) { const nc = await window.API.createChannel(data); setList(ls => [...ls, nc]); setSel(nc.id); }
    else { const nc = { id: 'ch_' + Date.now(), ...data, status: 'connected', convs: 0, lastSync: null, tokenDays: 90, mode: data.mode || 'Mỗi 15 phút', syncHistory: [] }; setList(ls => [...ls, nc]); setSel(nc.id); }
  };
  const saveEdit = async data => {
    const id = (modal && modal.channel && modal.channel.id) || sel;
    if (live) { const uc = await window.API.updateChannel(id, data); if (uc && uc.id) setList(ls => ls.map(c => c.id === uc.id ? uc : c)); }
    else setList(ls => ls.map(c => c.id === id ? { ...c, ...data } : c));
  };
  const chMeta = ch => (AAU.channels && AAU.channels[ch]) || { color: '#8a8f98', short: (ch || '?').toUpperCase(), label: ch };
  const history = (cur && cur.syncHistory && cur.syncHistory.length) ? cur.syncHistory : SYNC_HISTORY;

  return (
    <div className="page wide">
      {modal && <ConnectChannelModal channel={modal.channel} onClose={() => setModal(null)} onSubmit={modal.channel ? saveEdit : create} />}
      <PageHead title="Nguồn kết nối" subtitle="Kênh chat (Zalo OA · Facebook · Instagram) đồng bộ về để chấm QC · backend: channels — test · sync · reauth · sync-history · purge"
        actions={<Button variant="primary" icon="plus" onClick={() => setModal({})}>Kết nối kênh mới</Button>} />

      <div className="grid gc4 mb16">
        <KPI label="Kênh đã kết nối" value={connected + '/' + list.length} icon="message" />
        <KPI label="Hội thoại đã đồng bộ" value={AAU.fmtNum(totalConvs)} icon="inbox" />
        <KPI label="Đồng bộ gần nhất" value="5 phút" sub="Facebook Page" icon="refresh" />
        <KPI label="Token sắp hết hạn" value="1" sub="Instagram đã hết hạn" icon="alert" />
      </div>

      {list.length === 0 ? (
        <EmptyState icon="message" title="Chưa có kênh nào"
          desc="Đã xoá sạch để chạy real data. Thêm kênh thật rồi nối CQA (CQA_DB_DSN) + Đồng bộ để kéo tin nhắn thật vào Inbox."
          action={<Button variant="primary" icon="plus" onClick={() => setModal({})}>Kết nối kênh mới</Button>} />
      ) : (
      <div className="grid mb16" style={{ gridTemplateColumns: '1.15fr 1fr', alignItems: 'start' }}>
        <Card pad={false} title="Kênh chat">
          <table className="tbl">
            <thead><tr><th>Kênh</th><th>Trạng thái</th><th className="num">Hội thoại</th><th>Đồng bộ gần nhất</th><th></th></tr></thead>
            <tbody>
              {list.map(c => {
                const st = CH_STATUS[c.status]; const meta = chMeta(c.ch);
                return (
                  <tr key={c.id} onClick={() => setSel(c.id)} style={{ cursor: 'pointer', background: c.id === sel ? 'var(--p-bg-surface-secondary)' : undefined }}>
                    <td>
                      <div className="row aic g10">
                        <span className="center" style={{ width: 30, height: 30, borderRadius: 7, background: meta.color, color: '#fff', fontSize: 12, fontWeight: 700, flexShrink: 0 }}>{meta.short[0]}</span>
                        <div><div className="t13 fw6">{c.name}</div><div className="t11 subtle">{meta.label} · {c.handle}</div></div>
                      </div>
                    </td>
                    <td><Badge tone={st.tone} dot={st.dot}>{st.label}</Badge></td>
                    <td className="num t12">{c.convs ? AAU.fmtNum(c.convs) : '—'}</td>
                    <td className="t12 subtle">{c.lastSync ? AAU.relTime ? relTime(c.lastSync) : fmtTime(c.lastSync) : '—'}</td>
                    <td className="num"><Button size="sm" variant="plain" icon="chevronRight" /></td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </Card>

        <Card title={cur.name} subtitle={chMeta(cur.ch).label + ' · ' + cur.handle}
          actions={<Badge tone={CH_STATUS[cur.status].tone} dot={CH_STATUS[cur.status].dot}>{CH_STATUS[cur.status].label}</Badge>}>
          <div className="grid gc2 mb16">
            <div><div className="t11 subtle">Token</div><div className="t14 fw6" style={{ color: cur.tokenDays == null ? 'var(--p-text-subdued)' : cur.tokenDays < 0 ? '#c4320a' : cur.tokenDays < 30 ? '#9a6700' : '#0e7c4a' }}>{cur.tokenDays == null ? '—' : cur.tokenDays < 0 ? 'Đã hết hạn' : 'Còn ' + cur.tokenDays + ' ngày'}</div></div>
            <div><div className="t11 subtle">Lịch đồng bộ</div><div className="t14 fw6">{cur.mode}</div></div>
          </div>
          {cur.status === 'error' && <div className="row g8 mb16" style={{ padding: 12, borderRadius: 10, background: 'var(--p-critical-bg)' }}><Icon name="alert" size={18} color="#8e1f0b" /><div className="t12"><b>Token hết hạn.</b> Cần Reauth để tiếp tục đồng bộ hội thoại.</div></div>}
          <div className="row g8 mb16" style={{ flexWrap: 'wrap' }}>
            <Button variant="primary" size="sm" icon="refresh" onClick={() => sync(cur.id)} disabled={cur.status === 'disconnected'}>Đồng bộ ngay</Button>
            <Button size="sm" icon="edit" onClick={() => setModal({ channel: cur })}>Sửa</Button>
            <Button size="sm" icon="lock" onClick={() => reauth(cur.id)}>Reauth</Button>
            <Button size="sm" variant="plain" onClick={() => test(cur.id)}>Test kết nối</Button>
            {window.Perm && window.Perm.canDelete() && <Button size="sm" variant="plain" icon="trash" onClick={() => purge(cur.id)}>Purge</Button>}
          </div>
          {testMsg && (() => {
            const M = { ok: ['checkCircle', '#0a7d4f', 'var(--p-success-bg)'], warn: ['alert', '#9a6700', 'var(--p-warning-bg)'], fail: ['alert', '#c4320a', 'var(--p-critical-bg)'], pending: ['refresh', '#6b7280', 'var(--p-bg-surface-secondary)'] };
            const [ic, col, bg] = M[testMsg.level] || M.pending;
            return <div className="row aic g8 mb16 t12" style={{ padding: '8px 11px', borderRadius: 9, background: bg }}><Icon name={ic} size={14} color={col} /><span style={{ lineHeight: 1.5 }}>{testMsg.text}</span></div>;
          })()}
          <div className="t11 fw7 subtle mb8">LỊCH SỬ ĐỒNG BỘ</div>
          <table className="tbl">
            <thead><tr><th>Thời điểm</th><th>Loại</th><th className="num">Tin mới</th><th className="num">Trạng thái</th></tr></thead>
            <tbody>{history.map((h, i) => <tr key={i}><td className="t12">{fmtTime(h.t)}</td><td className="t12 muted">{h.kind}</td><td className="num t12">{h.added}</td><td className="num">{h.status === 'ok' ? <Badge tone="success">{h.dur}</Badge> : <Badge tone="critical">{h.dur}</Badge>}</td></tr>)}</tbody>
          </table>
        </Card>
      </div>
      )}

      <div style={{ marginTop: 16 }}><AdsSourcesSection /></div>
      <div style={{ marginTop: 16 }}><TikTokSection /></div>
      <div style={{ marginTop: 16 }}><TikTokOrganicSection /></div>
      <div style={{ marginTop: 16 }}><PageContentSection /></div>
      <div style={{ marginTop: 16 }}><IGContentSection /></div>
      <div style={{ marginTop: 16 }}><ThreadsContentSection /></div>
      <div style={{ marginTop: 16 }}><WebTrackingSection /></div>
      <div style={{ marginTop: 16 }}><WebSheetsSection /></div>
      <div style={{ marginTop: 16 }}><ZaloPersonalSection /></div>
    </div>
  );
}

// Chọn NHIỀU nhân viên phụ trách 1 tài khoản Zalo cá nhân (popover checkbox).
// Lưu ngay mỗi lần tick (gọi onSave với danh sách userId mới).
function ZaloOwnerPicker({ acc, busy, onSave }) {
  const [open, setOpen] = useState(false);
  const ref = React.useRef(null);
  const owners = Array.isArray(acc.owners) ? acc.owners : (acc.owner ? [acc.owner] : []);
  useEffect(() => {
    if (!open) return;
    const onDoc = e => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', onDoc);
    return () => document.removeEventListener('mousedown', onDoc);
  }, [open]);
  const users = (AAU.users || []).filter(u => u.active !== false);
  const nameOf = id => ((AAU.users || []).find(u => u.id === id) || {}).name || id;
  const toggle = id => onSave(owners.includes(id) ? owners.filter(x => x !== id) : [...owners, id]);
  const label = busy ? '…' : owners.length === 0 ? '— Chưa gán (chỉ admin thấy) —'
    : owners.length <= 2 ? owners.map(nameOf).join(', ') : owners.length + ' người phụ trách';
  return (
    <div style={{ position: 'relative' }} ref={ref}>
      <button className="row aic g6" onClick={() => setOpen(o => !o)} disabled={busy}
        style={{ cursor: 'pointer', width: '100%', border: '1px solid var(--p-border)', borderRadius: 7, padding: '5px 9px', background: '#fff', justifyContent: 'space-between' }}>
        <span className="t12 fw5" style={{ color: owners.length ? 'var(--p-text)' : '#8a8f98', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{label}</span>
        <Icon name="chevronDown" size={13} />
      </button>
      {open && (
        <div style={{ position: 'absolute', top: '100%', left: 0, zIndex: 40, marginTop: 4, width: 240, maxHeight: 290, overflowY: 'auto', background: '#fff', border: '1px solid var(--p-border)', borderRadius: 10, boxShadow: 'var(--p-shadow-popover)', padding: 6 }}>
          <div className="t11 subtle" style={{ padding: '6px 8px 4px', fontWeight: 700 }}>GÁN NHIỀU NGƯỜI PHỤ TRÁCH</div>
          {users.map(u => { const on = owners.includes(u.id); return (
            <div key={u.id} className="row aic g8" onClick={() => toggle(u.id)} style={{ padding: '7px 8px', cursor: 'pointer', borderRadius: 7 }} onMouseEnter={e => e.currentTarget.style.background = 'var(--p-bg-surface-hover)'} onMouseLeave={e => e.currentTarget.style.background = ''}>
              <Checkbox on={on} onChange={() => {}} />
              <Avatar name={u.name} color={u.color} size={22} />
              <span className="t12 fw5 f1" style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{u.name}</span>
              {on && <Icon name="check" size={14} color="#0e7c4a" />}
            </div>
          ); })}
          {users.length === 0 && <div className="t12 muted" style={{ padding: 8 }}>Chưa có người dùng.</div>}
        </div>
      )}
    </div>
  );
}

// Zalo cá nhân (2 chiều) — nối qua zalo-bridge (zca-js, KHÔNG chính thức).
// Quét QR bằng app Zalo → giữ session → tin nhắn khách tự vào Inbox + tạo Lead.
function ZaloPersonalSection() {
  const live = !!(window.API && window.API.enabled);
  const [accs, setAccs] = useState([]);
  const [offline, setOffline] = useState(false);
  const [modal, setModal] = useState(false);
  const reload = () => {
    if (!live || !window.API.zaloAccounts) return;
    window.API.zaloAccounts().then(r => {
      const list = Array.isArray(r) ? r : (r && r.accounts) || [];
      setAccs(list); setOffline(!!(r && r.bridgeOffline));
    }).catch(() => { setAccs([]); setOffline(true); });
  };
  useEffect(() => { reload(); }, [live]);
  const [zbusy, setZbusy] = useState('');
  const [zmsg, setZmsg] = useState(null);
  const remove = async (id) => { if (!window.confirm('Gỡ tài khoản Zalo này? (đăng xuất + xoá session)')) return; await window.API.zaloAccountDelete(id); reload(); };
  const rename = async (id, cur) => {
    const name = window.prompt('Tên hiển thị cho tài khoản Zalo này:', cur || '');
    if (name == null || !name.trim()) return;
    setZbusy('rn:' + id); try { await window.API.zaloRename(id, name.trim()); reload(); } finally { setZbusy(''); }
  };
  const sync = async (id) => {
    setZbusy('sy:' + id); setZmsg(null);
    try { const r = await window.API.zaloSync(id); setZmsg({ level: r.ok ? 'ok' : 'fail', text: r.note || r.error || 'Đã làm mới kết nối.' }); reload(); }
    catch (e) { setZmsg({ level: 'fail', text: 'Đồng bộ lỗi: ' + ((e && e.message) || '') + ' — có thể cần quét QR lại.' }); }
    finally { setZbusy(''); }
  };
  // Gán tài khoản Zalo cho NHIỀU nhân viên — sau khi gán, CHỈ những người đó (và
  // admin/manager) thấy + trả lời được hội thoại của số này trong Inbox.
  const assign = async (id, owners) => {
    const names = owners.map(oid => ((AAU.users || []).find(x => x.id === oid) || {}).name || '');
    setZbusy('as:' + id);
    try { await window.API.zaloAssign(id, owners, names); reload(); }
    catch (e) { setZmsg({ level: 'fail', text: 'Gán phụ trách lỗi: ' + ((e && e.message) || '') }); }
    finally { setZbusy(''); }
  };
  const STT = { connected: ['success', 'Đang nối'], error: ['critical', 'Lỗi'], expired: ['warning', 'Hết hạn — quét lại'], disconnected: ['neutral', 'Mất kết nối'] };

  return (
    <Card pad={false} title="Kết nối Zalo cá nhân (2 chiều)" subtitle="Quét QR bằng app Zalo → tin nhắn khách tự vào Inbox + tạo Lead, và sales trả lời 2 chiều ngay trong Inbox. Gán mỗi số cho 1 nhân viên để phân quyền. KHÔNG chính thức (như Pancake) — dùng SỐ PHỤ vì có rủi ro khóa tài khoản."
      actions={<Button variant="primary" size="sm" icon="plus" onClick={() => setModal(true)} disabled={!live} title={live ? '' : 'Bật LIVE để kết nối'}>Thêm tài khoản Zalo</Button>}>
      {modal && <ZaloQRModal onClose={() => setModal(false)} onConnected={reload} />}
      {!live ? (
        <div className="row aic g8 t13 muted" style={{ padding: 16 }}><Icon name="alert" size={15} color="#b06f00" /><span>Demo (MOCK). Mở app với <code>?api=…</code> để kết nối.</span></div>
      ) : (
        <div style={{ padding: 16 }}>
          <div className="row aic g8 t12 mb12" style={{ padding: '8px 11px', borderRadius: 9, background: 'var(--p-warning-bg)' }}>
            <Icon name="alert" size={14} color="#9a6700" />
            <span style={{ lineHeight: 1.5 }}>Bản <b>không chính thức</b> (mô phỏng Zalo Web) — vi phạm ToS Zalo, có thể bị khóa số. Chỉ dùng <b>số phụ</b> chuyên sales. {offline && <b style={{ color: '#c4320a' }}>· zalo-bridge đang offline</b>}</span>
          </div>
          {accs.length === 0 ? (
            <EmptyState icon="message" title="Chưa nối tài khoản Zalo nào"
              desc="Bấm “Thêm tài khoản Zalo” → quét QR bằng app Zalo trên điện thoại (số phụ)."
              action={<Button variant="primary" icon="plus" onClick={() => setModal(true)}>Thêm tài khoản Zalo</Button>} />
          ) : (
            <table className="tbl">
              <thead><tr><th>Tài khoản</th><th>Phụ trách</th><th>Trạng thái</th><th>Đồng bộ gần nhất</th><th></th></tr></thead>
              <tbody>
                {accs.map(a => { const st = STT[a.status] || STT.disconnected; return (
                  <tr key={a.id}>
                    <td><div className="row aic g10">
                      <span className="center" style={{ width: 30, height: 30, borderRadius: 7, background: '#0068ff', color: '#fff', fontSize: 12, fontWeight: 700, flexShrink: 0 }}>Z</span>
                      <div><div className="t13 fw6">{a.name || a.id}</div><div className="t11 subtle">Zalo cá nhân</div></div>
                    </div></td>
                    <td style={{ minWidth: 180 }}>
                      <ZaloOwnerPicker acc={a} busy={zbusy === 'as:' + a.id} onSave={owners => assign(a.id, owners)} />
                    </td>
                    <td><Badge tone={st[0]} dot={a.status === 'connected'}>{st[1]}</Badge></td>
                    <td className="t12 subtle">{a.lastSync ? fmtTime(a.lastSync) : '—'}{a.status === 'error' && a.lastError ? ' · ' + a.lastError : ''}</td>
                    <td className="num"><div className="row g6" style={{ justifyContent: 'flex-end', flexWrap: 'wrap' }}>
                      <Button size="sm" variant="plain" icon="refresh" onClick={() => sync(a.id)} disabled={zbusy === 'sy:' + a.id} title="Làm mới kết nối (Zalo cá nhân chỉ nhận tin mới, không kéo lịch sử cũ)">{zbusy === 'sy:' + a.id ? '…' : 'Đồng bộ'}</Button>
                      <Button size="sm" variant="plain" icon="edit" onClick={() => rename(a.id, a.name)} disabled={zbusy === 'rn:' + a.id}>Sửa</Button>
                      {a.status !== 'connected' && <Button size="sm" variant="plain" icon="search" onClick={() => setModal(true)} title="Quét QR đăng nhập lại số này">Quét lại</Button>}
                      <Button size="sm" variant="plain" icon="trash" onClick={() => remove(a.id)}>Gỡ</Button>
                    </div></td>
                  </tr>
                ); })}
              </tbody>
            </table>
          )}
          {zmsg && (() => {
            const M = { ok: ['checkCircle', '#0a7d4f', 'var(--p-success-bg)'], fail: ['alert', '#c4320a', 'var(--p-critical-bg)'] };
            const [ic, col, bg] = M[zmsg.level] || M.fail;
            return <div className="row aic g8 t12 mt12" style={{ padding: '8px 11px', borderRadius: 9, background: bg }}><Icon name={ic} size={14} color={col} /><span style={{ lineHeight: 1.5 }}>{zmsg.text}</span></div>;
          })()}
        </div>
      )}
    </Card>
  );
}

// ZaloQRModal — bắt đầu QR login → poll trạng thái → hiện QR để quét → connected.
function ZaloQRModal({ onClose, onConnected }) {
  const [state, setState] = useState({ status: 'starting', qr: null, error: '' });
  const loginIdRef = React.useRef(null);
  const timerRef = React.useRef(null);

  useEffect(() => {
    let alive = true;
    const poll = async () => {
      if (!alive || !loginIdRef.current) return;
      try {
        const st = await window.API.zaloLoginStatus(loginIdRef.current);
        if (!alive) return;
        setState(st);
        if (st.status === 'connected') { setTimeout(() => { onConnected && onConnected(); onClose(); }, 900); return; }
        if (['error', 'expired', 'declined'].includes(st.status)) return; // dừng poll
      } catch (e) { if (alive) setState(s => ({ ...s, error: (e && e.message) || 'lỗi' })); }
      timerRef.current = setTimeout(poll, 1500);
    };
    (async () => {
      try {
        const r = await window.API.zaloLoginStart();
        if (!alive) return;
        loginIdRef.current = r.loginId;
        setState({ status: 'pending', qr: null, error: '' });
        poll();
      } catch (e) { if (alive) setState({ status: 'error', qr: null, error: (e && e.message) || 'không khởi tạo được' }); }
    })();
    return () => { alive = false; if (timerRef.current) clearTimeout(timerRef.current); };
  }, []);

  const LABEL = { starting: 'Đang khởi tạo…', pending: 'Đang lấy mã QR…', qr: 'Mở app Zalo → Quét QR', scanned: 'Đã quét — xác nhận trên điện thoại…', connected: 'Đã kết nối ✓', expired: 'Mã QR hết hạn', declined: 'Đã từ chối trên điện thoại', error: 'Lỗi kết nối' };
  const failed = ['error', 'expired', 'declined'].includes(state.status);

  return (
    <Modal title="Kết nối tài khoản Zalo (quét QR)" width={460} onClose={onClose}
      footer={<Button variant="plain" onClick={onClose}>{state.status === 'connected' ? 'Đóng' : 'Hủy'}</Button>}>
      <div className="row aic g8 t12 mb12" style={{ padding: '8px 11px', borderRadius: 9, background: 'var(--p-warning-bg)' }}>
        <Icon name="alert" size={14} color="#9a6700" /><span style={{ lineHeight: 1.5 }}>Dùng <b>số phụ</b> chuyên sales — bản không chính thức có rủi ro khóa số.</span>
      </div>
      <div className="center" style={{ minHeight: 240, flexDirection: 'column', gap: 12 }}>
        {state.qr && state.status === 'qr' ? (
          <img src={state.qr} alt="QR Zalo" style={{ width: 220, height: 220, borderRadius: 12, border: '1px solid var(--p-border)' }} />
        ) : (
          <div className="center" style={{ width: 220, height: 220, borderRadius: 12, background: 'var(--p-bg-surface-secondary)' }}>
            <Icon name={failed ? 'alert' : 'refresh'} size={32} color={failed ? '#c4320a' : '#6b7280'} />
          </div>
        )}
        <div className="t13 fw6" style={{ color: failed ? '#c4320a' : (state.status === 'connected' ? '#0a7d4f' : 'inherit') }}>{LABEL[state.status] || state.status}</div>
        {state.error && <div className="t12 subtle">{state.error}</div>}
        {failed && <Button size="sm" variant="primary" icon="refresh" onClick={() => { loginIdRef.current = null; setState({ status: 'starting', qr: null, error: '' }); window.API.zaloLoginStart().then(r => { loginIdRef.current = r.loginId; setState({ status: 'pending', qr: null, error: '' }); }); }}>Lấy QR mới</Button>}
      </div>
      <div className="t11 subtle mt12" style={{ lineHeight: 1.5 }}>App Zalo trên điện thoại → bấm <b>+</b> (góc phải) → <b>Quét QR</b> → quét mã trên. Sau khi nối, tin nhắn khách sẽ tự vào <b>Hội thoại</b> + tạo Lead.</div>
    </Modal>
  );
}

// Form thêm/sửa 1 nguồn ads (Facebook Ad account). Có `source` ⇒ SỬA (prefill +
// PUT); không có ⇒ thêm mới (POST). Test ngay khi lưu (gọi Graph API xác thực).
function AdSourceModal({ source, onClose, onSaved }) {
  const editing = !!source;
  const [name, setName] = useState(source ? source.name || '' : '');
  const [acct, setAcct] = useState(source ? source.adAccount || '' : '');
  const [ver, setVer] = useState(source ? source.apiVersion || 'v21.0' : 'v21.0');
  const [token, setToken] = useState('');
  const [busy, setBusy] = useState(false);
  const [err, setErr] = useState(null);
  const save = async () => {
    if (busy || !acct.trim()) return;
    if (!editing && !token.trim()) { setErr('Lần đầu cần dán Access Token (ads_read).'); return; }
    setBusy(true); setErr(null);
    try {
      const body = { platform: 'facebook', name: name.trim(), adAccount: acct.trim(), apiVersion: ver.trim() };
      if (token.trim()) body.token = token.trim();
      if (editing) await window.API.adSourceUpdate(source.id, body);
      else await window.API.adSourceCreate(body);
      onSaved(); onClose();
    } catch (e) { setErr((e && e.message) || 'Không lưu được'); }
    finally { setBusy(false); }
  };
  return (
    <Modal title={editing ? 'Sửa nguồn Facebook Ads' : 'Thêm nguồn Facebook Ads'} width={540} onClose={onClose}
      footer={<><Button variant="plain" onClick={onClose}>Hủy</Button>
        <Button variant="primary" icon={editing ? 'edit' : 'plus'} onClick={save} disabled={busy || !acct.trim()}>{busy ? 'Đang kiểm tra…' : (editing ? 'Test & Lưu' : 'Test & Thêm')}</Button></>}>
      <Field label="Tên hiển thị"><Input value={name} onChange={setName} placeholder="VD: PEACH Ads 1" autoFocus /></Field>
      <Field label="Ad Account ID"><Input value={acct} onChange={setAcct} placeholder="act_1234567890 (hoặc 1234567890)" /></Field>
      <Field label={editing ? 'Đổi Access Token (để trống = giữ token cũ)' : 'Access Token (quyền ads_read)'}><Input value={token} onChange={setToken} type="password" placeholder={editing ? 'Dán token mới để thay' : 'EAAB… token ads_read'} /></Field>
      <Field label="Graph API version"><Input value={ver} onChange={setVer} placeholder="v21.0" /></Field>
      {err && <div className="row aic g8 t12" style={{ padding: '8px 11px', borderRadius: 9, background: 'var(--p-critical-bg)' }}><Icon name="alert" size={14} color="#c4320a" /><span style={{ lineHeight: 1.5 }}>{err}</span></div>}
      <div className="t11 subtle mt8" style={{ lineHeight: 1.5 }}>Bấm <b>Test</b>: app gọi Facebook xác thực token+account rồi lưu. Token cất ở kho riêng (không lộ ra API công khai).</div>
    </Modal>
  );
}

// Nguồn quảng cáo (Ads) — danh sách NHIỀU nguồn FB Ads, thêm/sửa/test/đồng bộ/xóa
// như kênh chat (Quyết định #2: chat = Inbox/QC · ads = đo hiệu quả Marketing).
// Nguồn "Đang dùng" (active) nuôi report/sync của Ad Tracking.
function AdsSourcesSection() {
  const live = !!(window.API && window.API.enabled);
  const [list, setList] = useState([]);
  const [sel, setSel] = useState(null);
  const [modal, setModal] = useState(null); // null=đóng · {}=thêm · {source}=sửa
  const [busy, setBusy] = useState('');     // id đang chạy thao tác
  const [testMsg, setTestMsg] = useState(null);

  const reload = () => { if (live && window.API.adSources) window.API.adSources().then(ls => { if (Array.isArray(ls)) { setList(ls); setSel(s => (ls.find(x => x.id === s) ? s : (ls[0] ? ls[0].id : null))); } }).catch(() => { }); };
  useEffect(() => { reload(); }, [live]);
  const cur = list.find(s => s.id === sel) || list[0] || null;

  const afterChange = () => { setTestMsg(null); reload(); };
  const sync = async (id) => {
    setBusy(id + ':sync'); setTestMsg(null);
    try { const r = await window.API.adSourceSync(id); if (window.API.hydrate) await window.API.hydrate(); setTestMsg({ level: 'ok', text: 'Đã đồng bộ' + (r && r.synced != null ? ' · ' + r.synced + ' campaign' : '') + ' từ Facebook.' }); reload(); }
    catch (e) { setTestMsg({ level: 'fail', text: 'Đồng bộ lỗi: ' + ((e && e.message) || 'không rõ') }); }
    finally { setBusy(''); }
  };
  const test = async (id) => {
    setBusy(id + ':test'); setTestMsg({ level: 'pending', text: 'Đang kiểm tra…' });
    try { const r = await window.API.adSourceTest(id); setTestMsg({ level: r.level || (r.ok ? 'ok' : 'fail'), text: r.detail }); reload(); }
    catch (e) { setTestMsg({ level: 'fail', text: 'Lỗi: ' + ((e && e.message) || 'không rõ') }); }
    finally { setBusy(''); }
  };
  const activate = async (id) => { setBusy(id + ':act'); try { await window.API.adSourceActivate(id); afterChange(); } finally { setBusy(''); } };
  const remove = async (id) => {
    if (!window.confirm('Gỡ nguồn ads này? (token sẽ bị xóa)')) return;
    setBusy(id + ':del');
    try { await window.API.adSourceDelete(id); setSel(null); afterChange(); } finally { setBusy(''); }
  };

  const STT = { connected: { tone: 'success', label: 'Đã kết nối', dot: true }, error: { tone: 'critical', label: 'Lỗi token' }, disconnected: { tone: 'neutral', label: 'Chưa kết nối' } };

  return (
    <Card pad={false} title="Nguồn quảng cáo (Ads)" subtitle="Nhiều tài khoản Facebook Ads — thêm/sửa/test/đồng bộ như kênh chat. Nguồn “Đang dùng” nuôi Ad Tracking. (chat ở trên = Inbox/QC, tách riêng)"
      actions={<Button variant="primary" size="sm" icon="plus" onClick={() => setModal({})} disabled={!live} title={live ? '' : 'Bật LIVE để thêm nguồn'}>Thêm nguồn ads</Button>}>
      {modal && <AdSourceModal source={modal.source} onClose={() => setModal(null)} onSaved={afterChange} />}
      {!live ? (
        <div className="row aic g8 t13 muted" style={{ padding: 16 }}><Icon name="alert" size={15} color="#b06f00" /><span>Đang ở chế độ demo (MOCK). Mở app với <code>?api=http://localhost:8080</code> để quản lý nguồn ads thật.</span></div>
      ) : list.length === 0 ? (
        <div style={{ padding: 16 }}>
          <EmptyState icon="dollar" title="Chưa có nguồn ads nào"
            desc="Thêm tài khoản Facebook Ads (token ads_read + ad account) để kéo chi phí/lead thật về Marketing."
            action={<Button variant="primary" icon="plus" onClick={() => setModal({})}>Thêm nguồn ads</Button>} />
        </div>
      ) : (
        <div className="grid" style={{ gridTemplateColumns: '1.15fr 1fr', alignItems: 'start' }}>
          <table className="tbl">
            <thead><tr><th>Nguồn</th><th>Trạng thái</th><th>Tài khoản</th><th></th></tr></thead>
            <tbody>
              {list.map(s => { const st = STT[s.status] || STT.disconnected; return (
                <tr key={s.id} onClick={() => { setSel(s.id); setTestMsg(null); }} style={{ cursor: 'pointer', background: s.id === (cur && cur.id) ? 'var(--p-bg-surface-secondary)' : undefined }}>
                  <td><div className="row aic g10">
                    <span className="center" style={{ width: 30, height: 30, borderRadius: 7, background: '#0084ff', color: '#fff', fontSize: 12, fontWeight: 700, flexShrink: 0 }}>F</span>
                    <div><div className="t13 fw6">{s.name || s.adAccount}{s.active && <span style={{ marginLeft: 6 }}><Badge tone="success" dot>Đang dùng</Badge></span>}</div><div className="t11 subtle">Facebook Ads</div></div>
                  </div></td>
                  <td><Badge tone={st.tone} dot={st.dot}>{st.label}</Badge></td>
                  <td className="t12 subtle">{s.adAccount}</td>
                  <td className="num"><Button size="sm" variant="plain" icon="chevronRight" /></td>
                </tr>
              ); })}
            </tbody>
          </table>

          {cur && (
            <Card title={cur.name || cur.adAccount} subtitle={'Facebook Ads · ' + cur.adAccount}
              actions={<Badge tone={(STT[cur.status] || STT.disconnected).tone} dot>{(STT[cur.status] || STT.disconnected).label}</Badge>}>
              <div className="grid gc2 mb16">
                <div><div className="t11 subtle">Token</div><div className="t14 fw6">{cur.hasToken ? (cur.tokenMask || '••••') : '— chưa có'}</div></div>
                <div><div className="t11 subtle">Đồng bộ gần nhất</div><div className="t14 fw6">{cur.lastSync ? fmtTime(cur.lastSync) : '—'}</div></div>
              </div>
              {cur.status === 'error' && cur.lastError && <div className="row g8 mb16" style={{ padding: 12, borderRadius: 10, background: 'var(--p-critical-bg)' }}><Icon name="alert" size={18} color="#8e1f0b" /><div className="t12"><b>Token lỗi.</b> {cur.lastError} — bấm Sửa để dán token mới.</div></div>}
              <div className="row g8 mb16" style={{ flexWrap: 'wrap' }}>
                <Button variant="primary" size="sm" icon="refresh" onClick={() => sync(cur.id)} disabled={busy === cur.id + ':sync'}>{busy === cur.id + ':sync' ? 'Đang đồng bộ…' : 'Đồng bộ ngay'}</Button>
                <Button size="sm" icon="edit" onClick={() => setModal({ source: cur })}>Sửa</Button>
                <Button size="sm" variant="plain" onClick={() => test(cur.id)} disabled={busy === cur.id + ':test'}>Test kết nối</Button>
                {!cur.active && <Button size="sm" variant="plain" icon="checkCircle" onClick={() => activate(cur.id)} disabled={busy === cur.id + ':act'}>Đặt làm nguồn dùng</Button>}
                <Button size="sm" variant="plain" icon="trash" onClick={() => remove(cur.id)} disabled={busy === cur.id + ':del'}>Gỡ</Button>
              </div>
              {testMsg && (() => {
                const M = { ok: ['checkCircle', '#0a7d4f', 'var(--p-success-bg)'], warn: ['alert', '#9a6700', 'var(--p-warning-bg)'], fail: ['alert', '#c4320a', 'var(--p-critical-bg)'], pending: ['refresh', '#6b7280', 'var(--p-bg-surface-secondary)'] };
                const [ic, col, bg] = M[testMsg.level] || M.pending;
                return <div className="row aic g8 t12" style={{ padding: '8px 11px', borderRadius: 9, background: bg }}><Icon name={ic} size={14} color={col} /><span style={{ lineHeight: 1.5 }}>{testMsg.text}</span></div>;
              })()}
              <div className="t11 subtle mt12" style={{ lineHeight: 1.5 }}>Token cất ở kho riêng, không lộ ra API công khai. Nguồn “Đang dùng” là tài khoản mà Ad Tracking kéo số. TikTok Ads ở card riêng bên dưới; Google sẽ thêm khi có connector.</div>
            </Card>
          )}
        </div>
      )}
    </Card>
  );
}

// Nguồn TikTok Ads — kết nối qua OAuth (App TikTok for Business · Marketing API).
// Kéo spend/lead cấp campaign → mktFacts (nguồn tiktok_ads) + comment dưới quảng
// cáo. Organic (follower/video) ở card RIÊNG bên dưới (Display API). KHÔNG có DM.
function TikTokSection() {
  const live = !!(window.API && window.API.enabled);
  const [conn, setConn] = useState(null);
  const [rep, setRep] = useState(null);
  const [busy, setBusy] = useState('');
  const [msg, setMsg] = useState(null);

  const last30 = () => {
    const to = new Date();
    const from = new Date(to.getTime() - 29 * 864e5);
    const iso = d => d.toISOString().slice(0, 10);
    return { from: iso(from), to: iso(to) };
  };

  const reload = () => {
    if (!live || !window.API.tiktokConn) return;
    window.API.tiktokConn().then(st => {
      setConn(st);
      if (st && st.connected) window.API.tiktokReport({ ...last30(), gran: 'day' }).then(r => setRep(r && r.current)).catch(() => { });
    }).catch(() => { });
  };
  useEffect(() => { reload(); }, [live]);

  const connected = conn && conn.connected;
  const startOAuth = async () => {
    setBusy('oauth');
    try {
      const r = await window.API.tiktokOAuthStart(location.href);
      if (r && r.authUrl) location.href = r.authUrl;
      else setMsg({ level: 'fail', text: 'Không lấy được link uỷ quyền (app chưa cấu hình?).' });
    } catch (e) { setMsg({ level: 'fail', text: (e && e.message) || 'Lỗi mở OAuth' }); }
    finally { setBusy(''); }
  };
  const switchAdv = async (id) => {
    setBusy('adv'); setMsg(null);
    try { await window.API.tiktokSetAdvertiser(id); reload(); }
    catch (e) { setMsg({ level: 'fail', text: 'Đổi tài khoản lỗi: ' + ((e && e.message) || '') }); }
    finally { setBusy(''); }
  };
  const sync = async () => {
    setBusy('sync'); setMsg({ level: 'pending', text: 'Đang kéo số TikTok Ads…' });
    try {
      const r = await window.API.tiktokSync(last30());
      setMsg({ level: 'ok', text: 'Đã đồng bộ ' + (r.synced || 0) + ' campaign · chi tiêu ' + AAU.fmtNum(r.totalSpend || 0) + 'đ · ' + (r.totalLeads || 0) + ' chuyển đổi' });
      reload();
    } catch (e) { setMsg({ level: 'fail', text: 'Đồng bộ lỗi: ' + ((e && e.message) || '') }); }
    finally { setBusy(''); }
  };
  const disconnect = async () => {
    if (!window.confirm('Gỡ kết nối TikTok Ads? (token bị xoá, số đã kéo vẫn giữ)')) return;
    setBusy('del');
    try { await window.API.tiktokDisconnect(); setRep(null); reload(); } finally { setBusy(''); }
  };

  const T = rep && rep.totals;
  return (
    <Card pad={false} title="Nguồn TikTok Ads" subtitle="OAuth App TikTok for Business (Marketing API) → kéo spend/lead cấp campaign vào mktFacts (nguồn tiktok_ads) + comment dưới quảng cáo. Organic (follower/video) ở card riêng bên dưới. KHÔNG có API tin nhắn (DM)."
      actions={<Button variant="primary" size="sm" icon={connected ? 'refresh' : 'plus'} onClick={connected ? sync : startOAuth} disabled={!live || busy === 'oauth' || busy === 'sync'} title={live ? '' : 'Bật LIVE để kết nối'}>{connected ? (busy === 'sync' ? 'Đang đồng bộ…' : 'Đồng bộ ngay') : (busy === 'oauth' ? 'Đang mở…' : 'Kết nối TikTok Ads')}</Button>}>
      {!live ? (
        <div className="row aic g8 t13 muted" style={{ padding: 16 }}><Icon name="alert" size={15} color="#b06f00" /><span>Đang ở chế độ demo (MOCK). Mở app LIVE để kết nối TikTok thật.</span></div>
      ) : !connected ? (
        <div style={{ padding: 16 }}>
          <EmptyState icon="bolt" title="Chưa nối TikTok Ads"
            desc="Bấm Kết nối → đăng nhập tài khoản TikTok Ads của AAU → uỷ quyền. Token tự lưu, kéo được spend/lead cấp campaign + comment dưới quảng cáo."
            action={<Button variant="primary" icon="plus" onClick={startOAuth} disabled={busy === 'oauth'}>{busy === 'oauth' ? 'Đang mở…' : 'Kết nối TikTok Ads'}</Button>} />
        </div>
      ) : (
        <div style={{ padding: 16 }}>
          <div className="row aic g8 t12 mb12" style={{ padding: '8px 11px', borderRadius: 9, background: 'var(--p-success-bg)' }}>
            <Icon name="checkCircle" size={14} color="#0a7d4f" />
            <span>Đang nối: <b>{conn.advertiserName || conn.advertiserId}</b> · token {conn.tokenMask}</span>
          </div>

          {Array.isArray(conn.advertisers) && conn.advertisers.length > 1 && (
            <Field label="Tài khoản quảng cáo đang dùng (token có nhiều tài khoản)">
              <select className="input" value={conn.advertiserId} onChange={e => switchAdv(e.target.value)} disabled={busy === 'adv'}>
                {conn.advertisers.map(a => <option key={a.id} value={a.id}>{a.name} ({a.id})</option>)}
              </select>
            </Field>
          )}

          {T && (
            <div className="grid gc4 mb12">
              <KPI label="Chi tiêu (30 ngày)" value={AAU.fmtNum(T.spend || 0) + 'đ'} sub={'CPL ' + AAU.fmtNum(Math.round(T.cpl || 0)) + 'đ'} icon="dollar" />
              <KPI label="Hiển thị" value={AAU.fmtNum(T.impressions || 0)} sub={'CTR ' + (T.ctr || 0).toFixed(2) + '%'} icon="eye" />
              <KPI label="Click" value={AAU.fmtNum(T.clicks || 0)} sub={'reach ' + AAU.fmtNum(T.reach || 0)} icon="bolt" />
              <KPI label="Chuyển đổi" value={AAU.fmtNum(T.leads || 0)} sub="lead/conversion" icon="target" />
            </div>
          )}
          {T && (T.spend || 0) === 0 && <div className="row aic g8 t12 mb12" style={{ padding: '8px 11px', borderRadius: 9, background: 'var(--p-warning-bg)' }}><Icon name="alert" size={14} color="#9a6700" /><span>Tài khoản này chưa có chi tiêu trong 30 ngày qua. Nếu chạy ad ở tài khoản khác, chọn lại ở ô “Tài khoản quảng cáo đang dùng”.</span></div>}

          {msg && (() => {
            const M = { ok: ['checkCircle', '#0a7d4f', 'var(--p-success-bg)'], warn: ['alert', '#9a6700', 'var(--p-warning-bg)'], fail: ['alert', '#c4320a', 'var(--p-critical-bg)'], pending: ['refresh', '#6b7280', 'var(--p-bg-surface-secondary)'] };
            const [ic, col, bg] = M[msg.level] || M.pending;
            return <div className="row aic g8 t12 mb12" style={{ padding: '8px 11px', borderRadius: 9, background: bg }}><Icon name={ic} size={14} color={col} /><span style={{ lineHeight: 1.5 }}>{msg.text}</span></div>;
          })()}
          <div className="row g8" style={{ flexWrap: 'wrap' }}>
            <Button variant="primary" size="sm" icon="refresh" onClick={sync} disabled={busy === 'sync'}>{busy === 'sync' ? 'Đang đồng bộ…' : 'Đồng bộ Ads ngay'}</Button>
            <Button size="sm" variant="plain" icon="externalLink" onClick={startOAuth} disabled={busy === 'oauth'}>Nối lại / đổi tài khoản</Button>
            <Button size="sm" variant="plain" icon="trash" onClick={disconnect} disabled={busy === 'del'}>Gỡ kết nối</Button>
          </div>
          <div className="t11 subtle mt12" style={{ lineHeight: 1.5 }}>Token cất kho riêng (không lộ ra API công khai). Đồng bộ kéo 30 ngày gần nhất (giới hạn TikTok theo ngày) vào <code>mktFacts</code> nguồn <code>tiktok_ads</code>.</div>
        </div>
      )}
    </Card>
  );
}

// Nguồn TikTok Organic (@kênh) — Display API (app riêng developers.tiktok.com, Login
// Kit). Đọc số organic THẬT của kênh: follower/tổng like/số video + từng video
// (views/like/comment/share). Token Bearer hết hạn 24h, tự refresh. KHÁC hẳn Ads.
function TikTokOrganicSection() {
  const live = !!(window.API && window.API.enabled);
  const [conn, setConn] = useState(null);
  const [ov, setOv] = useState(null);
  const [vids, setVids] = useState(null);
  const [busy, setBusy] = useState('');
  const [msg, setMsg] = useState(null);

  const reload = () => {
    if (!live || !window.API.tiktokOrganicConn) return;
    window.API.tiktokOrganicConn().then(st => {
      setConn(st);
      if (st && st.connected) {
        window.API.tiktokOrganicOverview().then(o => setOv(o && o.profile)).catch(() => { });
        window.API.tiktokOrganicVideos().then(setVids).catch(() => { });
      }
    }).catch(() => { });
  };
  useEffect(() => { reload(); }, [live]);

  const connected = conn && conn.connected;
  const startOAuth = async () => {
    setBusy('oauth');
    try {
      const r = await window.API.tiktokOrganicOAuthStart(location.href);
      if (r && r.authUrl) location.href = r.authUrl;
      else setMsg({ level: 'fail', text: 'Chưa cấu hình app Display API (Client key/secret).' });
    } catch (e) { setMsg({ level: 'fail', text: (e && e.message) || 'Lỗi mở OAuth' }); }
    finally { setBusy(''); }
  };
  const refresh = async () => {
    setBusy('sync'); setMsg({ level: 'pending', text: 'Đang kéo số organic…' });
    try {
      const [o, v] = await Promise.all([window.API.tiktokOrganicOverview(), window.API.tiktokOrganicVideos()]);
      setOv(o && o.profile); setVids(v);
      setMsg({ level: 'ok', text: 'Đã cập nhật: ' + AAU.fmtNum((o && o.profile && o.profile.followers) || 0) + ' follower · ' + (v && v.count || 0) + ' video gần đây' });
    } catch (e) { setMsg({ level: 'fail', text: 'Kéo số lỗi: ' + ((e && e.message) || '') }); }
    finally { setBusy(''); }
  };
  const disconnect = async () => {
    if (!window.confirm('Gỡ kết nối kênh TikTok organic?')) return;
    setBusy('del');
    try { await window.API.tiktokOrganicDisconnect(); setOv(null); setVids(null); reload(); } finally { setBusy(''); }
  };

  return (
    <Card pad={false} title="Nguồn TikTok Organic (@kênh)" subtitle="Display API (app riêng, Login Kit) → đọc số organic THẬT của kênh TikTok: follower · tổng like · video views + từng video (views/like/comment/share). Token tự gia hạn. Tách hẳn cổng Ads."
      actions={<Button variant="primary" size="sm" icon={connected ? 'refresh' : 'plus'} onClick={connected ? refresh : startOAuth} disabled={!live || busy === 'oauth' || busy === 'sync'} title={live ? '' : 'Bật LIVE để kết nối'}>{connected ? (busy === 'sync' ? 'Đang kéo…' : 'Cập nhật số') : (busy === 'oauth' ? 'Đang mở…' : 'Kết nối kênh TikTok')}</Button>}>
      {!live ? (
        <div className="row aic g8 t13 muted" style={{ padding: 16 }}><Icon name="alert" size={15} color="#b06f00" /><span>Đang ở chế độ demo (MOCK). Mở app LIVE để kết nối.</span></div>
      ) : !connected ? (
        <div style={{ padding: 16 }}>
          <EmptyState icon="user" title="Chưa nối kênh TikTok organic"
            desc="Bấm Kết nối → đăng nhập đúng tài khoản kênh (vd @aau_academy) → uỷ quyền. Đọc follower, lượt xem, hiệu suất từng video. (App Display API; sandbox cần thêm kênh làm target user.)"
            action={<Button variant="primary" icon="plus" onClick={startOAuth} disabled={busy === 'oauth'}>{busy === 'oauth' ? 'Đang mở…' : 'Kết nối kênh TikTok'}</Button>} />
        </div>
      ) : (
        <div style={{ padding: 16 }}>
          <div className="row aic g8 t12 mb12" style={{ padding: '8px 11px', borderRadius: 9, background: 'var(--p-success-bg)' }}>
            <Icon name="checkCircle" size={14} color="#0a7d4f" />
            <span>Đang nối kênh: <b>{conn.displayName || conn.openId || 'TikTok'}</b>{conn.tokenExpiresInH != null ? ' · token còn ' + conn.tokenExpiresInH + 'h (tự gia hạn)' : ''}</span>
          </div>

          {ov && (
            <div className="grid gc4 mb12">
              <KPI label="Follower" value={AAU.fmtNum(ov.followers || 0)} sub={ov.verified ? 'đã xác minh' : 'tài khoản'} icon="user" />
              <KPI label="Tổng lượt thích" value={AAU.fmtNum(ov.likes || 0)} sub="mọi video" icon="star" />
              <KPI label="Số video" value={AAU.fmtNum(ov.videoCount || 0)} sub="đã đăng" icon="eye" />
              <KPI label="Đang theo dõi" value={AAU.fmtNum(ov.following || 0)} sub="following" icon="users" />
            </div>
          )}
          {vids && vids.count > 0 && (
            <KPI label={'Tổng view ' + vids.count + ' video gần đây'} value={AAU.fmtNum(vids.totalViews || 0)} sub={AAU.fmtNum(vids.totalLikes || 0) + ' like'} icon="eye" />
          )}
          {vids && Array.isArray(vids.videos) && vids.videos.length > 0 && (
            <table className="tbl mt12 mb12">
              <thead><tr><th style={{ textAlign: 'left' }}>Video</th><th className="num">Views</th><th className="num">Like</th><th className="num">Comment</th><th className="num">Share</th><th className="num">ER%</th></tr></thead>
              <tbody>{vids.videos.slice(0, 10).map((v, i) => (
                <tr key={v.id || i}>
                  <td className="t12" style={{ maxWidth: 280, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{v.url ? <a href={v.url} target="_blank" rel="noreferrer">{v.title || '(không tiêu đề)'}</a> : (v.title || '(không tiêu đề)')}</td>
                  <td className="num t12">{AAU.fmtNum(v.views || 0)}</td>
                  <td className="num t12">{AAU.fmtNum(v.likes || 0)}</td>
                  <td className="num t12">{AAU.fmtNum(v.comments || 0)}</td>
                  <td className="num t12">{AAU.fmtNum(v.shares || 0)}</td>
                  <td className="num t12">{(v.engRate || 0).toFixed(1)}</td>
                </tr>
              ))}</tbody>
            </table>
          )}

          {msg && (() => {
            const M = { ok: ['checkCircle', '#0a7d4f', 'var(--p-success-bg)'], warn: ['alert', '#9a6700', 'var(--p-warning-bg)'], fail: ['alert', '#c4320a', 'var(--p-critical-bg)'], pending: ['refresh', '#6b7280', 'var(--p-bg-surface-secondary)'] };
            const [ic, col, bg] = M[msg.level] || M.pending;
            return <div className="row aic g8 t12 mb12" style={{ padding: '8px 11px', borderRadius: 9, background: bg }}><Icon name={ic} size={14} color={col} /><span style={{ lineHeight: 1.5 }}>{msg.text}</span></div>;
          })()}
          <div className="row g8" style={{ flexWrap: 'wrap' }}>
            <Button variant="primary" size="sm" icon="refresh" onClick={refresh} disabled={busy === 'sync'}>{busy === 'sync' ? 'Đang kéo…' : 'Cập nhật số'}</Button>
            <Button size="sm" variant="plain" icon="externalLink" onClick={startOAuth} disabled={busy === 'oauth'}>Nối lại / đổi kênh</Button>
            <Button size="sm" variant="plain" icon="trash" onClick={disconnect} disabled={busy === 'del'}>Gỡ kết nối</Button>
          </div>
          <div className="t11 subtle mt12" style={{ lineHeight: 1.5 }}>Token Bearer cất kho riêng (private), hết hạn 24h → hệ thống tự refresh bằng refresh_token. Số organic đọc trực tiếp qua Display API (open.tiktokapis.com).</div>
        </div>
      )}
    </Card>
  );
}

// Nguồn Page & Nội dung (Organic) — 1 token System User → khám phá MỌI tài sản
// (Page · Instagram · Ad account) rồi bật từng kết nối. Nuôi L3 Content Analytics
// (Page/Post Insights). Tách khỏi "Nguồn quảng cáo" (chỉ ads) ở trên.
function PageContentSection() {
  const live = !!(window.API && window.API.enabled);
  const [conn, setConn] = useState(null);
  const [modal, setModal] = useState(false);
  const [busy, setBusy] = useState('');
  const [msg, setMsg] = useState(null); // {level, text}
  const reload = () => { if (live && window.API.connList) window.API.connList().then(setConn).catch(() => { }); };
  useEffect(() => { reload(); }, [live]);

  const connected = conn && conn.connected;
  const assets = (conn && conn.assets) || [];
  const grp = { page: assets.filter(a => a.type === 'page'), ig: assets.filter(a => a.type === 'ig'), ad: assets.filter(a => a.type === 'ad') };
  const disconnect = async () => { if (!window.confirm('Gỡ token Page và toàn bộ kết nối nội dung?')) return; await window.API.connDisconnect(); reload(); };
  const removeOne = async (id) => {
    setBusy('rm:' + id); setMsg(null);
    try { await window.API.connSelect(assets.filter(a => a.id !== id)); reload(); }
    catch (e) { setMsg({ level: 'fail', text: 'Bỏ kết nối lỗi: ' + ((e && e.message) || '') }); }
    finally { setBusy(''); }
  };
  const checkToken = async () => {
    setBusy('check'); setMsg({ level: 'pending', text: 'Đang kiểm tra token với Facebook…' });
    try {
      const r = await window.API.connDiscover({}); // quét lại bằng token đã lưu
      setMsg({ level: r.permanent ? 'ok' : 'warn', text: r.permanent ? `Token còn tốt — vĩnh viễn · thấy ${r.counts.pages} Page · ${r.counts.instagram} IG · ${r.counts.ads} Ad.` : `Token còn dùng được nhưng CÓ HẠN — nên đổi sang System User. Thấy ${r.counts.pages} Page.` });
      reload();
    } catch (e) { setMsg({ level: 'fail', text: 'Token lỗi/hết hạn: ' + ((e && e.message) || '') + ' — bấm “Sửa / dán token” để cập nhật.' }); }
    finally { setBusy(''); }
  };
  const syncContent = async () => {
    if (!grp.page.length) { setMsg({ level: 'warn', text: 'Chưa bật Page nào để đồng bộ.' }); return; }
    setBusy('sync'); setMsg({ level: 'pending', text: 'Đang kéo bài viết & insights (full) từ ' + grp.page.length + ' Page…' });
    let total = 0, errs = 0;
    for (const pg of grp.page) {
      try { const r = await window.API.contentInsights({ pageId: pg.id, limit: 100 }); total += ((r && r.posts) || []).length; }
      catch (e) { errs++; }
    }
    setMsg({ level: errs ? 'warn' : 'ok', text: `Đã kéo ${total} bài từ ${grp.page.length} Page` + (errs ? ` · ${errs} page lỗi (token?)` : '') + '. (Sẽ cắm vào dashboard Content Analytics ở bản sau.)' });
    setBusy('');
  };

  return (
    <Card pad={false} title="Nguồn Page & Nội dung (Organic)" subtitle="1 token System User → khám phá Page · Instagram · Ad account rồi bật từng kết nối. Nuôi Content Analytics (Page/Post Insights). Token vĩnh viễn nếu là System User."
      actions={<Button variant="primary" size="sm" icon="search" onClick={() => setModal(true)} disabled={!live} title={live ? '' : 'Bật LIVE để kết nối'}>{connected ? 'Quét lại' : 'Khám phá kết nối'}</Button>}>
      {modal && <DiscoverModal conn={conn} onClose={() => setModal(false)} onSaved={reload} />}
      {!live ? (
        <div className="row aic g8 t13 muted" style={{ padding: 16 }}><Icon name="alert" size={15} color="#b06f00" /><span>Demo (MOCK). Mở app với <code>?api=…</code> để kết nối Page thật.</span></div>
      ) : !connected ? (
        <div style={{ padding: 16 }}>
          <EmptyState icon="megaphone" title="Chưa nối Page nào"
            desc="Dán 1 token (khuyến nghị System User — vĩnh viễn) để liệt kê toàn bộ Page/IG/Ad account đã gán, rồi bật kết nối cần dùng cho Content Analytics."
            action={<Button variant="primary" icon="search" onClick={() => setModal(true)}>Khám phá kết nối</Button>} />
        </div>
      ) : (
        <div style={{ padding: 16 }}>
          <div className="row aic g8 t12 mb12" style={{ padding: '8px 11px', borderRadius: 9, background: 'var(--p-success-bg)' }}>
            <Icon name="checkCircle" size={14} color="#0a7d4f" />
            <span>Token <b>{conn.tokenMask}</b> · {conn.tokenType || 'token'} · {conn.expiresAt === 0 ? <Badge tone="success">Vĩnh viễn</Badge> : <Badge tone="warning">Có hạn</Badge>} · {assets.length} kết nối đã bật</span>
          </div>
          <div className="grid" style={{ gridTemplateColumns: '1fr 1fr 1fr', gap: 12 }}>
            {[['page', 'Page (Organic + Inbox)', '#0866ff', 'F'], ['ig', 'Instagram', '#d62976', 'IG'], ['ad', 'Ad account', '#0a7d4f', '$']].map(([k, label, col, ic]) => (
              <Card key={k} title={label + ' · ' + grp[k].length}>
                {grp[k].length === 0 ? <div className="t12 subtle">— chưa bật</div> : grp[k].map(a => (
                  <div key={a.id} className="row aic g8 mb8" style={{ justifyContent: 'space-between' }}>
                    <div className="row aic g8" style={{ minWidth: 0 }}>
                      <span className="center" style={{ width: 26, height: 26, borderRadius: 6, background: col, color: '#fff', fontSize: 11, fontWeight: 700, flexShrink: 0 }}>{ic}</span>
                      <div style={{ minWidth: 0 }}><div className="t12 fw6 ellipsis">{a.name}</div><div className="t11 subtle">{(a.kinds || []).join(' · ')}</div></div>
                    </div>
                    <Button size="sm" variant="plain" icon="x" onClick={() => removeOne(a.id)} disabled={busy === 'rm:' + a.id} title="Bỏ kết nối này" />
                  </div>
                ))}
              </Card>
            ))}
          </div>
          {msg && (() => {
            const M = { ok: ['checkCircle', '#0a7d4f', 'var(--p-success-bg)'], warn: ['alert', '#9a6700', 'var(--p-warning-bg)'], fail: ['alert', '#c4320a', 'var(--p-critical-bg)'], pending: ['refresh', '#6b7280', 'var(--p-bg-surface-secondary)'] };
            const [ic, col, bg] = M[msg.level] || M.pending;
            return <div className="row aic g8 t12 mt12" style={{ padding: '8px 11px', borderRadius: 9, background: bg }}><Icon name={ic} size={14} color={col} /><span style={{ lineHeight: 1.5 }}>{msg.text}</span></div>;
          })()}
          <div className="row g8 mt12" style={{ flexWrap: 'wrap' }}>
            <Button size="sm" variant="primary" icon="refresh" onClick={syncContent} disabled={busy === 'sync'}>{busy === 'sync' ? 'Đang đồng bộ…' : 'Đồng bộ nội dung'}</Button>
            <Button size="sm" icon="edit" onClick={() => setModal(true)}>Sửa lựa chọn / dán token</Button>
            <Button size="sm" variant="plain" icon="refresh" onClick={checkToken} disabled={busy === 'check'}>Kiểm tra token</Button>
            <Button size="sm" variant="plain" icon="trash" onClick={disconnect}>Gỡ kết nối</Button>
          </div>
        </div>
      )}
    </Card>
  );
}

// DiscoverModal — dán token → quét tài sản → tick chọn → lưu registry.
function DiscoverModal({ conn, onClose, onSaved }) {
  const [token, setToken] = useState('');
  const [ver, setVer] = useState('v21.0');
  const [busy, setBusy] = useState(false);
  const [err, setErr] = useState(null);
  const [res, setRes] = useState(null);          // kết quả quét
  const [picked, setPicked] = useState({});      // id → true
  const had = conn && conn.connected;

  const scan = async () => {
    if (busy || (!token.trim() && !had)) return;
    setBusy(true); setErr(null);
    try {
      const r = await window.API.connDiscover({ token: token.trim(), apiVersion: ver.trim() });
      setRes(r);
      const p = {};
      // Đang sửa (đã có kết nối) → pre-check theo lựa chọn hiện tại; lần đầu → bật hết page+IG.
      const enabled = had ? new Set(((conn && conn.assets) || []).map(a => a.id)) : null;
      [...(r.pages || []), ...(r.instagram || [])].forEach(a => { p[a.id] = enabled ? enabled.has(a.id) : true; });
      (r.ads || []).forEach(a => { if (enabled && enabled.has(a.id)) p[a.id] = true; });
      setPicked(p);
    } catch (e) { setErr((e && e.message) || 'Quét thất bại'); }
    finally { setBusy(false); }
  };
  const save = async () => {
    if (busy || !res) return;
    setBusy(true); setErr(null);
    const all = [...(res.pages || []), ...(res.instagram || []), ...(res.ads || [])];
    const sel = all.filter(a => picked[a.id]).map(a => ({ ...a, enabled: true }));
    try { await window.API.connSelect(sel); onSaved(); onClose(); }
    catch (e) { setErr((e && e.message) || 'Lưu thất bại'); setBusy(false); }
  };
  const toggle = (id) => setPicked(p => ({ ...p, [id]: !p[id] }));

  const Group = ({ title, col, ic, items }) => !items || !items.length ? null : (
    <div className="mb12">
      <div className="t11 subtle mb6" style={{ textTransform: 'uppercase', letterSpacing: .4 }}>{title} · {items.length}</div>
      {items.map(a => (
        <label key={a.id} className="row aic g8 mb6" style={{ cursor: 'pointer', padding: '6px 8px', borderRadius: 8, background: picked[a.id] ? 'var(--p-bg-surface-secondary)' : 'transparent' }}>
          <input type="checkbox" checked={!!picked[a.id]} onChange={() => toggle(a.id)} />
          <span className="center" style={{ width: 24, height: 24, borderRadius: 6, background: col, color: '#fff', fontSize: 10, fontWeight: 700, flexShrink: 0 }}>{ic}</span>
          <div style={{ minWidth: 0, flex: 1 }}><div className="t12 fw6 ellipsis">{a.name}</div><div className="t11 subtle">{a.fanCount != null ? Math.round(a.fanCount) + ' fan · ' : ''}{a.followers != null ? Math.round(a.followers) + ' follower · ' : ''}{(a.kinds || []).join(' · ')}</div></div>
        </label>
      ))}
    </div>
  );

  return (
    <Modal title="Khám phá kết nối Facebook" width={620} onClose={onClose}
      footer={<>
        <Button variant="plain" onClick={onClose}>Hủy</Button>
        {!res ? <Button variant="primary" icon="search" onClick={scan} disabled={busy || (!token.trim() && !had)}>{busy ? 'Đang quét…' : 'Quét tài sản'}</Button>
          : <Button variant="primary" icon="check" onClick={save} disabled={busy}>{busy ? 'Đang lưu…' : 'Lưu kết nối đã chọn'}</Button>}
      </>}>
      {!res ? (
        <>
          <Field label="Access Token (khuyến nghị System User — vĩnh viễn)"><Input value={token} onChange={setToken} type="password" placeholder={had ? 'Đã có token — để trống để quét lại, hoặc dán token mới' : 'EAAB… token có pages_read_engagement + read_insights'} autoFocus /></Field>
          <Field label="Graph API version"><Input value={ver} onChange={setVer} placeholder="v21.0" /></Field>
          <div className="t11 subtle mt8" style={{ lineHeight: 1.5 }}>App gọi Facebook liệt kê mọi Page/Instagram/Ad account token nhìn thấy. Token lưu kho riêng (không lộ ra API công khai). Tạo System User token ở Business Settings → System Users → Generate (Never expire).</div>
        </>
      ) : (
        <>
          <div className="row aic g8 t12 mb12" style={{ padding: '8px 11px', borderRadius: 9, background: res.permanent ? 'var(--p-success-bg)' : 'var(--p-warning-bg)' }}>
            <Icon name={res.permanent ? 'checkCircle' : 'alert'} size={14} color={res.permanent ? '#0a7d4f' : '#9a6700'} />
            <span>{res.tokenType || 'token'} · {res.permanent ? 'KHÔNG hết hạn ✅' : 'CÓ hạn — nên đổi System User'} · tìm thấy {res.counts.pages} Page · {res.counts.instagram} IG · {res.counts.ads} Ad account</span>
          </div>
          <Group title="Trang Facebook" col="#0866ff" ic="F" items={res.pages} />
          <Group title="Instagram" col="#d62976" ic="IG" items={res.instagram} />
          <Group title="Tài khoản quảng cáo" col="#0a7d4f" ic="$" items={res.ads} />
        </>
      )}
      {err && <div className="row aic g8 t12 mt8" style={{ padding: '8px 11px', borderRadius: 9, background: 'var(--p-critical-bg)' }}><Icon name="alert" size={14} color="#c4320a" /><span style={{ lineHeight: 1.5 }}>{err}</span></div>}
    </Modal>
  );
}

// Nguồn Instagram (Organic) — CỔNG RIÊNG: 1 token IG riêng (instagram_basic +
// instagram_manage_insights) → khám phá IG business account, đọc insight THẬT:
// account (follower/reach/profile views) + per-post (like/comment/save/share) +
// Reels & Story tách riêng. Tách hẳn cổng Page ở trên (token + registry riêng).
function IGContentSection() {
  const live = !!(window.API && window.API.enabled);
  const [conn, setConn] = useState(null);
  const [modal, setModal] = useState(false);
  const [busy, setBusy] = useState('');
  const [msg, setMsg] = useState(null);   // {level, text}
  const [stats, setStats] = useState(null); // kết quả đồng bộ gần nhất (per igId)
  const reload = () => { if (live && window.API.igConnList) window.API.igConnList().then(setConn).catch(() => { }); };
  useEffect(() => { reload(); }, [live]);

  const connected = conn && conn.connected;
  const accounts = (conn && conn.assets) || [];
  const disconnect = async () => { if (!window.confirm('Gỡ token Instagram và toàn bộ kết nối IG?')) return; await window.API.igDisconnect(); setStats(null); reload(); };
  const removeOne = async (id) => {
    setBusy('rm:' + id); setMsg(null);
    try { await window.API.igSelect(accounts.filter(a => a.id !== id)); reload(); }
    catch (e) { setMsg({ level: 'fail', text: 'Bỏ kết nối lỗi: ' + ((e && e.message) || '') }); }
    finally { setBusy(''); }
  };
  const checkToken = async () => {
    setBusy('check'); setMsg({ level: 'pending', text: 'Đang kiểm tra token IG với Facebook…' });
    try {
      const r = await window.API.igDiscover({}); // quét lại bằng token đã lưu
      setMsg({ level: r.permanent ? 'ok' : 'warn', text: r.permanent ? `Token còn tốt — vĩnh viễn · thấy ${r.counts.instagram} tài khoản IG.` : `Token còn dùng được nhưng CÓ HẠN — nên đổi sang System User. Thấy ${r.counts.instagram} IG.` });
      reload();
    } catch (e) { setMsg({ level: 'fail', text: 'Token lỗi/hết hạn: ' + ((e && e.message) || '') + ' — bấm "Sửa / dán token" để cập nhật.' }); }
    finally { setBusy(''); }
  };
  const syncIG = async () => {
    if (!accounts.length) { setMsg({ level: 'warn', text: 'Chưa bật tài khoản IG nào để đồng bộ.' }); return; }
    setBusy('sync'); setMsg({ level: 'pending', text: 'Đang kéo account + media + reels + story insights từ ' + accounts.length + ' IG…' }); setStats(null);
    const rows = []; let errs = 0;
    for (const ig of accounts) {
      try {
        const r = await window.API.igInsights({ igId: ig.id, limit: 50 });
        rows.push({ id: ig.id, username: r.username || ig.username, acc: r.account || {}, c: r.counts || {} });
      } catch (e) { errs++; }
    }
    setStats(rows);
    setMsg({ level: errs ? 'warn' : 'ok', text: `Đã kéo insight ${rows.length}/${accounts.length} IG` + (errs ? ` · ${errs} lỗi (token thiếu quyền insight?)` : '') + '.' });
    setBusy('');
  };

  const fmtN = (n) => n == null ? '—' : Math.round(n).toLocaleString('vi-VN');

  return (
    <Card pad={false} title="Nguồn Instagram (Organic)" subtitle="Cổng RIÊNG: 1 token IG (instagram_basic + instagram_manage_insights) → khám phá IG business account, đọc insight thật: account (follower/reach/profile views) + bài đăng (like/comment/save/share) + Reels & Story."
      actions={<Button variant="primary" size="sm" icon="search" onClick={() => setModal(true)} disabled={!live} title={live ? '' : 'Bật LIVE để kết nối'}>{connected ? 'Quét lại' : 'Khám phá IG'}</Button>}>
      {modal && <IGDiscoverModal conn={conn} onClose={() => setModal(false)} onSaved={reload} />}
      {!live ? (
        <div className="row aic g8 t13 muted" style={{ padding: 16 }}><Icon name="alert" size={15} color="#b06f00" /><span>Demo (MOCK). Mở app với <code>?api=…</code> để kết nối Instagram thật.</span></div>
      ) : !connected ? (
        <div style={{ padding: 16 }}>
          <EmptyState icon="megaphone" title="Chưa nối Instagram"
            desc="Dán 1 token IG riêng (khuyến nghị System User — vĩnh viễn, đủ quyền instagram_basic + instagram_manage_insights) để liệt kê IG business account đã gán, rồi bật kết nối cần đo insight."
            action={<Button variant="primary" icon="search" onClick={() => setModal(true)}>Khám phá IG</Button>} />
        </div>
      ) : (
        <div style={{ padding: 16 }}>
          <div className="row aic g8 t12 mb12" style={{ padding: '8px 11px', borderRadius: 9, background: 'var(--p-success-bg)' }}>
            <Icon name="checkCircle" size={14} color="#0a7d4f" />
            <span>Token <b>{conn.tokenMask}</b> · {conn.tokenType || 'token'} · {conn.expiresAt === 0 ? <Badge tone="success">Vĩnh viễn</Badge> : <Badge tone="warning">Có hạn</Badge>} · {accounts.length} tài khoản IG đã bật</span>
          </div>
          {accounts.length === 0 ? <div className="t12 subtle">— chưa bật tài khoản IG nào (bấm "Sửa lựa chọn").</div> : (
            <div className="grid" style={{ gridTemplateColumns: '1fr 1fr', gap: 12 }}>
              {accounts.map(a => {
                const s = stats && stats.find(x => x.id === a.id);
                return (
                  <Card key={a.id} title={a.name || ('@' + a.username)}
                    actions={<Button size="sm" variant="plain" icon="x" onClick={() => removeOne(a.id)} disabled={busy === 'rm:' + a.id} title="Bỏ kết nối này" />}>
                    <div className="row aic g8 mb8">
                      <span className="center" style={{ width: 28, height: 28, borderRadius: 8, background: '#d62976', color: '#fff', fontSize: 11, fontWeight: 700 }}>IG</span>
                      <div className="t12 subtle">{(a.kinds || []).join(' · ')} · {fmtN(a.followers)} follower</div>
                    </div>
                    {s && (
                      <div className="t12" style={{ lineHeight: 1.7 }}>
                        <div className="row" style={{ justifyContent: 'space-between' }}><span className="subtle">Reach 30 ngày</span><b>{fmtN(s.acc.reach_30d)}</b></div>
                        <div className="row" style={{ justifyContent: 'space-between' }}><span className="subtle">Lượt xem hồ sơ 30 ngày</span><b>{fmtN(s.acc.profile_views_30d)}</b></div>
                        <div className="row" style={{ justifyContent: 'space-between' }}><span className="subtle">Click website 30 ngày</span><b>{fmtN(s.acc.website_clicks_30d)}</b></div>
                        <div className="row" style={{ justifyContent: 'space-between' }}><span className="subtle">Bài · Reels · Story</span><b>{fmtN(s.c.posts)} · {fmtN(s.c.reels)} · {fmtN(s.c.stories)}</b></div>
                      </div>
                    )}
                  </Card>
                );
              })}
            </div>
          )}
          {msg && (() => {
            const M = { ok: ['checkCircle', '#0a7d4f', 'var(--p-success-bg)'], warn: ['alert', '#9a6700', 'var(--p-warning-bg)'], fail: ['alert', '#c4320a', 'var(--p-critical-bg)'], pending: ['refresh', '#6b7280', 'var(--p-bg-surface-secondary)'] };
            const [ic, col, bg] = M[msg.level] || M.pending;
            return <div className="row aic g8 t12 mt12" style={{ padding: '8px 11px', borderRadius: 9, background: bg }}><Icon name={ic} size={14} color={col} /><span style={{ lineHeight: 1.5 }}>{msg.text}</span></div>;
          })()}
          <div className="row g8 mt12" style={{ flexWrap: 'wrap' }}>
            <Button size="sm" variant="primary" icon="refresh" onClick={syncIG} disabled={busy === 'sync'}>{busy === 'sync' ? 'Đang đồng bộ…' : 'Đồng bộ Instagram'}</Button>
            <Button size="sm" icon="edit" onClick={() => setModal(true)}>Sửa lựa chọn / dán token</Button>
            <Button size="sm" variant="plain" icon="refresh" onClick={checkToken} disabled={busy === 'check'}>Kiểm tra token</Button>
            <Button size="sm" variant="plain" icon="trash" onClick={disconnect}>Gỡ kết nối</Button>
          </div>
        </div>
      )}
    </Card>
  );
}

// IGDiscoverModal — dán token IG → quét IG business account → tick chọn → lưu registry.
function IGDiscoverModal({ conn, onClose, onSaved }) {
  const [token, setToken] = useState('');
  const [ver, setVer] = useState('v21.0');
  const [busy, setBusy] = useState(false);
  const [err, setErr] = useState(null);
  const [res, setRes] = useState(null);
  const [picked, setPicked] = useState({});
  const had = conn && conn.connected;

  const scan = async () => {
    if (busy || (!token.trim() && !had)) return;
    setBusy(true); setErr(null);
    try {
      const r = await window.API.igDiscover({ token: token.trim(), apiVersion: ver.trim() });
      setRes(r);
      const enabled = had ? new Set(((conn && conn.assets) || []).map(a => a.id)) : null;
      const p = {};
      (r.instagram || []).forEach(a => { p[a.id] = enabled ? enabled.has(a.id) : true; });
      setPicked(p);
    } catch (e) { setErr((e && e.message) || 'Quét thất bại'); }
    finally { setBusy(false); }
  };
  const save = async () => {
    if (busy || !res) return;
    setBusy(true); setErr(null);
    const sel = (res.instagram || []).filter(a => picked[a.id]).map(a => ({ ...a, enabled: true }));
    try { await window.API.igSelect(sel); onSaved(); onClose(); }
    catch (e) { setErr((e && e.message) || 'Lưu thất bại'); setBusy(false); }
  };
  const toggle = (id) => setPicked(p => ({ ...p, [id]: !p[id] }));

  return (
    <Modal title="Khám phá kết nối Instagram" width={620} onClose={onClose}
      footer={<>
        <Button variant="plain" onClick={onClose}>Hủy</Button>
        {!res ? <Button variant="primary" icon="search" onClick={scan} disabled={busy || (!token.trim() && !had)}>{busy ? 'Đang quét…' : 'Quét tài khoản IG'}</Button>
          : <Button variant="primary" icon="check" onClick={save} disabled={busy}>{busy ? 'Đang lưu…' : 'Lưu kết nối đã chọn'}</Button>}
      </>}>
      {!res ? (
        <>
          <Field label="Access Token IG (khuyến nghị System User — vĩnh viễn)"><Input value={token} onChange={setToken} type="password" placeholder={had ? 'Đã có token — để trống để quét lại, hoặc dán token mới' : 'EAAB… token có instagram_basic + instagram_manage_insights + pages_show_list'} autoFocus /></Field>
          <Field label="Graph API version"><Input value={ver} onChange={setVer} placeholder="v21.0" /></Field>
          <div className="t11 subtle mt8" style={{ lineHeight: 1.5 }}>IG business account phải liên kết 1 Page Facebook. App đi qua Page để liệt kê IG. Token lưu kho RIÊNG (tách cổng Page, không lộ ra API công khai). Tạo System User token ở Business Settings → System Users → Generate (Never expire), gán quyền instagram_basic + instagram_manage_insights.</div>
        </>
      ) : (
        <>
          <div className="row aic g8 t12 mb12" style={{ padding: '8px 11px', borderRadius: 9, background: res.permanent ? 'var(--p-success-bg)' : 'var(--p-warning-bg)' }}>
            <Icon name={res.permanent ? 'checkCircle' : 'alert'} size={14} color={res.permanent ? '#0a7d4f' : '#9a6700'} />
            <span>{res.tokenType || 'token'} · {res.permanent ? 'KHÔNG hết hạn ✅' : 'CÓ hạn — nên đổi System User'} · tìm thấy {res.counts.instagram} tài khoản IG</span>
          </div>
          {!res.instagram || !res.instagram.length ? (
            <div className="row aic g8 t12" style={{ padding: '8px 11px', borderRadius: 9, background: 'var(--p-warning-bg)' }}><Icon name="alert" size={14} color="#9a6700" /><span>Token không thấy IG business account nào. Kiểm tra: IG đã chuyển sang tài khoản Business/Creator và liên kết Page chưa? Token có quyền instagram_basic chưa?</span></div>
          ) : (
            <div className="mb12">
              <div className="t11 subtle mb6" style={{ textTransform: 'uppercase', letterSpacing: .4 }}>Tài khoản Instagram · {res.instagram.length}</div>
              {res.instagram.map(a => (
                <label key={a.id} className="row aic g8 mb6" style={{ cursor: 'pointer', padding: '6px 8px', borderRadius: 8, background: picked[a.id] ? 'var(--p-bg-surface-secondary)' : 'transparent' }}>
                  <input type="checkbox" checked={!!picked[a.id]} onChange={() => toggle(a.id)} />
                  <span className="center" style={{ width: 24, height: 24, borderRadius: 6, background: '#d62976', color: '#fff', fontSize: 10, fontWeight: 700, flexShrink: 0 }}>IG</span>
                  <div style={{ minWidth: 0, flex: 1 }}><div className="t12 fw6 ellipsis">{a.name}</div><div className="t11 subtle">{a.followers != null ? Math.round(a.followers) + ' follower · ' : ''}qua Page: {a.pageName || '—'}</div></div>
                </label>
              ))}
            </div>
          )}
        </>
      )}
      {err && <div className="row aic g8 t12 mt8" style={{ padding: '8px 11px', borderRadius: 9, background: 'var(--p-critical-bg)' }}><Icon name="alert" size={14} color="#c4320a" /><span style={{ lineHeight: 1.5 }}>{err}</span></div>}
    </Modal>
  );
}

// Nguồn Threads (Organic) — CỔNG RIÊNG: host graph.threads.net, token riêng
// (threads_basic + threads_manage_insights). KHÁC FB/IG: token Threads sống 60
// ngày (làm mới được), KHÔNG có System User vĩnh viễn. Đọc insight THẬT: account
// (followers + views/likes/replies/reposts/quotes 30 ngày) + per-post. Threads
// API KHÔNG có inbox/DM cho dev → cổng này chỉ organic; ads Threads đo qua cổng
// FB Ads (breakdown nền tảng).
function ThreadsContentSection() {
  const live = !!(window.API && window.API.enabled);
  const [conn, setConn] = useState(null);
  const [modal, setModal] = useState(false);
  const [busy, setBusy] = useState('');
  const [msg, setMsg] = useState(null);   // {level, text}
  const [stats, setStats] = useState(null); // kết quả đồng bộ gần nhất
  const [appId, setAppId] = useState('');
  const [appSecret, setAppSecret] = useState('');
  const reload = () => { if (live && window.API.threadsConnList) window.API.threadsConnList().then(c => { setConn(c); if (c && c.appId) setAppId(c.appId); }).catch(() => { }); };
  useEffect(() => { reload(); }, [live]);
  // bắt kết quả OAuth khi Threads redirect về (?threads=connected|error)
  useEffect(() => {
    const q = new URLSearchParams(location.search);
    if (q.get('threads') === 'connected') setMsg({ level: 'ok', text: 'Đã kết nối Threads qua OAuth — token dài hạn đã lưu.' });
    if (q.get('threads') === 'error') setMsg({ level: 'fail', text: 'Uỷ quyền Threads bị huỷ hoặc lỗi — thử lại.' });
  }, []);

  const connected = conn && conn.connected;
  const accounts = (conn && conn.assets) || [];
  const startOAuth = async () => {
    setBusy('oauth'); setMsg(null);
    try {
      // lưu App ID/Secret trước (nếu vừa nhập), rồi lấy authUrl và chuyển hướng
      if (appId.trim() || appSecret.trim()) await window.API.threadsOAuthConfig({ appId: appId.trim(), appSecret: appSecret.trim() });
      const r = await window.API.threadsOAuthStart(location.href);
      if (r && r.authUrl) location.href = r.authUrl;
      else setMsg({ level: 'fail', text: 'Không lấy được link uỷ quyền (thiếu App ID/Secret?).' });
    } catch (e) { setMsg({ level: 'fail', text: (e && e.message) || 'Lỗi mở OAuth' }); setBusy(''); }
  };
  const disconnect = async () => { if (!window.confirm('Gỡ token Threads và toàn bộ kết nối?')) return; await window.API.threadsDisconnect(); setStats(null); reload(); };
  const removeOne = async (id) => {
    setBusy('rm:' + id); setMsg(null);
    try { await window.API.threadsSelect(accounts.filter(a => a.id !== id)); reload(); }
    catch (e) { setMsg({ level: 'fail', text: 'Bỏ kết nối lỗi: ' + ((e && e.message) || '') }); }
    finally { setBusy(''); }
  };
  const refreshToken = async () => {
    setBusy('refresh'); setMsg({ level: 'pending', text: 'Đang làm mới token Threads (gia hạn 60 ngày)…' });
    try {
      const r = await window.API.threadsRefresh();
      const days = r.expiresIn ? Math.round(r.expiresIn / 86400) : 60;
      setMsg({ level: 'ok', text: `Đã làm mới token — còn ~${days} ngày.` });
      reload();
    } catch (e) { setMsg({ level: 'fail', text: 'Làm mới lỗi (token <24h tuổi hoặc đã hết hạn?): ' + ((e && e.message) || '') }); }
    finally { setBusy(''); }
  };
  const syncThreads = async () => {
    if (!accounts.length) { setMsg({ level: 'warn', text: 'Chưa bật tài khoản Threads nào để đồng bộ.' }); return; }
    setBusy('sync'); setMsg({ level: 'pending', text: 'Đang kéo account + bài đăng insights từ ' + accounts.length + ' Threads…' }); setStats(null);
    const rows = []; let errs = 0;
    for (const t of accounts) {
      try {
        const r = await window.API.threadsInsights({ userId: t.id, limit: 50 });
        rows.push({ id: t.id, username: r.username || t.username, acc: r.account || {}, c: r.counts || {} });
      } catch (e) { errs++; }
    }
    setStats(rows);
    setMsg({ level: errs ? 'warn' : 'ok', text: `Đã kéo insight ${rows.length}/${accounts.length} Threads` + (errs ? ` · ${errs} lỗi (token thiếu quyền insight?)` : '') + '.' });
    setBusy('');
  };

  const fmtN = (n) => n == null ? '—' : Math.round(n).toLocaleString('vi-VN');
  const expLabel = conn && conn.expiresAt ? new Date(conn.expiresAt * 1000).toLocaleDateString('vi-VN') : null;

  return (
    <Card pad={false} title="Nguồn Threads (Organic)" subtitle="Cổng RIÊNG (host graph.threads.net): 1 token Threads (threads_basic + threads_manage_insights) → đọc insight thật: account (followers/views/likes/replies/reposts/quotes) + bài đăng. Token sống 60 ngày — làm mới định kỳ. Threads không có API inbox/DM; ads Threads đo qua cổng FB Ads."
      actions={<Button variant="primary" size="sm" icon="search" onClick={() => setModal(true)} disabled={!live} title={live ? '' : 'Bật LIVE để kết nối'}>{connected ? 'Quét lại' : 'Khám phá Threads'}</Button>}>
      {modal && <ThreadsDiscoverModal conn={conn} onClose={() => setModal(false)} onSaved={reload} />}
      {!live ? (
        <div className="row aic g8 t13 muted" style={{ padding: 16 }}><Icon name="alert" size={15} color="#b06f00" /><span>Demo (MOCK). Mở app với <code>?api=…</code> để kết nối Threads thật.</span></div>
      ) : !connected ? (
        <div style={{ padding: 16 }}>
          <div className="t13 mb12" style={{ lineHeight: 1.55 }}>Nối Threads bằng <b>OAuth 1 bấm</b>: dán <b>ID + Khoá bí mật</b> ứng dụng Threads (Meta App → use case "Threads API") → bấm Kết nối → uỷ quyền → token dài hạn tự lưu. Không cần lấy token tay.</div>
          <div className="grid" style={{ gridTemplateColumns: '1fr 1fr', gap: 12 }}>
            <Field label="ID ứng dụng Threads"><Input value={appId} onChange={setAppId} placeholder="vd 3394376057388584" /></Field>
            <Field label="Khoá bí mật ứng dụng Threads"><Input value={appSecret} onChange={setAppSecret} type="password" placeholder={conn && conn.hasSecret ? 'Đã lưu — để trống nếu không đổi' : 'dán App Secret'} /></Field>
          </div>
          <div className="t11 subtle mb12" style={{ lineHeight: 1.5 }}>
            <b>Quan trọng:</b> trong app Threads, ô <b>"Chuyển hướng URL gọi lại"</b> phải điền ĐÚNG: <code style={{ userSelect: 'all' }}>{(conn && conn.redirectUri) || 'https://crm.aauacademy.com/marketing/threads/oauth/callback'}</code> — nếu lệch sẽ lỗi khi uỷ quyền. Trước khi nối, vào app bật quyền <code>threads_basic</code> + <code>threads_manage_insights</code> và thêm tài khoản Threads của bạn làm <b>Tester</b>.
          </div>
          <div className="row g8" style={{ flexWrap: 'wrap' }}>
            <Button variant="primary" icon="plus" onClick={startOAuth} disabled={busy === 'oauth' || (!appId.trim() && !(conn && conn.appId))}>{busy === 'oauth' ? 'Đang mở…' : 'Kết nối Threads'}</Button>
            <Button variant="plain" icon="search" onClick={() => setModal(true)}>hoặc dán token tay</Button>
          </div>
          {msg && (() => {
            const M = { ok: ['checkCircle', '#0a7d4f', 'var(--p-success-bg)'], warn: ['alert', '#9a6700', 'var(--p-warning-bg)'], fail: ['alert', '#c4320a', 'var(--p-critical-bg)'], pending: ['refresh', '#6b7280', 'var(--p-bg-surface-secondary)'] };
            const [ic, col, bg] = M[msg.level] || M.pending;
            return <div className="row aic g8 t12 mt12" style={{ padding: '8px 11px', borderRadius: 9, background: bg }}><Icon name={ic} size={14} color={col} /><span style={{ lineHeight: 1.5 }}>{msg.text}</span></div>;
          })()}
        </div>
      ) : (
        <div style={{ padding: 16 }}>
          <div className="row aic g8 t12 mb12" style={{ padding: '8px 11px', borderRadius: 9, background: 'var(--p-success-bg)' }}>
            <Icon name="checkCircle" size={14} color="#0a7d4f" />
            <span>Token <b>{conn.tokenMask}</b> · {expLabel ? <Badge tone="warning">Hết hạn {expLabel}</Badge> : <Badge tone="warning">60 ngày (làm mới được)</Badge>} · {accounts.length} tài khoản đã bật</span>
          </div>
          {accounts.length === 0 ? <div className="t12 subtle">— chưa bật tài khoản Threads nào (bấm "Sửa lựa chọn").</div> : (
            <div className="grid" style={{ gridTemplateColumns: '1fr 1fr', gap: 12 }}>
              {accounts.map(a => {
                const s = stats && stats.find(x => x.id === a.id);
                return (
                  <Card key={a.id} title={a.name || ('@' + a.username)}
                    actions={<Button size="sm" variant="plain" icon="x" onClick={() => removeOne(a.id)} disabled={busy === 'rm:' + a.id} title="Bỏ kết nối này" />}>
                    <div className="row aic g8 mb8">
                      <span className="center" style={{ width: 28, height: 28, borderRadius: 8, background: '#000', color: '#fff', fontSize: 11, fontWeight: 700 }}>@</span>
                      <div className="t12 subtle">@{a.username} · {(a.kinds || []).join(' · ')}</div>
                    </div>
                    {s && (
                      <div className="t12" style={{ lineHeight: 1.7 }}>
                        <div className="row" style={{ justifyContent: 'space-between' }}><span className="subtle">Followers</span><b>{fmtN(s.acc.followers)}</b></div>
                        <div className="row" style={{ justifyContent: 'space-between' }}><span className="subtle">Lượt xem 30 ngày</span><b>{fmtN(s.acc.views_30d)}</b></div>
                        <div className="row" style={{ justifyContent: 'space-between' }}><span className="subtle">Like · Reply 30 ngày</span><b>{fmtN(s.acc.likes_30d)} · {fmtN(s.acc.replies_30d)}</b></div>
                        <div className="row" style={{ justifyContent: 'space-between' }}><span className="subtle">Số bài kéo về</span><b>{fmtN(s.c.posts)}</b></div>
                      </div>
                    )}
                  </Card>
                );
              })}
            </div>
          )}
          {msg && (() => {
            const M = { ok: ['checkCircle', '#0a7d4f', 'var(--p-success-bg)'], warn: ['alert', '#9a6700', 'var(--p-warning-bg)'], fail: ['alert', '#c4320a', 'var(--p-critical-bg)'], pending: ['refresh', '#6b7280', 'var(--p-bg-surface-secondary)'] };
            const [ic, col, bg] = M[msg.level] || M.pending;
            return <div className="row aic g8 t12 mt12" style={{ padding: '8px 11px', borderRadius: 9, background: bg }}><Icon name={ic} size={14} color={col} /><span style={{ lineHeight: 1.5 }}>{msg.text}</span></div>;
          })()}
          <div className="row g8 mt12" style={{ flexWrap: 'wrap' }}>
            <Button size="sm" variant="primary" icon="refresh" onClick={syncThreads} disabled={busy === 'sync'}>{busy === 'sync' ? 'Đang đồng bộ…' : 'Đồng bộ Threads'}</Button>
            <Button size="sm" icon="edit" onClick={() => setModal(true)}>Sửa lựa chọn / dán token</Button>
            <Button size="sm" variant="plain" icon="refresh" onClick={refreshToken} disabled={busy === 'refresh'}>Làm mới token</Button>
            <Button size="sm" variant="plain" icon="trash" onClick={disconnect}>Gỡ kết nối</Button>
          </div>
        </div>
      )}
    </Card>
  );
}

// ThreadsDiscoverModal — dán token Threads → /me lấy profile → tick chọn → lưu registry.
function ThreadsDiscoverModal({ conn, onClose, onSaved }) {
  const [token, setToken] = useState('');
  const [busy, setBusy] = useState(false);
  const [err, setErr] = useState(null);
  const [res, setRes] = useState(null);
  const [picked, setPicked] = useState({});
  const had = conn && conn.connected;

  const scan = async () => {
    if (busy || (!token.trim() && !had)) return;
    setBusy(true); setErr(null);
    try {
      const r = await window.API.threadsDiscover({ token: token.trim() });
      setRes(r);
      const enabled = had ? new Set(((conn && conn.assets) || []).map(a => a.id)) : null;
      const p = {};
      (r.threads || []).forEach(a => { p[a.id] = enabled ? enabled.has(a.id) : true; });
      setPicked(p);
    } catch (e) { setErr((e && e.message) || 'Quét thất bại'); }
    finally { setBusy(false); }
  };
  const save = async () => {
    if (busy || !res) return;
    setBusy(true); setErr(null);
    const sel = (res.threads || []).filter(a => picked[a.id]).map(a => ({ ...a, enabled: true }));
    try { await window.API.threadsSelect(sel); onSaved(); onClose(); }
    catch (e) { setErr((e && e.message) || 'Lưu thất bại'); setBusy(false); }
  };
  const toggle = (id) => setPicked(p => ({ ...p, [id]: !p[id] }));

  return (
    <Modal title="Khám phá kết nối Threads" width={620} onClose={onClose}
      footer={<>
        <Button variant="plain" onClick={onClose}>Hủy</Button>
        {!res ? <Button variant="primary" icon="search" onClick={scan} disabled={busy || (!token.trim() && !had)}>{busy ? 'Đang quét…' : 'Quét tài khoản Threads'}</Button>
          : <Button variant="primary" icon="check" onClick={save} disabled={busy}>{busy ? 'Đang lưu…' : 'Lưu kết nối đã chọn'}</Button>}
      </>}>
      {!res ? (
        <>
          <Field label="Access Token Threads (long-lived)"><Input value={token} onChange={setToken} type="password" placeholder={had ? 'Đã có token — để trống để quét lại, hoặc dán token mới' : 'THAA… token có threads_basic + threads_manage_insights'} autoFocus /></Field>
          <div className="t11 subtle mt8" style={{ lineHeight: 1.5 }}>Threads dùng OAuth RIÊNG (host graph.threads.net, KHÔNG phải graph.facebook.com). Lấy long-lived token ở Meta App → use case "Threads API" → đổi token short-lived sang long-lived (60 ngày). Token lưu kho RIÊNG (không lộ ra API công khai). Lưu ý: Threads KHÔNG có System User vĩnh viễn — dùng nút "Làm mới token" để gia hạn.</div>
        </>
      ) : (
        <>
          <div className="row aic g8 t12 mb12" style={{ padding: '8px 11px', borderRadius: 9, background: 'var(--p-warning-bg)' }}>
            <Icon name="alert" size={14} color="#9a6700" />
            <span>Token Threads CÓ hạn 60 ngày (làm mới được) · tìm thấy {res.counts.threads} tài khoản</span>
          </div>
          {!res.threads || !res.threads.length ? (
            <div className="row aic g8 t12" style={{ padding: '8px 11px', borderRadius: 9, background: 'var(--p-warning-bg)' }}><Icon name="alert" size={14} color="#9a6700" /><span>Token không thấy tài khoản Threads. Kiểm tra: token có quyền threads_basic chưa? Tài khoản đã bật Threads chưa?</span></div>
          ) : (
            <div className="mb12">
              <div className="t11 subtle mb6" style={{ textTransform: 'uppercase', letterSpacing: .4 }}>Tài khoản Threads · {res.threads.length}</div>
              {res.threads.map(a => (
                <label key={a.id} className="row aic g8 mb6" style={{ cursor: 'pointer', padding: '6px 8px', borderRadius: 8, background: picked[a.id] ? 'var(--p-bg-surface-secondary)' : 'transparent' }}>
                  <input type="checkbox" checked={!!picked[a.id]} onChange={() => toggle(a.id)} />
                  <span className="center" style={{ width: 24, height: 24, borderRadius: 6, background: '#000', color: '#fff', fontSize: 10, fontWeight: 700, flexShrink: 0 }}>@</span>
                  <div style={{ minWidth: 0, flex: 1 }}><div className="t12 fw6 ellipsis">{a.displayName || a.name || ('@' + a.username)}</div><div className="t11 subtle">@{a.username}{a.bio ? ' · ' + a.bio : ''}</div></div>
                </label>
              ))}
            </div>
          )}
        </>
      )}
      {err && <div className="row aic g8 t12 mt8" style={{ padding: '8px 11px', borderRadius: 9, background: 'var(--p-critical-bg)' }}><Icon name="alert" size={14} color="#c4320a" /><span style={{ lineHeight: 1.5 }}>{err}</span></div>}
    </Modal>
  );
}

// Nguồn Website (GA4) — đo TRAFFIC & HÀNH VI website thật qua Google Analytics 4
// Data API. Dán service account JSON + Property ID (dãy số, KHÔNG phải G-XXXX) →
// backend ký JWT → kéo sessions/engagement/CVR về webFacts. Tách khỏi Ads/Page.
function WebConnectModal({ status, onClose, onSaved }) {
  const editing = !!(status && status.connected);
  const [propertyId, setPropertyId] = useState(status ? (status.propertyId || '') : '');
  const [sa, setSa] = useState('');
  const [busy, setBusy] = useState(false);
  const [err, setErr] = useState(null);
  const save = async () => {
    if (busy || !propertyId.trim()) return;
    setBusy(true); setErr(null);
    try { await window.API.webConnect({ propertyId: propertyId.trim(), serviceAccount: sa.trim() }); onSaved(); onClose(); }
    catch (e) { setErr((e && e.message) || 'Kết nối thất bại'); setBusy(false); }
  };
  return (
    <Modal title={editing ? 'Sửa kết nối GA4' : 'Kết nối Google Analytics 4'} width={600} onClose={onClose}
      footer={<><Button variant="plain" onClick={onClose}>Hủy</Button>
        <Button variant="primary" icon={editing ? 'edit' : 'plus'} onClick={save} disabled={busy || !propertyId.trim()}>{busy ? 'Đang kiểm tra…' : (editing ? 'Kiểm tra & Lưu' : 'Kiểm tra & Kết nối')}</Button></>}>
      <Field label="GA4 Property ID (dãy số — vd 493931538)"><Input value={propertyId} onChange={setPropertyId} placeholder="493931538" autoFocus /></Field>
      <Field label={editing ? 'Service account JSON (để trống = giữ key cũ)' : 'Service account JSON (file key tải từ Google Cloud)'}>
        <textarea className="input" rows={8} value={sa} onChange={e => setSa(e.target.value)} placeholder='{ "type": "service_account", "client_email": "...@...iam.gserviceaccount.com", "private_key": "-----BEGIN PRIVATE KEY-----\n..." }' style={{ resize: 'vertical', fontFamily: 'var(--p-font-mono)', fontSize: 11 }} />
      </Field>
      {err && <div className="row aic g8 t12" style={{ padding: '8px 11px', borderRadius: 9, background: 'var(--p-critical-bg)' }}><Icon name="alert" size={14} color="#c4320a" /><span style={{ lineHeight: 1.5 }}>{err}</span></div>}
      <div className="t11 subtle mt8" style={{ lineHeight: 1.5 }}>
        Property ID ở GA4 → <b>Quản trị → Thông tin về tài sản → Mã tài sản</b> (KHÁC mã <code>G-XXXX</code>).
        Service account: tạo ở Google Cloud (bật <b>Analytics Data API</b>) → tải JSON key → thêm email service account đó làm <b>Người xem</b> trên property GA4. Key cất ở kho riêng, không lộ ra API công khai.
      </div>
    </Modal>
  );
}

function WebTrackingSection() {
  const live = !!(window.API && window.API.enabled);
  const [status, setStatus] = useState(null);
  const [ov, setOv] = useState(null);
  const [modal, setModal] = useState(false);
  const [busy, setBusy] = useState('');
  const [msg, setMsg] = useState(null);

  const reload = () => {
    if (!live || !window.API.webStatus) return;
    window.API.webStatus().then(st => {
      setStatus(st);
      if (st && st.connected) window.API.webOverview('30d').then(setOv).catch(() => { });
    }).catch(() => { });
  };
  useEffect(() => { reload(); }, [live]);

  const connected = status && status.connected;
  const sync = async () => {
    setBusy('sync'); setMsg({ level: 'pending', text: 'Đang kéo dữ liệu từ GA4…' });
    try {
      const r = await window.API.webSync(90);
      setMsg({ level: 'ok', text: 'Đã đồng bộ ' + (r.synced || 0) + ' dòng · ' + (r.landingPages || 0) + ' trang đích' + (r.conversionsTracked ? '' : ' · (property chưa có key event → conversions = 0)') });
      reload();
    } catch (e) { setMsg({ level: 'fail', text: 'Đồng bộ lỗi: ' + ((e && e.message) || 'không rõ') }); }
    finally { setBusy(''); }
  };
  const disconnect = async () => {
    if (!window.confirm('Gỡ kết nối GA4? (service account sẽ bị xoá, dữ liệu đã kéo vẫn giữ)')) return;
    setBusy('del');
    try { await window.API.webDisconnect(); setOv(null); reload(); } finally { setBusy(''); }
  };
  const pct = v => (v == null ? '—' : (v * 100).toFixed(1) + '%');
  const sec = v => (v == null ? '—' : Math.round(v) + 's');

  return (
    <Card pad={false} title="Nguồn Website (GA4)" subtitle="Đo traffic & hành vi website thật qua Google Analytics 4 Data API → webFacts (sessions · engagement · CVR). Tách khỏi Ads/Page. Cần Property ID + service account."
      actions={<Button variant="primary" size="sm" icon={connected ? 'edit' : 'plus'} onClick={() => setModal(true)} disabled={!live} title={live ? '' : 'Bật LIVE để kết nối'}>{connected ? 'Sửa kết nối' : 'Kết nối GA4'}</Button>}>
      {modal && <WebConnectModal status={status} onClose={() => setModal(false)} onSaved={reload} />}
      {!live ? (
        <div className="row aic g8 t13 muted" style={{ padding: 16 }}><Icon name="alert" size={15} color="#b06f00" /><span>Đang ở chế độ demo (MOCK). Mở app với <code>?api=http://localhost:8080</code> để kết nối GA4 thật.</span></div>
      ) : !connected ? (
        <div style={{ padding: 16 }}>
          <EmptyState icon="chart" title="Chưa nối GA4"
            desc="Dán Property ID (dãy số) + service account JSON để kéo lưu lượng & hành vi website thật về dashboard. Measurement ID G-XXXX đã gắn trên web là việc khác — ở đây cần Property ID."
            action={<Button variant="primary" icon="plus" onClick={() => setModal(true)}>Kết nối GA4</Button>} />
        </div>
      ) : (
        <div style={{ padding: 16 }}>
          <div className="row aic g8 t12 mb12" style={{ padding: '8px 11px', borderRadius: 9, background: status.lastError ? 'var(--p-critical-bg)' : 'var(--p-success-bg)' }}>
            <Icon name={status.lastError ? 'alert' : 'checkCircle'} size={14} color={status.lastError ? '#c4320a' : '#0a7d4f'} />
            <span>Property <b>{status.propertyId}</b> · {status.clientEmail || 'service account'} · {status.dataDays ? status.dataDays + ' ngày dữ liệu' : 'chưa đồng bộ'}{status.lastSync ? ' · đồng bộ ' + fmtTime(status.lastSync) : ''}</span>
          </div>
          {status.lastError && <div className="row g8 mb12" style={{ padding: 12, borderRadius: 10, background: 'var(--p-critical-bg)' }}><Icon name="alert" size={18} color="#8e1f0b" /><div className="t12"><b>Lỗi GA4.</b> {status.lastError}</div></div>}

          {ov && ov.total && (
            <div className="grid gc4 mb12">
              <KPI label="Sessions (30 ngày)" value={AAU.fmtNum(ov.total.sessions)} sub={AAU.fmtNum(ov.total.users) + ' người dùng'} icon="eye" />
              <KPI label="Tỷ lệ tương tác" value={pct(ov.total.engagementRate)} sub={'TB ' + sec(ov.total.avgEngagementSec)} icon="bolt" />
              <KPI label="Chuyển đổi" value={AAU.fmtNum(ov.total.conversions)} sub={'CVR ' + pct(ov.total.cvr)} icon="target" />
              <KPI label="Người dùng mới" value={AAU.fmtNum(ov.total.newUsers)} sub="khách lần đầu" icon="user" />
            </div>
          )}
          {ov && ov.byChannel && ov.byChannel.length > 0 && (
            <table className="tbl mb12">
              <thead><tr><th>Nhóm kênh</th><th className="num">Sessions</th><th className="num">Người dùng</th><th className="num">Tương tác</th><th className="num">Chuyển đổi</th></tr></thead>
              <tbody>{ov.byChannel.slice(0, 6).map((r, i) => (
                <tr key={i}><td className="t13 fw6">{r.channel}</td><td className="num t12">{AAU.fmtNum(r.sessions)}</td><td className="num t12">{AAU.fmtNum(r.users)}</td><td className="num t12">{pct(r.engagementRate)}</td><td className="num t12">{AAU.fmtNum(r.conversions)}</td></tr>
              ))}</tbody>
            </table>
          )}
          {msg && (() => {
            const M = { ok: ['checkCircle', '#0a7d4f', 'var(--p-success-bg)'], warn: ['alert', '#9a6700', 'var(--p-warning-bg)'], fail: ['alert', '#c4320a', 'var(--p-critical-bg)'], pending: ['refresh', '#6b7280', 'var(--p-bg-surface-secondary)'] };
            const [ic, col, bg] = M[msg.level] || M.pending;
            return <div className="row aic g8 t12 mb12" style={{ padding: '8px 11px', borderRadius: 9, background: bg }}><Icon name={ic} size={14} color={col} /><span style={{ lineHeight: 1.5 }}>{msg.text}</span></div>;
          })()}
          <div className="row g8" style={{ flexWrap: 'wrap' }}>
            <Button variant="primary" size="sm" icon="refresh" onClick={sync} disabled={busy === 'sync'}>{busy === 'sync' ? 'Đang đồng bộ…' : 'Đồng bộ ngay'}</Button>
            <Button size="sm" icon="edit" onClick={() => setModal(true)}>Sửa kết nối</Button>
            <Button size="sm" variant="plain" icon="trash" onClick={disconnect} disabled={busy === 'del'}>Gỡ kết nối</Button>
          </div>
          <div className="t11 subtle mt12" style={{ lineHeight: 1.5 }}>Service account cất ở kho riêng (không lộ ra API công khai). Đồng bộ kéo 90 ngày gần nhất vào <code>webFacts</code> — engine suy ra engagement/CVR, không lưu số chốt.</div>
        </div>
      )}
    </Card>
  );
}

// Nguồn Lead Web (Google Sheet) — đọc form web (đánh giá F&B, ebook, đăng ký
// khóa/sự kiện) → tạo Lead vào Kanban (nguồn 2.1 "lead từ web/event"). Dùng chung
// service account với GA4; chỉ cần share sheet cho email SA + bật Sheets API.
function WebSheetAddModal({ onClose, onSaved }) {
  const [url, setUrl] = useState('');
  const [name, setName] = useState('');
  const [busy, setBusy] = useState(false);
  const [err, setErr] = useState(null);
  const save = async () => {
    if (busy || !url.trim()) return;
    setBusy(true); setErr(null);
    try { await window.API.webSheetAdd({ url: url.trim(), name: name.trim() }); onSaved(); onClose(); }
    catch (e) { setErr((e && e.message) || 'Thêm thất bại'); setBusy(false); }
  };
  return (
    <Modal title="Thêm Google Sheet nguồn lead" width={560} onClose={onClose}
      footer={<><Button variant="plain" onClick={onClose}>Hủy</Button>
        <Button variant="primary" icon="plus" onClick={save} disabled={busy || !url.trim()}>{busy ? 'Đang kiểm tra…' : 'Kiểm tra & Thêm'}</Button></>}>
      <Field label="Link Google Sheet"><Input value={url} onChange={setUrl} placeholder="https://docs.google.com/spreadsheets/d/…/edit?gid=0" autoFocus /></Field>
      <Field label="Tên hiển thị (tùy chọn)"><Input value={name} onChange={setName} placeholder="VD: Form đăng ký web" /></Field>
      {err && <div className="row aic g8 t12" style={{ padding: '8px 11px', borderRadius: 9, background: 'var(--p-critical-bg)' }}><Icon name="alert" size={14} color="#c4320a" /><span style={{ lineHeight: 1.5 }}>{err}</span></div>}
      <div className="t11 subtle mt8" style={{ lineHeight: 1.5 }}>App tự nhận diện loại sheet (đánh giá / form-ebook) qua tên cột. Cần <b>share sheet</b> cho service account GA4 (quyền Viewer) + bật <b>Google Sheets API</b> trong project.</div>
    </Modal>
  );
}

function WebSheetsSection() {
  const live = !!(window.API && window.API.enabled);
  const [list, setList] = useState([]);
  const [saEmail, setSaEmail] = useState('');
  const [modal, setModal] = useState(false);
  const [busy, setBusy] = useState('');
  const [msg, setMsg] = useState(null);
  const [preview, setPreview] = useState(null); // {id, stats, samples}

  const reload = () => {
    if (!live || !window.API.webSheets) return;
    window.API.webSheets().then(ls => { if (Array.isArray(ls)) setList(ls); }).catch(() => { });
    if (window.API.webStatus) window.API.webStatus().then(st => setSaEmail((st && st.clientEmail) || '')).catch(() => { });
  };
  useEffect(() => { reload(); }, [live]);

  const doPreview = async (id) => {
    setBusy(id + ':prev'); setMsg(null); setPreview(null);
    try { const r = await window.API.webSheetPreview(id); setPreview({ id, ...r }); }
    catch (e) { setMsg({ level: 'fail', text: 'Xem trước lỗi: ' + ((e && e.message) || '') }); }
    finally { setBusy(''); }
  };
  const doImport = async (id) => {
    setBusy(id + ':imp'); setMsg(null);
    try {
      const r = await window.API.webSheetImport(id);
      if (window.API.refreshLeads) await window.API.refreshLeads();
      const moved = (r.stats && r.stats.movedStage) || 0;
      setMsg({ level: 'ok', text: 'Đã tạo ' + (r.created || 0) + ' lead mới' + (r.merged ? ' · gộp ' + r.merged + ' trùng SĐT' : '') + (moved ? ' · dời ' + moved + ' lead web về 2.1' : '') + ' → vào Kanban (2.1 Lead Event/Tài liệu).' });
      setPreview(null); reload();
    } catch (e) { setMsg({ level: 'fail', text: 'Nhập lỗi: ' + ((e && e.message) || '') }); }
    finally { setBusy(''); }
  };
  const remove = async (id) => {
    if (!window.confirm('Gỡ sheet này khỏi nguồn lead? (lead đã tạo vẫn giữ)')) return;
    setBusy(id + ':del');
    try { await window.API.webSheetDelete(id); reload(); } finally { setBusy(''); }
  };
  const courseName = (cid) => { if (!cid) return '—'; const c = (AAU.courses || []).find(x => x.id === cid); return c ? c.name : cid; };

  return (
    <Card pad={false} title="Nguồn Lead Web (Google Sheet)" subtitle="Form website (đánh giá F&B · ebook · đăng ký khóa/sự kiện) → tạo Lead vào Kanban (nguồn 2.1). Gộp trùng SĐT, auto-assign sales. Dùng chung service account với GA4."
      actions={<Button variant="primary" size="sm" icon="plus" onClick={() => setModal(true)} disabled={!live} title={live ? '' : 'Bật LIVE để thêm'}>Thêm Google Sheet</Button>}>
      {modal && <WebSheetAddModal onClose={() => setModal(false)} onSaved={reload} />}
      {!live ? (
        <div className="row aic g8 t13 muted" style={{ padding: 16 }}><Icon name="alert" size={15} color="#b06f00" /><span>Đang ở chế độ demo (MOCK). Mở app với <code>?api=…</code> để nối sheet thật.</span></div>
      ) : (
        <div style={{ padding: 16 }}>
          {saEmail && <div className="t11 subtle mb12" style={{ lineHeight: 1.5 }}>Nhớ share mỗi sheet (quyền Viewer) cho service account: <b style={{ fontFamily: 'var(--p-font-mono)' }}>{saEmail}</b></div>}
          {list.length === 0 ? (
            <EmptyState icon="table" title="Chưa nối sheet nào"
              desc="Thêm Google Sheet thu form từ website để tự tạo lead vào pipeline. App nhận diện cột tên/SĐT/email/khóa và gộp trùng theo SĐT."
              action={<Button variant="primary" icon="plus" onClick={() => setModal(true)}>Thêm Google Sheet</Button>} />
          ) : (
            <table className="tbl">
              <thead><tr><th>Sheet</th><th>Loại</th><th>Đồng bộ gần nhất</th><th className="num">Lần cuối +</th><th></th></tr></thead>
              <tbody>
                {list.map(s => (
                  <tr key={s.id}>
                    <td><div className="t13 fw6">{s.name}</div>{s.lastError && <div className="t11" style={{ color: '#c4320a' }}>{s.lastError}</div>}</td>
                    <td><Badge tone={s.kind === 'assessment' ? 'magic' : 'info'}>{s.kind === 'assessment' ? 'Đánh giá' : 'Form/Ebook'}</Badge></td>
                    <td className="t12 subtle">{s.lastSync ? fmtTime(s.lastSync) : '—'}</td>
                    <td className="num t12">{s.lastCount || 0}</td>
                    <td className="num"><div className="row g6" style={{ justifyContent: 'flex-end' }}>
                      <Button size="sm" variant="plain" icon="eye" onClick={() => doPreview(s.id)} disabled={busy === s.id + ':prev'}>{busy === s.id + ':prev' ? '…' : 'Xem trước'}</Button>
                      <Button size="sm" variant="primary" icon="download" onClick={() => doImport(s.id)} disabled={busy === s.id + ':imp'}>{busy === s.id + ':imp' ? '…' : 'Nhập'}</Button>
                      <Button size="sm" variant="plain" icon="trash" onClick={() => remove(s.id)} disabled={busy === s.id + ':del'} />
                    </div></td>
                  </tr>
                ))}
              </tbody>
            </table>
          )}
          {preview && preview.stats && (
            <Card title="Xem trước" subtitle="Số liệu khi nhập (chưa ghi gì)" className="mt12">
              <div className="row g16 t12 mb12" style={{ flexWrap: 'wrap' }}>
                <span><b>{preview.stats.new}</b> lead mới</span>
                <span className="subtle">{preview.stats.rows} dòng</span>
                <span className="subtle">trùng trong sheet: {preview.stats.dupInSheet}</span>
                <span className="subtle">đã có trong CRM: {preview.stats.inCRM}</span>
                <span className="subtle">thiếu liên hệ: {preview.stats.noContact}</span>
                <span className="subtle">test bỏ: {preview.stats.test}</span>
              </div>
              {preview.samples && preview.samples.length > 0 && (
                <table className="tbl">
                  <thead><tr><th>Tên</th><th>SĐT</th><th>Email</th><th>Khóa suy ra</th></tr></thead>
                  <tbody>{preview.samples.map((s, i) => (
                    <tr key={i}><td className="t12 fw6">{s.name}</td><td className="t12">{s.phone || '—'}</td><td className="t12 subtle">{s.email || '—'}</td><td className="t12">{courseName(s.course)}</td></tr>
                  ))}</tbody>
                </table>
              )}
              <div className="row g8 mt12">
                <Button size="sm" variant="primary" icon="download" onClick={() => doImport(preview.id)} disabled={busy === preview.id + ':imp'}>{busy === preview.id + ':imp' ? 'Đang nhập…' : 'Nhập ' + preview.stats.new + ' lead này'}</Button>
                <Button size="sm" variant="plain" onClick={() => setPreview(null)}>Đóng</Button>
              </div>
            </Card>
          )}
          {msg && (() => {
            const M = { ok: ['checkCircle', '#0a7d4f', 'var(--p-success-bg)'], warn: ['alert', '#9a6700', 'var(--p-warning-bg)'], fail: ['alert', '#c4320a', 'var(--p-critical-bg)'], pending: ['refresh', '#6b7280', 'var(--p-bg-surface-secondary)'] };
            const [ic, col, bg] = M[msg.level] || M.pending;
            return <div className="row aic g8 t12 mt12" style={{ padding: '8px 11px', borderRadius: 9, background: bg }}><Icon name={ic} size={14} color={col} /><span style={{ lineHeight: 1.5 }}>{msg.text}</span></div>;
          })()}
          <div className="t11 subtle mt12" style={{ lineHeight: 1.5 }}>Lead web vào stage <b>2.1 Lead Event/Tài liệu</b> (branch B); STATUS bạn điền trong sheet được giữ ở ghi chú lead (không tự đổi stage). Tự đồng bộ định kỳ ~30 phút; mỗi người gộp 1 lead theo SĐT.</div>
        </div>
      )}
    </Card>
  );
}

/* ───────────────────────── 2 · AI COST (cost_logs) ───────────────────────── */
const COST_LOGS = [
  { t: nowMinus(8), provider: 'claude', model: 'claude-sonnet-4', tin: 18240, tout: 3120, usd: 0.0962 },
  { t: nowMinus(46), provider: 'claude', model: 'claude-sonnet-4', tin: 22150, tout: 4080, usd: 0.1183 },
  { t: nowMinus(92), provider: 'gemini', model: 'gemini-2.5-flash', tin: 31200, tout: 5400, usd: 0.0146 },
  { t: nowMinus(140), provider: 'claude', model: 'claude-haiku-3.5', tin: 12800, tout: 2200, usd: 0.0184 },
  { t: nowMinus(220), provider: 'gemini', model: 'gemini-2.5-pro', tin: 28400, tout: 6100, usd: 0.0712 },
  { t: nowMinus(300), provider: 'claude', model: 'claude-sonnet-4', tin: 19600, tout: 3640, usd: 0.1042 },
];
const COST_BY_DAY = [
  { l: '28/5', v: 1.82, color: '#7c3aed' }, { l: '29/5', v: 2.41, color: '#7c3aed' }, { l: '30/5', v: 1.96, color: '#7c3aed' },
  { l: '31/5', v: 3.12, color: '#7c3aed' }, { l: '01/6', v: 2.74, color: '#7c3aed' }, { l: '02/6', v: 2.18, color: '#0084ff' },
];

function AICost() {
  const [provider, setProvider] = useState('all');
  const rows = COST_LOGS.filter(r => provider === 'all' || r.provider === provider);
  const totalUsd = rows.reduce((s, r) => s + r.usd, 0);
  const totalTok = rows.reduce((s, r) => s + r.tin + r.tout, 0);
  const monthUsd = 14.23;
  return (
    <div className="page wide">
      <PageHead title="AI Cost" subtitle="Chi phí token mỗi lần gọi AI (Claude / Gemini) · backend: cost-logs · nguồn cho dòng “CP Hệ thống & AI” của Finance"
        actions={<Button icon="download">Xuất CSV</Button>} />
      <div className="grid gc4 mb16">
        <KPI label="Chi phí tháng 6" value={'$' + monthUsd.toFixed(2)} sub={AAU.fmtVND(monthUsd * FX_RATE)} icon="dollar" />
        <KPI label="Tổng token" value={(totalTok / 1000).toFixed(0) + 'K'} sub="input + output" icon="sparkles" />
        <KPI label="Số lần gọi" value={rows.length} sub="trong bộ lọc" icon="robot" />
        <KPI label="TB / hội thoại" value="$0,018" sub={'≈ ' + AAU.fmtVND(457)} icon="message" />
      </div>
      <Card title="Chi phí theo ngày" subtitle="USD · 7 ngày gần nhất" className="mb16"><BarChart data={COST_BY_DAY} height={170} /></Card>
      <Card pad={false} title="Nhật ký chi phí AI"
        actions={<div className="row g8 aic">
          <Seg value={provider} onChange={setProvider} options={[{ value: 'all', label: 'Tất cả' }, { value: 'claude', label: 'Claude' }, { value: 'gemini', label: 'Gemini' }]} />
          <Input icon="calendar" value="Tháng 6/2026" onChange={() => {}} style={{ width: 150 }} />
        </div>}>
        <table className="tbl">
          <thead><tr><th>Thời điểm</th><th>Provider</th><th>Model</th><th className="num">Input</th><th className="num">Output</th><th className="num">USD</th><th className="num">VND</th></tr></thead>
          <tbody>
            {rows.map((r, i) => (
              <tr key={i}>
                <td className="t12">{fmtTime(r.t)}</td>
                <td><Badge tone={r.provider === 'claude' ? 'magic' : 'info'}>{r.provider}</Badge></td>
                <td className="t12 muted" style={{ fontFamily: 'var(--p-font-mono)', fontSize: 11 }}>{r.model}</td>
                <td className="num t12">{AAU.fmtNum(r.tin)}</td>
                <td className="num t12">{AAU.fmtNum(r.tout)}</td>
                <td className="num t12 fw6">${r.usd.toFixed(4)}</td>
                <td className="num t12">{AAU.fmtVND(r.usd * FX_RATE)}</td>
              </tr>
            ))}
          </tbody>
          <tfoot><tr style={{ fontWeight: 700, background: 'var(--p-bg-surface-secondary)' }}><td colSpan={5} className="num">Tổng:</td><td className="num">${totalUsd.toFixed(4)}</td><td className="num">{AAU.fmtVND(totalUsd * FX_RATE)}</td></tr></tfoot>
        </table>
      </Card>
    </div>
  );
}

/* ───────────────────────── 3 · NHẬT KÝ HOẠT ĐỘNG (activity_logs) ───────────────────────── */
const ACT_META = {
  'job.run': { tone: 'info', label: 'Chạy job' }, 'job.create': { tone: 'success', label: 'Tạo job' },
  'job.delete': { tone: 'warning', label: 'Xóa job' }, 'ai.error': { tone: 'critical', label: 'Lỗi AI' },
  'notification': { tone: 'info', label: 'Thông báo' }, 'settings': { tone: 'neutral', label: 'Cài đặt' },
  'channel.sync': { tone: 'info', label: 'Đồng bộ kênh' }, 'auth.login': { tone: 'neutral', label: 'Đăng nhập' },
};
const ACT_LOGS = [
  { t: nowMinus(4), action: 'job.run', user: 'system', detail: 'Job "Chấm QC chat ngoài giờ" chạy · 42 hội thoại · 38 PASS / 4 FAIL', err: null },
  { t: nowMinus(18), action: 'channel.sync', user: 'system', detail: 'Đồng bộ Facebook Page · +34 tin nhắn mới', err: null },
  { t: nowMinus(33), action: 'settings', user: 'thu.nguyen@aau.vn', detail: 'Cập nhật model chấm QC: claude-haiku → claude-sonnet-4', err: null },
  { t: nowMinus(70), action: 'ai.error', user: 'system', detail: 'Gọi Gemini thất bại khi chấm batch #1182', err: 'rate_limit_exceeded (429)' },
  { t: nowMinus(95), action: 'channel.sync', user: 'system', detail: 'Đồng bộ Instagram thất bại', err: 'token expired' },
  { t: nowMinus(140), action: 'job.create', user: 'an.le@aau.vn', detail: 'Tạo job "Phân loại chủ đề inbox" (classification)', err: null },
  { t: nowMinus(220), action: 'notification', user: 'system', detail: 'Gửi cảnh báo FAIL qua Telegram → nhóm QC HCM', err: null },
  { t: nowMinus(300), action: 'auth.login', user: 'thu.nguyen@aau.vn', detail: 'Đăng nhập từ IP 14.161.xx.xx', err: null },
  { t: nowMinus(360), action: 'job.delete', user: 'an.le@aau.vn', detail: 'Xóa job test "demo-qc-2"', err: null },
];

function ActivityLog() {
  const [filter, setFilter] = useState('all');
  const opts = [{ value: 'all', label: 'Tất cả hành động' }, ...Object.keys(ACT_META).map(k => ({ value: k, label: ACT_META[k].label }))];
  const rows = ACT_LOGS.filter(r => filter === 'all' || r.action === filter);
  return (
    <div className="page wide">
      <PageHead title="Nhật ký hoạt động" subtitle="Ai · làm gì · khi nào — phục vụ audit & truy vết lỗi · backend: activity-logs"
        actions={<Select value={filter} onChange={setFilter} options={opts} style={{ width: 200 }} />} />
      <Card pad={false}>
        <table className="tbl">
          <thead><tr><th>Thời điểm</th><th>Hành động</th><th>Người</th><th>Chi tiết</th><th>Lỗi</th></tr></thead>
          <tbody>
            {rows.map((r, i) => {
              const m = ACT_META[r.action];
              return (
                <tr key={i}>
                  <td className="t12 subtle" style={{ whiteSpace: 'nowrap' }}>{fmtTime(r.t)}</td>
                  <td><Badge tone={m.tone}>{m.label}</Badge></td>
                  <td className="t12">{r.user === 'system' ? <span className="subtle">system</span> : r.user}</td>
                  <td className="t12 muted" style={{ maxWidth: 440 }}>{r.detail}</td>
                  <td>{r.err && <Badge tone="critical">{r.err}</Badge>}</td>
                </tr>
              );
            })}
          </tbody>
        </table>
      </Card>
    </div>
  );
}

/* ───────────────────────── 4 · MCP CONNECTIONS (mcp/clients) ───────────────────────── */
function MCPConnections() {
  const [clients, setClients] = useState([
    { id: 'm1', name: 'Claude Web', client_id: 'mcp_a1b2c3d4e5', redirect: ['https://claude.ai/api/mcp/auth_callback'], scopes: ['read', 'write'], created: '2026-05-21T09:12:00' },
    { id: 'm2', name: 'Claude Desktop — máy Thu', client_id: 'mcp_f6g7h8i9j0', redirect: ['http://localhost:5173/callback'], scopes: ['read'], created: '2026-05-28T14:40:00' },
  ]);
  const [modal, setModal] = useState(false);
  const revoke = id => setClients(cs => cs.filter(c => c.id !== id));
  return (
    <div className="page wide">
      <PageHead title="MCP Connections" subtitle="Cho phép Claude Web/Desktop truy vấn dữ liệu qua giao thức MCP · backend: mcp/clients + OAuth 2.0"
        actions={<Button variant="primary" icon="plus" onClick={() => setModal(true)}>Tạo kết nối</Button>} />
      <div className="row g8 mb16" style={{ padding: 14, borderRadius: 12, background: 'var(--p-bg-surface-secondary)' }}>
        <Icon name="externalLink" size={18} color="#7c3aed" />
        <div className="t12 muted">Mỗi kết nối cấp một <b>client_id</b> + <b>secret</b> để nối Claude. Cấu hình Redirect URI khớp với <code style={{ fontSize: 11 }}>claude.ai/api/mcp/auth_callback</code> và scope read/write.</div>
      </div>
      <Card pad={false}>
        <table className="tbl">
          <thead><tr><th>Tên</th><th>Client ID</th><th>Redirect URIs</th><th>Scopes</th><th>Tạo lúc</th><th></th></tr></thead>
          <tbody>
            {clients.map(c => (
              <tr key={c.id}>
                <td className="t13 fw6">{c.name}</td>
                <td style={{ fontFamily: 'var(--p-font-mono)', fontSize: 11 }} className="muted">{c.client_id}</td>
                <td>{c.redirect.map(u => <span key={u} className="tag" style={{ fontSize: 10.5 }}>{u}</span>)}</td>
                <td><div className="row g6">{c.scopes.map(s => <Badge key={s} tone={s === 'write' ? 'magic' : 'info'}>{s}</Badge>)}</div></td>
                <td className="t12 subtle">{AAU.fmtDate(c.created.slice(0, 10))}</td>
                <td className="num"><Button size="sm" variant="plain" icon="trash" onClick={() => revoke(c.id)}>Thu hồi</Button></td>
              </tr>
            ))}
            {clients.length === 0 && <tr><td colSpan={6}><div className="empty" style={{ padding: 28 }}><div className="et">Chưa có kết nối MCP</div><div className="ed">Tạo kết nối để Claude truy vấn dữ liệu CQA.</div></div></td></tr>}
          </tbody>
        </table>
      </Card>
      {modal && <MCPCreateModal onClose={() => setModal(false)} onCreate={c => setClients(cs => [c, ...cs])} />}
    </div>
  );
}

function MCPCreateModal({ onClose, onCreate }) {
  const [name, setName] = useState('');
  const [redirect, setRedirect] = useState('https://claude.ai/api/mcp/auth_callback');
  const [scopes, setScopes] = useState(['read', 'write']);
  const [gen, setGen] = useState(null);
  const toggleScope = s => setScopes(ss => ss.includes(s) ? ss.filter(x => x !== s) : [...ss, s]);
  const generate = () => {
    const cid = 'mcp_' + Math.random().toString(36).slice(2, 12);
    const secret = 'sk_mcp_' + Math.random().toString(36).slice(2, 14) + Math.random().toString(36).slice(2, 10);
    setGen({ client_id: cid, secret });
    onCreate({ id: 'm' + Date.now(), name, client_id: cid, redirect: [redirect], scopes, created: new Date().toISOString() });
  };
  return (
    <Modal title="Tạo kết nối MCP" onClose={onClose} width={560}
      footer={gen ? <Button variant="primary" onClick={onClose}>Đóng</Button> : <><Button onClick={onClose}>Hủy</Button><Button variant="primary" onClick={generate} disabled={!name.trim()}>Tạo kết nối</Button></>}>
      {!gen ? (
        <div className="col g16">
          <Field label="Tên kết nối"><Input value={name} onChange={setName} placeholder="VD: Claude Web" autoFocus /></Field>
          <Field label="Redirect URI"><Input value={redirect} onChange={setRedirect} placeholder="https://claude.ai/api/mcp/auth_callback" /></Field>
          <Field label="Phân quyền (scopes)">
            <div className="col g8">
              {[['read', 'Đọc dữ liệu (hội thoại, điểm QC, vi phạm)'], ['write', 'Ghi / kích hoạt (chạy job, đồng bộ)']].map(([s, d]) => (
                <div key={s} className="row aic g10" style={{ padding: 10, borderRadius: 10, border: '1px solid var(--p-border)' }} onClick={() => toggleScope(s)}>
                  <Checkbox on={scopes.includes(s)} onChange={() => toggleScope(s)} />
                  <div><div className="t13 fw6">{s}</div><div className="t11 subtle">{d}</div></div>
                </div>
              ))}
            </div>
          </Field>
        </div>
      ) : (
        <div className="col g16">
          <div className="row g8" style={{ padding: 12, borderRadius: 10, background: 'var(--p-warning-bg)', color: 'var(--p-warning-text)' }}><Icon name="alert" size={18} /><div className="t12"><b>Secret chỉ hiện 1 lần.</b> Hãy copy ngay — sau khi đóng sẽ không xem lại được.</div></div>
          <Field label="Client ID"><div style={{ fontFamily: 'var(--p-font-mono)', fontSize: 13, padding: '10px 12px', borderRadius: 8, background: 'var(--p-bg-surface-secondary)' }}>{gen.client_id}</div></Field>
          <Field label="Client Secret"><div style={{ fontFamily: 'var(--p-font-mono)', fontSize: 13, padding: '10px 12px', borderRadius: 8, background: 'var(--p-bg-surface-secondary)', color: '#c4320a' }}>{gen.secret}</div></Field>
        </div>
      )}
    </Modal>
  );
}

/* ───────────────────────── 5 · NHẬT KÝ THÔNG BÁO (notification_logs) ───────────────────────── */
const NOTIF_LOGS = [
  { id: 'n1', t: nowMinus(6), channel: 'telegram', to: 'Nhóm QC HCM', status: 'sent', subject: null, body: '⚠️ 4 hội thoại FAIL QC hôm nay. Top vi phạm: "Báo giá khi chưa qualify". Xem chi tiết tại CQA › QC Review.' },
  { id: 'n2', t: nowMinus(70), channel: 'email', to: 'thu.nguyen@aau.vn', status: 'sent', subject: 'Báo cáo QC cuối ngày 02/06', body: 'Tổng 42 hội thoại đã chấm · Pass rate 90% · Điểm TB 7.8. Chi tiết trong file đính kèm.' },
  { id: 'n3', t: nowMinus(190), channel: 'telegram', to: 'Nhóm QC HCM', status: 'error', subject: null, body: 'Cảnh báo job treo', },
  { id: 'n4', t: nowMinus(320), channel: 'email', to: 'an.le@aau.vn', status: 'sent', subject: 'Job hoàn tất', body: 'Job "Phân loại chủ đề inbox" đã chạy xong: 128 hội thoại được gắn thẻ.' },
];

function NotificationLog() {
  const [exp, setExp] = useState(null);
  return (
    <div className="page wide">
      <PageHead title="Nhật ký thông báo" subtitle="Cảnh báo Telegram / Email khi job phát hiện vi phạm · backend: notification-logs" />
      <Card pad={false}>
        <table className="tbl">
          <thead><tr><th>Thời điểm</th><th>Kênh</th><th>Người nhận</th><th className="num">Trạng thái</th><th></th></tr></thead>
          <tbody>
            {NOTIF_LOGS.map(n => (
              <React.Fragment key={n.id}>
                <tr>
                  <td className="t12 subtle" style={{ whiteSpace: 'nowrap' }}>{fmtTime(n.t)}</td>
                  <td><Badge tone={n.channel === 'telegram' ? 'info' : 'warning'}><Icon name={n.channel === 'telegram' ? 'send' : 'mail'} size={12} />{n.channel}</Badge></td>
                  <td className="t12">{n.to}</td>
                  <td className="num">{n.status === 'sent' ? <Badge tone="success" dot>Đã gửi</Badge> : <Badge tone="critical">Lỗi</Badge>}</td>
                  <td className="num"><Button size="sm" variant="plain" icon="eye" onClick={() => setExp(exp === n.id ? null : n.id)}>{exp === n.id ? 'Ẩn' : 'Xem'}</Button></td>
                </tr>
                {exp === n.id && <tr><td colSpan={5} style={{ background: 'var(--p-bg-surface-secondary)' }}>
                  <div style={{ padding: '4px 2px' }}>
                    {n.subject && <div className="t11 subtle mb4">Tiêu đề: <b>{n.subject}</b></div>}
                    <div className="t12" style={{ whiteSpace: 'pre-wrap', padding: 12, borderRadius: 8, background: 'var(--p-bg-surface)', border: '1px solid var(--p-border)' }}>{n.body}</div>
                    {n.status === 'error' && <div className="t12 mt8" style={{ color: '#c4320a' }}>Lỗi: bot không có quyền gửi vào group (chat not found)</div>}
                  </div>
                </td></tr>}
              </React.Fragment>
            ))}
          </tbody>
        </table>
      </Card>
    </div>
  );
}

/* ───────────────────────── 6 · AGENTS CENTER (agents API) ───────────────────────── */
const AGENTS = [
  { name: 'cqa.sync', version: '1.0.0', health: 'healthy', desc: 'Đồng bộ tin nhắn từ kênh ngoài (Zalo OA, Facebook) vào CQA', caps: ['sync_all', 'sync_channel', 'query:conversations', 'query:messages'], lastRun: nowMinus(18), runs: 142 },
  { name: 'cqa.qc', version: '1.0.0', health: 'healthy', desc: 'Chấm chất lượng CSKH theo bộ quy tắc bằng AI', caps: ['analyze_quality', 'query:violations', 'query:scores'], lastRun: nowMinus(4), runs: 388 },
  { name: 'cqa.classify', version: '1.0.0', health: 'idle', desc: 'Phân loại & gắn thẻ hội thoại bằng AI theo luật', caps: ['classify_conversations', 'query:tags', 'query:rules'], lastRun: nowMinus(140), runs: 96 },
];
const AGENT_RUNS = [
  { t: nowMinus(4), agent: 'cqa.qc', action: 'analyze_quality', status: 'success', summary: '42 hội thoại · 4 vi phạm' },
  { t: nowMinus(18), agent: 'cqa.sync', action: 'sync_all', status: 'success', summary: '+67 tin nhắn / 3 kênh' },
  { t: nowMinus(140), agent: 'cqa.classify', action: 'classify_conversations', status: 'partial', summary: '128 gắn thẻ · 2 lỗi' },
];
const HEALTH = { healthy: { tone: 'success', label: 'Healthy', dot: true }, idle: { tone: 'neutral', label: 'Nghỉ' }, error: { tone: 'critical', label: 'Lỗi' } };

function AgentsCenter() {
  return (
    <div className="page wide">
      <PageHead title="Agents Center" subtitle="Cổng tích hợp “Company OS” — chạy & truy vấn năng lực AI của CQA · backend: /agents (run · query · health)"
        actions={<Button icon="book">Tài liệu API</Button>} />
      <div className="grid gc3 mb16">
        {AGENTS.map(a => {
          const h = HEALTH[a.health];
          return (
            <Card key={a.name}>
              <div className="row jcsb aic mb8">
                <div className="row aic g8"><span className="center" style={{ width: 32, height: 32, borderRadius: 8, background: 'var(--p-bg-surface-tertiary)', color: '#7c3aed' }}><Icon name="robot" size={17} /></span>
                  <div><div className="t13 fw7" style={{ fontFamily: 'var(--p-font-mono)' }}>{a.name}</div><div className="t11 subtle">v{a.version}</div></div></div>
                <Badge tone={h.tone} dot={h.dot}>{h.label}</Badge>
              </div>
              <div className="t12 muted" style={{ lineHeight: 1.45, minHeight: 34 }}>{a.desc}</div>
              <div className="t11 fw7 subtle mt12 mb6">CAPABILITIES</div>
              <div className="row g6" style={{ flexWrap: 'wrap' }}>{a.caps.map(c => <span key={c} className="tag" style={{ fontSize: 10.5, fontFamily: 'var(--p-font-mono)' }}>{c}</span>)}</div>
              <div className="row jcsb aic mt12 t11 subtle"><span>{a.runs} lần chạy</span><span>gần nhất {fmtTime(a.lastRun)}</span></div>
              <div className="row g6 mt12"><Button size="sm" variant="primary" icon="play">Run</Button><Button size="sm" variant="plain" icon="search">Query</Button><Button size="sm" variant="plain" icon="checkCircle">Health</Button></div>
            </Card>
          );
        })}
      </div>
      <Card pad={false} title="Nhật ký Run / Query">
        <table className="tbl">
          <thead><tr><th>Thời điểm</th><th>Agent</th><th>Action</th><th className="num">Kết quả</th><th>Tóm tắt</th></tr></thead>
          <tbody>{AGENT_RUNS.map((r, i) => (
            <tr key={i}><td className="t12 subtle">{fmtTime(r.t)}</td>
              <td className="t12" style={{ fontFamily: 'var(--p-font-mono)' }}>{r.agent}</td>
              <td className="t12 muted" style={{ fontFamily: 'var(--p-font-mono)', fontSize: 11 }}>{r.action}</td>
              <td className="num"><Badge tone={r.status === 'success' ? 'success' : r.status === 'partial' ? 'warning' : 'critical'}>{r.status}</Badge></td>
              <td className="t12 muted">{r.summary}</td></tr>
          ))}</tbody>
        </table>
      </Card>
    </div>
  );
}

/* ───────────────────────── 7 · DỮ LIỆU MẪU (demo data) ───────────────────────── */
function DemoData() {
  const [loaded, setLoaded] = useState(true);
  const [busy, setBusy] = useState(false);
  const act = fn => { setBusy(true); setTimeout(() => { fn(); setBusy(false); }, 700); };
  const SAMPLE = [['Hội thoại mẫu', 240], ['Tin nhắn', 3180], ['Kết quả QC', 156], ['Thẻ phân loại', 128], ['Job mẫu', 4]];
  return (
    <div className="page wide">
      <PageHead title="Dữ liệu mẫu" subtitle="Nạp / xóa bộ dữ liệu demo theo tenant để trải nghiệm nhanh · backend: demo (status · import · reset) · chỉ owner/admin" />
      <div className="grid mb16" style={{ gridTemplateColumns: '1fr 1fr', alignItems: 'start' }}>
        <Card title="Trạng thái" actions={loaded ? <Badge tone="success" dot>Đã nạp</Badge> : <Badge tone="neutral">Chưa nạp</Badge>}>
          {loaded ? (
            <div className="col g10">
              <div className="t12 muted">Tenant <b>AAU Academy — HCM</b> đang chứa dữ liệu mẫu:</div>
              <table className="tbl">
                <tbody>{SAMPLE.map(([k, v]) => <tr key={k}><td className="t12">{k}</td><td className="num t12 fw6">{AAU.fmtNum(v)}</td></tr>)}</tbody>
              </table>
            </div>
          ) : <div className="t13 muted">Chưa có dữ liệu mẫu. Nạp bộ demo để xem thử dashboard, QC và inbox với dữ liệu thật.</div>}
        </Card>
        <Card title="Thao tác">
          <div className="col g12">
            <div className="row g8" style={{ padding: 12, borderRadius: 10, background: 'var(--p-warning-bg)', color: 'var(--p-warning-text)' }}><Icon name="alert" size={18} /><div className="t12"><b>Lưu ý:</b> “Xóa & làm lại” sẽ xóa toàn bộ dữ liệu mẫu hiện có rồi nạp lại từ đầu. Không ảnh hưởng dữ liệu thật đã đồng bộ.</div></div>
            <div className="row g8">
              <Button variant="primary" icon="download" disabled={busy || loaded} onClick={() => act(() => setLoaded(true))}>Nạp dữ liệu mẫu</Button>
              <Button icon="refresh" disabled={busy || !loaded} onClick={() => act(() => setLoaded(true))}>Xóa & làm lại</Button>
              <Button variant="plain" icon="trash" disabled={busy || !loaded} onClick={() => act(() => setLoaded(false))}>Xóa dữ liệu mẫu</Button>
            </div>
            {busy && <div className="t12 subtle">Đang xử lý…</div>}
          </div>
        </Card>
      </div>
    </div>
  );
}

Object.assign(window, { ChatChannels, AICost, ActivityLog, MCPConnections, MCPCreateModal, NotificationLog, AgentsCenter, DemoData });

/* ───────────────────────── Nhật ký cập nhật (Changelog) ───────────────────────── */
const CL_META = {
  feature: { icon: 'sparkles', color: '#0a7d4f', bg: 'var(--p-success-bg)', label: 'Tính năng' },
  fix: { icon: 'alert', color: '#c4320a', bg: 'var(--p-critical-bg)', label: 'Sửa lỗi' },
  improve: { icon: 'bolt', color: '#1a4fa3', bg: 'var(--p-info-bg)', label: 'Cải thiện' },
  infra: { icon: 'settings', color: '#6d28d9', bg: 'var(--p-bg-surface-tertiary)', label: 'Hạ tầng' },
};

function ChangelogScreen() {
  const log = (typeof window !== 'undefined' && window.CHANGELOG) || [];
  const totalFeat = log.reduce((s, d) => s + d.entries.filter(e => e.type === 'feature').length, 0);
  const totalFix = log.reduce((s, d) => s + d.entries.filter(e => e.type === 'fix').length, 0);
  return (
    <div className="page wide">
      <PageHead title="Nhật ký cập nhật" subtitle="Mọi thay đổi đã gắn lên hệ thống — tính năng mới, sửa lỗi, cải thiện. Mới nhất ở trên." />
      <div className="grid gc4 mb16">
        <KPI label="Số đợt cập nhật" value={log.length} icon="calendar" />
        <KPI label="Tính năng mới" value={totalFeat} icon="sparkles" />
        <KPI label="Lỗi đã sửa" value={totalFix} icon="checkCircle" />
        <KPI label="Bản mới nhất" value={log[0] ? log[0].date : '—'} sub={log[0] ? log[0].tag : ''} icon="bolt" />
      </div>
      {log.length === 0 && <EmptyState icon="fileText" title="Chưa có bản ghi" desc="changelog.js trống." />}
      {log.map((day, di) => (
        <Card key={di} className="mb16" title={day.date + (day.tag ? ' · ' + day.tag : '')} subtitle={day.entries.length + ' thay đổi'}>
          <div className="col g10">
            {day.entries.map((e, ei) => {
              const m = CL_META[e.type] || CL_META.improve;
              return (
                <div key={ei} className="row g10" style={{ alignItems: 'flex-start', padding: 11, borderRadius: 10, background: 'var(--p-bg-surface-secondary)' }}>
                  <span className="center" style={{ width: 26, height: 26, borderRadius: 7, background: m.bg, color: m.color, flexShrink: 0 }}><Icon name={m.icon} size={14} /></span>
                  <div className="f1" style={{ minWidth: 0 }}>
                    <div className="row aic g6" style={{ flexWrap: 'wrap' }}>
                      <span className="badge" style={{ fontSize: 10, background: m.bg, color: m.color }}>{m.label}</span>
                      {e.area && <span className="badge neutral" style={{ fontSize: 10 }}>{e.area}</span>}
                      <span className="t13 fw7">{e.title}</span>
                    </div>
                    <div className="t12 muted mt4" style={{ lineHeight: 1.5 }}>{e.detail}</div>
                    {e.files && e.files.length > 0 && <div className="t11 subtle mt4" style={{ fontFamily: 'monospace' }}>{e.files.join(' · ')}</div>}
                  </div>
                </div>
              );
            })}
          </div>
        </Card>
      ))}
    </div>
  );
}
window.ChangelogScreen = ChangelogScreen;

/* ───────────────────────── Cấu hình AI (model cho chat + QC scan) ───────────────────────── */
const AI_MODELS = {
  claude: [
    { value: 'claude-opus-4-8', label: 'Claude Opus 4.8 (mạnh nhất)' },
    { value: 'claude-sonnet-4-6', label: 'Claude Sonnet 4.6 (cân bằng)' },
    { value: 'claude-haiku-4-5', label: 'Claude Haiku 4.5 (nhanh/rẻ)' },
  ],
  gemini: [
    { value: 'gemini-2.5-pro', label: 'Gemini 2.5 Pro' },
    { value: 'gemini-2.5-flash', label: 'Gemini 2.5 Flash (rẻ)' },
  ],
};

function AISettings() {
  const live = !!(window.API && window.API.enabled);
  const [cfg, setCfg] = useState(null);
  const [saved, setSaved] = useState(false);
  const [busy, setBusy] = useState(false);
  useEffect(() => { if (live && window.API.aiConfig) window.API.aiConfig().then(setCfg).catch(() => { }); }, [live]);
  const set = (k, v) => { setCfg(c => ({ ...c, [k]: v })); setSaved(false); };
  const setProvider = p => setCfg(c => ({ ...c, provider: p, model: (AI_MODELS[p][0] || {}).value }));
  const save = async () => {
    if (!cfg || busy) return; setBusy(true);
    try { const r = await window.API.saveAiConfig({ provider: cfg.provider, model: cfg.model, qcEngine: cfg.qcEngine, defaultMode: cfg.defaultMode }); setCfg(r); setSaved(true); }
    catch (e) { window.alert('Lưu lỗi: ' + (e && e.message || e)); }
    finally { setBusy(false); }
  };
  const head = <PageHead title="Cấu hình AI" subtitle="Chọn model AI dùng cho QC scan & trợ lý chat — chỉnh & lưu tại đây." />;
  if (!live) return <div className="page wide">{head}<EmptyState icon="robot" title="Cần chế độ LIVE" desc="Mở app qua backend để chỉnh cấu hình." /></div>;
  if (!cfg) return <div className="page wide">{head}<div className="muted t13" style={{ padding: 20 }}>Đang tải cấu hình…</div></div>;
  const models = AI_MODELS[cfg.provider] || AI_MODELS.claude;
  return (
    <div className="page">
      {head}
      <Card title="Model AI" subtitle="Áp dụng cho QC scan (khi chế độ = AI) và trợ lý soạn tin">
        <Field label="Nhà cung cấp">
          <Seg value={cfg.provider} onChange={setProvider} options={[{ value: 'claude', label: 'Claude (Anthropic)' }, { value: 'gemini', label: 'Gemini (Google)' }]} />
        </Field>
        <Field label="Model"><Select value={cfg.model} onChange={v => set('model', v)} options={models} /></Field>
        <Field label="Chế độ chấm QC">
          <Select value={cfg.qcEngine} onChange={v => set('qcEngine', v)} options={[
            { value: 'rule', label: 'Rule-based (miễn phí, theo từ khóa)' },
            { value: 'ai', label: 'AI (dùng model trên — cần API key)' },
          ]} />
        </Field>
        {cfg.qcEngine === 'ai' && <div className="row aic g8 t12 mb12" style={{ padding: '9px 12px', borderRadius: 9, background: 'var(--p-warning-bg)', color: '#7a4d00' }}><Icon name="alert" size={14} /><span>Chế độ AI cần cấu hình API key của {cfg.provider}. Chưa có key → QC vẫn chạy rule-based.</span></div>}
        <Field label="Chế độ trợ lý chat mặc định">
          <Select value={cfg.defaultMode} onChange={v => set('defaultMode', v)} options={[{ value: 'off', label: 'Tắt' }, { value: 'copilot', label: 'Copilot (gợi ý, người duyệt)' }, { value: 'autopilot', label: 'Autopilot (tự trả lời)' }]} />
        </Field>
        <div className="row aic g10 mt8">
          <Button variant="primary" icon="check" onClick={save} disabled={busy}>{busy ? 'Đang lưu…' : 'Lưu cấu hình'}</Button>
          {saved && <span className="badge success" style={{ fontSize: 11 }}><Icon name="checkCircle" size={12} /> Đã lưu</span>}
          <span className="t12 subtle mla">Đang dùng: <b>{cfg.model}</b> · QC {cfg.qcEngine === 'ai' ? 'AI' : 'rule-based'}</span>
        </div>
      </Card>
      <div className="t11 subtle mt12" style={{ lineHeight: 1.5 }}>Model này hiển thị trên QC Report và dùng khi bật chấm AI. Theo dõi chi phí token ở <span className="linkish" onClick={() => navigate('/system/ai-cost')}>AI Cost</span>.</div>
    </div>
  );
}
window.AISettings = AISettings;
