// sotuong.jsx — 象 Sổ Tượng cá nhân: bắt mẫu «cụm từ (Tên quẻ/quái)» trong Tâm Ký → tượng riêng của người tu,
// gom theo quẻ (h47) / quái (qKhảm). Nguồn: Tâm Ký (src 'ky') + nạp từ kho ngoài (src 'kb', st.tuongNap).
// Xuất ra localStorage 'tt-so-tuong' để Vũ Trụ 64 Quẻ (assets/vutru-ui.js) đọc chung; tuong.jsx dùng để ưu tiên tượng riêng khi bắt từ khoá.
// Exports to window: ttTachTuong, ttSoTuong, ttNapTuong, stTenKey, stVuTruKey, ST_PROMPT_KB, SoTuong

const ST_QUAI = ['Kiền', 'Đoài', 'Ly', 'Chấn', 'Tốn', 'Khảm', 'Cấn', 'Khôn'];
const ST_DANH = ['thiên', 'trạch', 'hỏa', 'lôi', 'phong', 'thủy', 'sơn', 'địa'];
function stNorm(s) { return String(s || '').toLowerCase().replace(/\s+/g, ' ').trim(); }
// bảng tên → key, dựng một lần từ HEXAGRAMS
const ST_TEN = (() => {
  const m = {};
  ST_QUAI.forEach(q => { m[stNorm(q)] = 'q' + q; m['quái ' + stNorm(q)] = 'q' + q; });
  m['càn'] = 'qKiền'; m['quái càn'] = 'qKiền';
  // biết đọc khác: Minh Sản = Minh Di, Khuể = Khuê, Quan = Quán, Càn = Kiền
  const ALIAS = { 'minh di': 'minh sản', 'khuê': 'khuể', 'quán': 'quan', 'càn': 'kiền' }; // app ghi theo sách DLVN (Minh Sản, Khuể, Quan); vẫn nhận cách ghi Chu Dịch
  HEXAGRAMS.forEach(h => {
    const full = stNorm(h.name), k = 'h' + h.n;
    m[full] = k; m['quẻ ' + full] = k;
    const w = full.split(' ');
    if (w[0] === 'thuần') { m['thuần ' + (w[1] === 'kiền' ? 'càn' : w[1])] = k; }
    else if (w.length >= 3 && ST_DANH.indexOf(w[0]) >= 0 && ST_DANH.indexOf(w[1]) >= 0) { const sh = w.slice(2).join(' '); m[sh] = k; m['quẻ ' + sh] = k; }
  });
  Object.entries(ALIAS).forEach(([a, b]) => { Object.keys(m).forEach(name => { if (name.split(' ').indexOf(b) >= 0 || name.endsWith(' ' + b) || name === b) m[name.replace(new RegExp('(^| )' + b + '$'), '$1' + a)] = m[name]; }); });
  return m;
})();
function stKeyCuaTen(ten) { return ST_TEN[stNorm(ten).replace(/^(quẻ|quái)\s+/, '')] || ST_TEN[stNorm(ten)] || null; }
function stTenKey(key) {
  if (!key) return '';
  if (key[0] === 'q') return 'quái ' + key.slice(1);
  const h = HEXAGRAMS[Number(key.slice(1)) - 1];
  return h ? h.name : key;
}
function stHexKey(key) { return key && key[0] === 'h' ? HEXAGRAMS[Number(key.slice(1)) - 1] : null; }
// key Vũ Trụ 64 Quẻ: quẻ = "thượng+hạ" số Tiên Thiên; quái = tên
function stVuTruKey(key) { const h = stHexKey(key); return h ? String(TT_TRI.indexOf(h.up)) + String(TT_TRI.indexOf(h.lo)) : key.slice(1); }

