/* Relationships: signer graph, rules, per-placement preview with why, bought-together review. */
const RULE_LABEL = { hand_picked: 'Hand-picked', step_up: 'Step up', completes_set: 'Completes the set', signer_graph: 'Signer graph', bought_together: 'Bought together', searchspring: 'Searchspring similar', block: 'Blocked' };
const PLACEMENT_LABEL = { home_similar: 'Home · similar rail', pdp: 'Product page', cart: 'Cart', app_pdp: 'App product page', email: 'Email' };
const REL_KINDS = ['band', 'team', 'franchise', 'genre', 'era', 'costar', 'rival', 'related'];

function RelationsPage() {
  const toast = useToast(); const { doc, setDoc, user } = useDraft();
  const [tab, setTab] = useState('explore'); const [q, setQ] = useState(''); const [signers, setSigners] = useState([]); const [signer, setSigner] = useState(null); const [product, setProduct] = useState(null);
  const [preview, setPreview] = useState(null); const [busy, setBusy] = useState(false); const [rules, setRules] = useState([]); const [pairs, setPairs] = useState([]); const [ruleForm, setRuleForm] = useState(null); const [jobLog, setJobLog] = useState(null);
  useDebouncedEffect(async () => { const r = await api(`/signers?q=${encodeURIComponent(q)}&limit=40`); setSigners(r.data || []); }, [q], 250);
  useEffect(() => { api('/signers?limit=40').then(r => setSigners(r.data || [])); loadRules(); }, []);
  const loadRules = async () => { const r = await api('/relations?limit=300'); setRules(r.data || []); const p = await api('/relations/bought-together/top?limit=80'); setPairs(p.data || []); };
  const openSigner = async (s) => { const r = await api(`/signers/${s.id}`); setSigner(r.data || s); setProduct(null); runPreview({ signer: s.name }); };
  const runPreview = async (a) => { setBusy(true); const qs = a.signer ? `signer=${encodeURIComponent(a.signer)}` : `product=${a.product}`; const r = await api(`/relations/preview?${qs}`); setBusy(false); setPreview(r.data || null); };
  const addRel = async (to, kind) => { if (!to) return; const r = await api(`/signers/${signer.id}/relations`, { method: 'POST', body: { to, kind } }); if (r.error) return toast(r.error, 'bad'); openSigner(signer); };
  const rmRel = async (id) => { await api(`/signers/relations/${id}`, { method: 'DELETE' }); openSigner(signer); };
  const saveRule = async (r) => { const x = await api('/relations', { method: 'POST', body: r }); if (x.error) return toast(x.error, 'bad'); toast('Rule saved'); setRuleForm(null); loadRules(); if (signer) openSigner(signer); else if (product) runPreview({ product: product.id }); };
  const delRule = async (id) => { await api(`/relations/${id}`, { method: 'DELETE' }); loadRules(); };
  const promote = async (id) => { const r = await api(`/relations/${id}/promote`, { method: 'POST' }); if (r.error) return toast(r.error, 'bad'); toast('Promoted to hand-picked'); loadRules(); };
  const blockPair = async (p) => { await api('/relations', { method: 'POST', body: { subject_type: 'product', subject_id: p.subject_id, related_type: 'product', related_id: p.related_id, rule: 'block' } }); toast('Blocked'); loadRules(); };
  const runJob = async () => { if (!confirm('Read the last 24 months of BigCommerce orders and rebuild bought-together pairs? Runs a few minutes.')) return; setJobLog(['Running…']); const r = await api('/relations/bought-together/run', { method: 'POST', body: {}, timeout: 600000 }); setJobLog(r.error ? [r.error] : r.data.log); loadRules(); };
  const placements = (doc.related && doc.related.placements) || {};
  const setPlacement = (k, patch) => setDoc(d => ({ ...d, related: { ...(d.related || {}), placements: { ...((d.related || {}).placements || {}), [k]: { ...(((d.related || {}).placements || {})[k] || {}), ...patch } } } }));
  const setGuard = (patch) => setDoc(d => ({ ...d, related: { ...(d.related || {}), guardrails: { ...((d.related || {}).guardrails || {}), ...patch } } }));
  const guard = (doc.related && doc.related.guardrails) || {};
  return <div>
    <div className="topbar"><h1>Relationships</h1><span className="muted">One graph, every placement: home similar rail, product page, cart, app and email.</span><div className="tabs row right">{[['explore', 'Explore & edit'], ['rules', 'Rules'], ['data', 'Bought together'], ['placements', 'Placements']].map(([k, l]) => <button key={k} className={'tab ' + (tab === k ? 'active' : '')} onClick={() => setTab(k)}>{l}</button>)}</div></div>
    {tab === 'explore' && <div className="split">
      <div className="stack">
        <div className="card tight stack"><input type="search" value={q} onChange={e => setQ(e.target.value)} placeholder="Find a signer…" /><div className="stack" style={{ maxHeight: 360, overflow: 'auto', gap: 4 }}>{signers.map(s => <div key={s.id} className={'list-item ' + (signer && signer.id === s.id ? 'active' : '')} onClick={() => openSigner(s)}><b>{s.name}</b> <span className="muted small">· {s.in_stock_count} in stock</span></div>)}</div><button className="btn sm" onClick={async () => { const r = await api('/signers/sync', { method: 'POST' }); toast(r.error || `Synced ${r.data.synced} signers from the catalog`); }}>Sync signers from catalog</button></div>
        <div className="card tight"><h3>Or start from a product</h3><ProductSearch onPick={p => { setProduct(p); setSigner(null); runPreview({ product: p.id }); }} /></div>
      </div>
      <div className="stack">
        {signer && <div className="card"><div className="row"><h2>{signer.name}</h2><Pill>{signer.in_stock_count} in stock</Pill>{signer.url && <a className="small" href={STORE + signer.url} target="_blank" rel="noreferrer">brand page ↗</a>}<button className="btn sm right" onClick={() => setRuleForm({ subject_type: 'signer', subject_id: signer.name })}>+ Rule for this signer</button></div>
          <h3 style={{ marginTop: 12 }}>Related signers</h3><div className="row wrap" style={{ margin: '6px 0' }}>{(signer.relations || []).map(r => <span className="chip" key={r.id}>{r.from_id === signer.id ? r.to_name : r.from_name} <span className="muted">· {r.kind}{r.two_way ? '' : ' →'}</span><button onClick={() => rmRel(r.id)}>✕</button></span>)}{!(signer.relations || []).length && <span className="muted small">None yet. Ozzy → Black Sabbath, Tony Iommi, Zakk Wylde…</span>}</div>
          <AddRelation onAdd={addRel} />
          {(signer.rules || []).length > 0 && <div style={{ marginTop: 10 }}><h3>Rules on this signer</h3>{signer.rules.map(r => <div key={r.id} className="row small"><Pill kind={r.rule === 'block' ? 'bad' : 'navy'}>{RULE_LABEL[r.rule]}</Pill><span>{r.related_type} {r.related_id}</span><button className="btn sm ghost" onClick={() => delRule(r.id).then(() => openSigner(signer))}>remove</button></div>)}</div>}
        </div>}
        {product && <div className="card"><div className="row"><div className="tile" style={{ width: 90 }}><div className="img" style={{ backgroundImage: `url("${product.img}")` }} /></div><div><b>{product.name}</b><div className="muted small">{product.brand} · {money(product.price)} · id {product.id}</div></div><button className="btn sm right" onClick={() => setRuleForm({ subject_type: 'product', subject_id: product.id, subject_label: product.name })}>+ Rule for this product</button></div></div>}
        {(signer || product) && <div className="card"><div className="row"><h2>What each placement shows{busy ? '…' : ''}</h2><span className="muted small">and why: the rule that placed each tile</span></div>
          {preview && Object.entries(preview).map(([k, v]) => <div key={k} style={{ marginTop: 12 }}><div className="row"><b>{PLACEMENT_LABEL[k]}</b><span className="muted small">{v.count} of {placements[k]?.count || '?'} · rules: {v.rules.map(r => RULE_LABEL[r]).join(' › ')}</span></div><Tiles items={v.items} /></div>)}
        </div>}
        {!signer && !product && <div className="card muted">Pick a signer or a product to see what the engine would show in every placement, then draw relations, pin, block or add rules.</div>}
      </div>
    </div>}
    {tab === 'rules' && <div className="card"><div className="row" style={{ marginBottom: 10 }}><h2>Rules</h2><span className="muted small">Hand-picked, step-up, completes-the-set and blocks written by the team.</span><button className="btn primary right" onClick={() => setRuleForm({ subject_type: 'product' })}>+ New rule</button></div>
      <Table cols={[{ k: 'rule', l: 'Rule', f: r => <Pill kind={r.rule === 'block' ? 'bad' : r.source === 'data' ? '' : 'navy'}>{RULE_LABEL[r.rule]}</Pill> }, { k: 's', l: 'Next to', f: r => <span>{r.subject_type} <span className="mono">{r.subject_id}</span></span> }, { k: 'r', l: 'Show', f: r => <span>{r.related_type} <span className="mono">{r.related_id}</span></span> }, { k: 'weight', l: 'Weight', num: true }, { k: 'p', l: 'Placements', f: r => (r.placements || ['all']).join(', ') }, { k: 'w', l: 'Window', f: r => r.start_at || r.end_at ? `${r.start_at || '…'} → ${r.end_at || '…'}` : '' }, { k: 'n', l: 'Note', f: r => r.meta?.note || (r.meta?.co ? `co ${r.meta.co} · lift ${r.meta.lift}` : '') }, { k: 'a', l: '', f: r => <button className="btn sm danger" onClick={() => delRule(r.id)}>Delete</button> }]} rows={rules.filter(r => r.source !== 'data')} /></div>}
    {tab === 'data' && <div className="card"><div className="row" style={{ marginBottom: 10 }}><h2>Bought together</h2><span className="muted small">Pairs that co-occur in orders (min {guard.min_cooccurrence || 3} times, lift ≥ {guard.min_lift || 1.5}). Promote a good pair into a hand-picked rule, or block an embarrassing one.</span>{user?.can_publish && <button className="btn right" onClick={runJob}>Rebuild from orders now</button>}</div>
      {jobLog && <div className="notice info mono" style={{ marginBottom: 10 }}>{jobLog.map((l, i) => <div key={i}>{l}</div>)}</div>}
      <PairTable pairs={pairs} onPromote={promote} onBlock={blockPair} /></div>}
    {tab === 'placements' && <div className="card"><h2>Placements</h2><span className="muted small">Per placement: how many tiles and which rules, in order. Saved with the draft and published with it.</span>
      <div className="stack" style={{ marginTop: 12 }}>{Object.entries(placements).map(([k, p]) => <div className="card tight" key={k}><div className="row wrap"><b style={{ width: 160 }}>{PLACEMENT_LABEL[k] || k}</b><Field label="Title"><input type="text" value={p.title || ''} onChange={e => setPlacement(k, { title: e.target.value })} style={{ width: 220 }} /></Field><Field label="Count"><input type="number" min="1" max="24" value={p.count || 8} onChange={e => setPlacement(k, { count: Number(e.target.value) })} style={{ width: 70 }} /></Field><Field label="Rules in order (click to toggle)"><div className="row wrap">{['hand_picked', 'step_up', 'completes_set', 'signer_graph', 'bought_together', 'searchspring'].map(r => <button key={r} className={'tab ' + ((p.rules || []).includes(r) ? 'active' : '')} onClick={() => setPlacement(k, { rules: (p.rules || []).includes(r) ? p.rules.filter(x => x !== r) : [...(p.rules || []), r] })}>{RULE_LABEL[r]}</button>)}</div></Field></div></div>)}</div>
      <h3 style={{ marginTop: 14 }}>Guardrails</h3><div className="grid4" style={{ marginTop: 8 }}><Field label="In-stock floor"><input type="number" value={guard.in_stock_floor ?? 1} onChange={e => setGuard({ in_stock_floor: Number(e.target.value) })} /></Field><Field label="Pre-orders next to in-stock"><Toggle on={!!guard.allow_preorder_next_to_instock} onChange={v => setGuard({ allow_preorder_next_to_instock: v })} label={guard.allow_preorder_next_to_instock ? 'Allowed' : 'Never'} /></Field><Field label="Bought-together min co-occurrence"><input type="number" value={guard.min_cooccurrence ?? 3} onChange={e => setGuard({ min_cooccurrence: Number(e.target.value) })} /></Field><Field label="Bought-together min lift"><input type="number" step="0.1" value={guard.min_lift ?? 1.5} onChange={e => setGuard({ min_lift: Number(e.target.value) })} /></Field></div></div>}
    {ruleForm && <RuleForm init={ruleForm} onSave={saveRule} onClose={() => setRuleForm(null)} />}
  </div>;
}
function AddRelation({ onAdd }) {
  const [to, setTo] = useState(''); const [kind, setKind] = useState('band'); const [sug, setSug] = useState([]);
  useDebouncedEffect(async () => { if (to.length < 2) return setSug([]); const r = await api(`/signers?q=${encodeURIComponent(to)}&limit=6`); setSug(r.data || []); }, [to], 250);
  return <div className="row wrap"><input type="text" value={to} onChange={e => setTo(e.target.value)} placeholder="Related signer (any name; new ones are created)" style={{ width: 300 }} list="signer-sug" /><datalist id="signer-sug">{sug.map(s => <option key={s.id} value={s.name} />)}</datalist><select value={kind} onChange={e => setKind(e.target.value)} style={{ width: 120 }}>{REL_KINDS.map(k => <option key={k}>{k}</option>)}</select><button className="btn sm primary" onClick={() => { onAdd(to, kind); setTo(''); }}>Add relation</button><span className="muted tiny">Two-way by default</span></div>;
}
function PairTable({ pairs, onPromote, onBlock }) {
  const [names, setNames] = useState({});
  useEffect(() => { const ids = [...new Set(pairs.flatMap(p => [p.subject_id, p.related_id]))].slice(0, 100); if (!ids.length) return; api(`/products/by-id?ids=${ids.join(',')}`).then(r => { const m = {}; (r.data || []).forEach(p => { m[p.id] = p; }); setNames(m); }); }, [pairs]);
  const nm = (id) => names[id] ? <span>{names[id].name.slice(0, 48)} <span className="muted tiny">{names[id].brand}</span></span> : <span className="mono">{id}</span>;
  return <Table cols={[{ k: 'a', l: 'Bought', f: p => nm(p.subject_id) }, { k: 'b', l: 'With', f: p => nm(p.related_id) }, { k: 'co', l: 'Times', num: true, f: p => p.meta?.co }, { k: 'lift', l: 'Lift', num: true, f: p => p.meta?.lift }, { k: 'w', l: 'Weight', num: true, f: p => p.weight }, { k: 'x', l: '', f: p => <div className="row"><button className="btn sm" onClick={() => onPromote(p.id)}>Promote</button><button className="btn sm danger" onClick={() => onBlock(p)}>Block</button></div> }]} rows={pairs} empty="No pairs yet. Run the job (needs BC_ORDERS_TOKEN on Railway) or wait for the nightly run." />;
}
function RuleForm({ init, onSave, onClose }) {
  const [r, set] = useState({ rule: 'hand_picked', related_type: 'product', weight: 5, placements: [], allow_preorder: false, ...init }); const up = p => set(x => ({ ...x, ...p }));
  const [picked, setPicked] = useState(null);
  return <Modal title="Relationship rule" wide onClose={onClose} footer={<><button className="btn" onClick={onClose}>Cancel</button><button className="btn primary" disabled={!r.subject_id || !r.related_id} onClick={() => onSave(r)}>Save rule</button></>}>
    <div className="stack">
      <div className="grid3"><Field label="Rule"><select value={r.rule} onChange={e => up({ rule: e.target.value })}>{['hand_picked', 'step_up', 'completes_set', 'block'].map(k => <option key={k} value={k}>{RULE_LABEL[k]}</option>)}</select></Field>
        <Field label="Next to (subject)"><div className="row"><select value={r.subject_type} onChange={e => up({ subject_type: e.target.value })} style={{ width: 110 }}><option value="product">product</option><option value="signer">signer</option></select><input type="text" value={r.subject_id || ''} onChange={e => up({ subject_id: e.target.value })} placeholder={r.subject_type === 'product' ? 'product id' : 'signer name'} /></div>{r.subject_label && <span className="hint">{r.subject_label}</span>}</Field>
        <Field label={r.rule === 'block' ? 'Never show' : 'Show'}><div className="row"><select value={r.related_type} onChange={e => up({ related_type: e.target.value })} style={{ width: 110 }}><option value="product">product</option><option value="signer">signer</option></select><input type="text" value={r.related_id || ''} onChange={e => up({ related_id: e.target.value })} placeholder={r.related_type === 'product' ? 'product id' : 'signer name'} /></div>{picked && <span className="hint">{picked.name}</span>}</Field></div>
      {r.related_type === 'product' && <div className="card tight"><h3>Find the product</h3><ProductSearch onPick={p => { up({ related_id: p.id }); setPicked(p); }} /></div>}
      {r.rule !== 'block' && <div className="grid4"><Field label="Weight" hint="Higher wins within the rule"><input type="number" value={r.weight} onChange={e => up({ weight: Number(e.target.value) })} /></Field><Field label="Placements" hint="Empty = every placement"><div className="row wrap">{Object.keys(PLACEMENT_LABEL).map(p => <label key={p} className="chip"><input type="checkbox" checked={(r.placements || []).includes(p)} onChange={e => up({ placements: e.target.checked ? [...(r.placements || []), p] : r.placements.filter(x => x !== p) })} /> {p}</label>)}</div></Field><Field label="Window (optional)"><div className="row"><input type="date" value={(r.start_at || '').slice(0, 10)} onChange={e => up({ start_at: e.target.value })} /><input type="date" value={(r.end_at || '').slice(0, 10)} onChange={e => up({ end_at: e.target.value })} /></div></Field><Field label="Price band vs anchor (%)" hint="e.g. 100–300 for a step-up"><div className="row"><input type="number" placeholder="min" value={r.price_band?.min_pct ?? ''} onChange={e => up({ price_band: { ...(r.price_band || {}), min_pct: e.target.value === '' ? null : Number(e.target.value) } })} /><input type="number" placeholder="max" value={r.price_band?.max_pct ?? ''} onChange={e => up({ price_band: { ...(r.price_band || {}), max_pct: e.target.value === '' ? null : Number(e.target.value) } })} /></div></Field></div>}
      {r.rule !== 'block' && <div className="row"><Toggle on={r.allow_preorder} onChange={v => up({ allow_preorder: v })} label="Allow a pre-order next to in-stock" /><input type="text" className="grow" placeholder="Note (shown as the why line)" value={r.meta?.note || ''} onChange={e => up({ meta: { ...(r.meta || {}), note: e.target.value } })} /></div>}
    </div>
  </Modal>;
}