// tách «cụm từ (Tên)» — cụm = đoạn trước ngoặc, cắt từ dấu ) / . ; : ! ? trước đó, lấy tối đa 6 chữ cuối
function ttTachTuong(text) {
  const out = [], s = String(text || '');
  const re = /\(([^()]{1,40})\)/g;
  let m, prev = 0;
  while ((m = re.exec(s))) {
    const keys = [];
    m[1].split(/[,·\/;]|\s+và\s+|\s+hoặc\s+/).forEach(p => { const k = stKeyCuaTen(p); if (k && keys.indexOf(k) < 0) keys.push(k); });
    const truoc = s.slice(prev, m.index);
    prev = m.index + m[0].length;
    if (!keys.length) continue;
    let cum = truoc.split(/[.;:!?,\n]/).pop().replace(/^[\s\-–—]+|[\s\-–—]+$/g, '').trim();
    cum = cum.replace(/^((và|mới|phải|thì|là|nên|rồi|còn|mà|để|nên là|đã|đang|sẽ|cũng)\s+)+/i, '');
    const w = cum.split(/\s+/).filter(Boolean);
    if (w.length > 6) cum = w.slice(-6).join(' ');
    if (cum.length < 2 || stKeyCuaTen(cum)) continue;
    keys.forEach(key => out.push({ tu: cum, key, ten: stTenKey(key) }));
  }
  return out;
}

// gom sổ: { key: [{ tu, ts, src, jid }] } — nguồn: Tâm Ký ('ky'), tự luận trong Sổ Quẻ ('que'), kho nạp ('kb'); trùng cụm thì giữ bản sớm nhất; bo = bia mộ cho mục kb đã xoá
function ttSoTuong(journal, nap, bo, soQue) {
  const so = {}, seen = {};
  const them = (key, tu, ts, src, jid) => {
    const id = key + '|' + stNorm(tu);
    if (seen[id] || (bo && bo[id])) return;
    seen[id] = 1; (so[key] = so[key] || []).push({ tu, ts, src, jid });
  };
  (journal || []).slice().sort((a, b) => (a.ts || 0) - (b.ts || 0)).forEach(e => ttTachTuong(e.text).forEach(t => them(t.key, t.tu, e.ts, 'ky', e.id)));
  (soQue || []).filter(e => e && e.tuLuan).slice().sort((a, b) => (a.ts || 0) - (b.ts || 0)).forEach(e => ttTachTuong(e.tuLuan).forEach(t => them(t.key, t.tu, e.ts, 'que', e.id)));
  (nap || []).forEach(e => { if (e && e.key && e.tu) them(e.key, e.tu, e.ts || 0, 'kb'); });
  // kho chung gốc (app/so-tuong-kho.js) — xếp sau cùng, làm nền
  (window.TT_SO_TUONG_KHO || []).forEach(e => { const key = stKeyCuaTen(e.que); if (key) (e.tu || []).forEach(t => them(key, t, 0, 'kho')); });
  return so;
}

// nạp từ kho: JSON [{que:"Trạch Thủy Khốn", tu:["…"]}] hoặc mỗi dòng "Tên quẻ: từ, từ | từ"
function ttNapTuong(text) {
  const out = [], ts = Date.now();
  const push = (ten, tu) => { const key = stKeyCuaTen(ten); const t = String(tu || '').trim(); if (key && t.length >= 2 && t.length <= 60) out.push({ key, tu: t, ts }); };
  const s = String(text || '').trim();
  try {
    const j = JSON.parse(s);
    const arr = Array.isArray(j) ? j : (j && Array.isArray(j.tuong) ? j.tuong : null);
    if (arr) { arr.forEach(e => { if (!e) return; const ten = e.que || e.quai || e.ten || e.name; (Array.isArray(e.tu) ? e.tu : String(e.tu || '').split(/[,|;]/)).forEach(t => push(ten, t)); }); return out; }
  } catch (e) {}
  s.split(/\n+/).forEach(line => { const i = line.indexOf(':'); if (i < 1) return; const ten = line.slice(0, i).replace(/^[\s\-•*\d.]+/, ''); line.slice(i + 1).split(/[,|;]/).forEach(t => push(ten, t)); });
  return out;
}

const ST_PROMPT_KB = `Bạn đang soạn "Sổ Tượng" cho app Tu Tiên (Dịch lý Việt Nam). Từ kho tư liệu local (KB) về 64 quẻ và 8 quái, hãy trích TỪ KHÓA TƯỢNG — những cụm từ đời thường (2–6 chữ, tiếng Việt, không giải thích) mà khi thấy trong một sự việc thì người đọc Dịch nhận ra quẻ/quái ấy. Ví dụ Khốn: "chưa tốt", "làm đi làm lại", "nghẽn chưa thông"; Thuần Khảm: "trùng lặp", "cặp đôi cân đối"; quái Khảm: "hãm hiểm", "khắc sâu vào".
Yêu cầu:
- Mỗi quẻ 6–12 cụm, mỗi quái 8–15 cụm. Ưu tiên tượng DLVN (động tĩnh, hình thế, việc đời), tránh nghĩa chung chung ("tốt", "xấu").
- Tên quẻ dùng đúng tên Việt đủ (vd "Trạch Thủy Khốn", "Thuần Khảm", "Hỏa Thiên Đại Hữu"); quái ghi "quái Khảm", "quái Đoài"…
- Chỉ xuất JSON, không lời dẫn, đúng dạng:
[{"que":"Trạch Thủy Khốn","tu":["chưa tốt","làm đi làm lại"]},{"que":"quái Khảm","tu":["hãm hiểm","khắc sâu vào"]}]
Có thể chia nhiều lần (mỗi lần 8–16 quẻ) để tôi dán dần vào app.`;

// ── UI: thẻ Sổ Tượng trong Tâm Ký ──
function SoTuong({ so, onNap, onXoa, onCopy }) {
  const [open, setOpen] = React.useState(false);
  const [napOpen, setNapOpen] = React.useState(false);
  const [txt, setTxt] = React.useState('');
  const [openKey, setOpenKey] = React.useState(null);
  const keys = Object.keys(so || {}).sort((a, b) => {
    const r = k => so[k].filter(t => t.src !== 'kho').length; return r(b) - r(a) || so[b].length - so[a].length || a.localeCompare(b);
  });
  const tong = keys.reduce((n, k) => n + so[k].length, 0);
  const nKy = keys.reduce((n, k) => n + so[k].filter(t => t.src !== 'kb' && t.src !== 'kho').length, 0);
  const nKho = keys.reduce((n, k) => n + so[k].filter(t => t.src === 'kho').length, 0);
  const [locRieng, setLocRieng] = React.useState(false);
  const keysHien = locRieng ? keys.filter(k => so[k].some(t => t.src !== 'kho')) : keys;
  const xem = txt.trim() ? ttNapTuong(txt) : [];
  const nap = () => { if (!xem.length) return; onNap(xem); setTxt(''); setNapOpen(false); };
  return (
    <InkCard style={{ padding: '12px 14px', marginBottom: 12 }}>
      <div onClick={() => setOpen(o => !o)} style={{ display: 'flex', alignItems: 'center', gap: 9, cursor: 'pointer' }}>
        <span style={{ ...han, fontSize: 17, color: INK.goldDim, lineHeight: 1 }}>象</span>
        <span style={{ flex: 1, fontFamily: "'Spectral', serif", fontSize: 14, fontWeight: 600, color: INK.text }}>Sổ Tượng</span>
        <span style={{ fontFamily: "'Be Vietnam Pro', sans-serif", fontSize: 11, color: INK.dim, whiteSpace: 'nowrap' }}>{nKy ? `${nKy} tự đúc kết · ` : ''}{tong} tượng</span>
        <span style={{ color: INK.faint, fontSize: 11, transform: open ? 'rotate(180deg)' : 'none', transition: 'transform .2s' }}>▼</span>
      </div>
      {!open && !nKy && <p style={{ fontFamily: "'Be Vietnam Pro', sans-serif", fontSize: 11, color: INK.faint, margin: '6px 0 0', lineHeight: 1.5, textWrap: 'pretty' }}>Kho gốc {nKho} tượng đời thường cho 64 quẻ · 8 quái. Ghi Tâm Ký hay tự luận quẻ theo lối <i>cụm từ (Tên quẻ)</i> — vd “làm đi làm lại (Khốn)” — tượng riêng của đạo hữu xếp lên trước kho.</p>}
      {open && (
        <div style={{ marginTop: 10 }}>
          <p style={{ fontFamily: "'Be Vietnam Pro', sans-serif", fontSize: 11, color: INK.faint, margin: '0 0 9px', lineHeight: 1.5, textWrap: 'pretty' }}>Vàng = tự đúc kết (Tâm Ký, tự luận quẻ){nKy ? ` · ${nKy}` : ''} · xám = kho gốc {nKho}. Sổ Tượng chỉ là từ khoá khởi đầu — đọc tượng theo ngữ cảnh câu hỏi.</p>
          {nKy > 0 && <div style={{ display: 'flex', gap: 6, marginBottom: 8 }}>{[['Tất cả', false], ['Có tượng riêng', true]].map(([l, v]) => <button key={l} onClick={() => setLocRieng(v)} style={{ padding: '4px 10px', borderRadius: 999, cursor: 'pointer', background: locRieng === v ? 'rgba(var(--gold-rgb),0.14)' : 'transparent', border: `1px solid ${locRieng === v ? INK.goldDim + '88' : INK.hair}`, fontFamily: "'Be Vietnam Pro', sans-serif", fontSize: 10.5, color: locRieng === v ? INK.gold : INK.dim }}>{l}</button>)}</div>}
          <div style={{ display: 'flex', flexDirection: 'column', gap: 6, maxHeight: 420, overflowY: 'auto', paddingRight: 2 }}>
            {keysHien.map(k => {
              const list = so[k], hx = stHexKey(k), o = openKey === k;
              return (
                <div key={k} style={{ borderRadius: 10, background: 'rgba(0,0,0,0.2)', border: `1px solid ${INK.hair}`, padding: '8px 10px' }}>
                  <div onClick={() => setOpenKey(o ? null : k)} style={{ display: 'flex', alignItems: 'center', gap: 8, cursor: 'pointer' }}>
                    {hx ? <HexLines lines={hexLines(hx)} w={18} gap={2.5} h={2.5} color={INK.goldDim} /> : <span style={{ ...han, fontSize: 13, color: INK.goldDim, width: 18, textAlign: 'center' }}>卦</span>}
                    <span style={{ flex: 1, fontFamily: "'Spectral', serif", fontSize: 13.5, fontWeight: 600, color: INK.text }}>{stTenKey(k)}</span>
                    <span style={{ fontFamily: "'Be Vietnam Pro', sans-serif", fontSize: 10.5, color: INK.faint }}>{(() => { const r = list.filter(t => t.src !== 'kho').length; return r ? `${r} · ${list.length}` : list.length; })()}</span>
                  </div>
                  <div style={{ display: 'flex', flexWrap: 'wrap', gap: 5, marginTop: 7 }}>
                    {(o ? list : list.slice(0, 6)).map((t, i) => (
                      <span key={i} style={{ display: 'inline-flex', alignItems: 'center', gap: 4, padding: '3px 8px', borderRadius: 999, background: t.src === 'kho' ? 'transparent' : (t.src === 'kb' ? 'rgba(255,255,255,0.05)' : 'rgba(var(--gold-rgb),0.12)'), border: `1px solid ${t.src === 'kho' ? INK.hair : (t.src === 'kb' ? INK.hair : INK.goldDim + '66')}`, fontFamily: "'Be Vietnam Pro', sans-serif", fontSize: 11, color: t.src === 'kho' ? INK.faint : (t.src === 'kb' ? INK.dim : INK.gold) }}>
                        {t.tu}{o && t.src !== 'ky' && t.src !== 'que' && onXoa && <button onClick={(e) => { e.stopPropagation(); onXoa(k, t.tu); }} title="Bỏ tượng này" style={{ background: 'none', border: 'none', cursor: 'pointer', color: INK.faint, fontSize: 12, lineHeight: 1, padding: 0 }}>×</button>}
                      </span>
                    ))}
                    {!o && list.length > 6 && <span style={{ fontFamily: "'Be Vietnam Pro', sans-serif", fontSize: 10.5, color: INK.faint, alignSelf: 'center' }}>+{list.length - 6}</span>}
                  </div>
                  {o && hx && <a href={`Vu-Tru-64-Que.html?que=${stVuTruKey(k)}`} style={{ display: 'inline-block', marginTop: 7, fontFamily: "'Be Vietnam Pro', sans-serif", fontSize: 10.5, color: INK.goldDim, textDecoration: 'none' }}>宙 Xem trong Vũ Trụ 64 Quẻ ›</a>}
                </div>
              );
            })}
          </div>
          <div style={{ display: 'flex', gap: 8, marginTop: 10 }}>
            <button onClick={() => setNapOpen(v => !v)} style={{ flex: 1, padding: '8px', borderRadius: 9, cursor: 'pointer', background: napOpen ? 'rgba(var(--gold-rgb),0.12)' : 'transparent', border: `1px solid ${INK.goldDim}66`, fontFamily: "'Be Vietnam Pro', sans-serif", fontSize: 11.5, fontWeight: 600, color: INK.gold }}>⤒ Nạp từ kho</button>
            <button onClick={() => onCopy && onCopy(ST_PROMPT_KB)} style={{ flex: 1, padding: '8px', borderRadius: 9, cursor: 'pointer', background: 'transparent', border: `1px solid ${INK.hair}`, fontFamily: "'Be Vietnam Pro', sans-serif", fontSize: 11.5, color: INK.dim }}>Chép prompt soạn kho</button>
          </div>
          {napOpen && (
            <div style={{ marginTop: 9 }}>
              <textarea value={txt} onChange={(e) => setTxt(e.target.value)} rows={5} placeholder={'Dán JSON [{"que":"Trạch Thủy Khốn","tu":["chưa tốt","làm đi làm lại"]}]\nhoặc mỗi dòng: Thuần Khảm: trùng lặp, cặp đôi cân đối'} style={{ width: '100%', boxSizing: 'border-box', resize: 'vertical', borderRadius: 10, background: 'rgba(0,0,0,0.25)', border: `1px solid ${INK.hair}`, color: INK.text, fontFamily: "'Be Vietnam Pro', sans-serif", fontSize: 12, lineHeight: 1.5, padding: '9px 11px', outline: 'none' }}></textarea>
              <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 7 }}>
                <span style={{ flex: 1, fontFamily: "'Be Vietnam Pro', sans-serif", fontSize: 11, color: xem.length ? INK.dim : INK.faint }}>{txt.trim() ? (xem.length ? `Nhận ${xem.length} tượng · ${new Set(xem.map(x => x.key)).size} quẻ` : 'Chưa nhận ra tên quẻ nào') : 'Trùng với tượng đã có thì bỏ qua'}</span>
                <button onClick={nap} disabled={!xem.length} style={{ padding: '8px 14px', borderRadius: 9, cursor: xem.length ? 'pointer' : 'default', background: xem.length ? INK.gold : 'rgba(var(--gold-rgb),0.12)', border: 'none', fontFamily: "'Spectral', serif", fontSize: 13, fontWeight: 700, color: xem.length ? '#1a1206' : INK.goldDim }}>Nạp</button>
              </div>
            </div>
          )}
        </div>
      )}
    </InkCard>
  );
}

Object.assign(window, { ttTachTuong, ttSoTuong, ttNapTuong, stTenKey, stVuTruKey, stHexKey, ST_PROMPT_KB, SoTuong });
