/* App root: sign-in, the shared draft with autosave, navigation, Overview, Versions, Users, Preview pane. */
const NAV = [
  { group: 'Page' }, { key: 'overview', label: 'Overview' }, { key: 'sections', label: 'Sections' }, { key: 'copy', label: 'Copy' }, { key: 'hub', label: 'Collection hub' }, { key: 'images', label: 'Images' },
  { group: 'Merchandising' }, { key: 'seasons', label: 'Seasons' }, { key: 'relations', label: 'Relationships' }, { key: 'targeting', label: 'Segments & tests' },
  { group: 'Results' }, { key: 'analytics', label: 'Analytics' },
  { group: 'Safety' }, { key: 'versions', label: 'Versions' }, { key: 'users', label: 'Users' },
];

function Login({ onDone }) {
  const [email, setEmail] = useState(''); const [pw, setPw] = useState(''); const [err, setErr] = useState(''); const [busy, setBusy] = useState(false);
  const go = async () => { setBusy(true); setErr(''); const r = await api('/auth/login', { method: 'POST', body: { email, password: pw } }); setBusy(false); if (r.error) return setErr(r.error); onDone(r.data.user); };
  return <div className="login"><div className="card"><h1>Home editor</h1><div className="muted small" style={{ marginBottom: 14 }}>Premiere Collectibles · storefront home page</div>
    <div className="stack"><Field label="Email"><input type="email" value={email} onChange={e => setEmail(e.target.value)} autoComplete="username" /></Field><Field label="Password"><input type="password" value={pw} onChange={e => setPw(e.target.value)} autoComplete="current-password" onKeyDown={e => e.key === 'Enter' && go()} /></Field>
      {err && <div className="notice bad">{err}</div>}<button className="btn primary" onClick={go} disabled={busy || !email || !pw}>{busy ? 'Signing in…' : 'Sign in'}</button></div></div></div>;
}

function App() { return <ToastHost><Root /></ToastHost>; }
function Root() {
  const [user, setUser] = useState(undefined);
  useEffect(() => { api('/auth/me').then(r => setUser(r.data || null)); const h = () => setUser(null); window.addEventListener('home:unauth', h); return () => window.removeEventListener('home:unauth', h); }, []);
  if (user === undefined) return <div className="boot">Loading…</div>;
  if (!user) return <Login onDone={u => api('/auth/me').then(r => setUser(r.data || u))} />;
  return <Editor user={user} onSignOut={async () => { await api('/auth/logout', { method: 'POST' }); setUser(null); }} />;
}

function Editor({ user, onSignOut }) {
  const toast = useToast();
  const [view, setView] = useState(location.hash.replace('#', '') || 'overview');
  const [doc, setDocState] = useState(null); const [base, setBase] = useState(0); const [dirty, setDirty] = useState(false); const [saving, setSaving] = useState(false); const [savedAt, setSavedAt] = useState(null);
  const [assets, setAssets] = useState([]); const [segments, setSegments] = useState([]); const [status, setStatus] = useState(null); const [showPreview, setShowPreview] = useState(window.innerWidth > 1400);
  const skipSave = useRef(true);
  useEffect(() => { location.hash = view; }, [view]);
  const reloadAssets = useCallback(() => api('/assets').then(r => setAssets(r.data || [])), []);
  const reloadSegments = useCallback(() => api('/segments').then(r => setSegments(r.data || [])), []);
  const refreshLive = useCallback(() => api('/status').then(r => r.data && setStatus(r.data)), []);
  useEffect(() => { api('/config/draft').then(r => { if (r.data) { skipSave.current = true; setDocState(r.data.doc); setBase(r.data.base_version || 0); setSavedAt(r.data.updated_at); } }); reloadAssets(); reloadSegments(); refreshLive(); }, []);
  const setDoc = useCallback((fn) => { setDocState(d => { const n = typeof fn === 'function' ? fn(d) : fn; return n; }); skipSave.current = false; setDirty(true); }, []);
  // autosave the shared draft 1.2 s after the last change
  useEffect(() => { if (!doc || skipSave.current) return; const t = setTimeout(async () => { setSaving(true); const r = await api('/config/draft', { method: 'PUT', body: { doc, base_version: base } }); setSaving(false); if (r.error) toast('Draft not saved: ' + r.error, 'bad'); else { setSavedAt(r.data.updated_at); } }, 1200); return () => clearTimeout(t); }, [doc]);
  const ctx = useMemo(() => ({ doc, setDoc, assets, reloadAssets, segments, reloadSegments, user, status, refreshLive, base }), [doc, assets, segments, user, status, base]);
  if (!doc) return <div className="boot">Loading the draft…</div>;
  const Page = { overview: OverviewPage, sections: SectionsPage, copy: CopyPage, hub: HubPage, images: ImagesPage, seasons: SeasonsPage, relations: RelationsPage, targeting: TargetingPage, analytics: AnalyticsPage, versions: VersionsPage, users: UsersPage }[view] || OverviewPage;
  return <DraftCtx.Provider value={ctx}><div className={'shell ' + (showPreview ? 'with-preview' : '')}>
    <nav className="nav"><div className="brand">Living Home<small>home.premieremarketing.com</small></div>
      {NAV.map((n, i) => n.group ? <div className="group" key={i}>{n.group}</div> : <button key={n.key} className={view === n.key ? 'active' : ''} onClick={() => setView(n.key)}>{n.label}</button>)}
      <div className="user"><div><b>{user.name}</b></div><div>{user.can_publish ? 'Publisher' : 'Editor'} · <a href="#" style={{ color: 'inherit' }} onClick={e => { e.preventDefault(); onSignOut(); }}>sign out</a></div><div style={{ marginTop: 8 }}><button className="btn sm" style={{ width: '100%' }} onClick={() => setShowPreview(!showPreview)}>{showPreview ? 'Hide preview' : 'Show preview'}</button></div></div></nav>
    <main className="main"><PublishBar dirty={dirty} saving={saving} savedAt={savedAt} onPublished={() => { setDirty(false); refreshLive(); }} /><Page /></main>
    {showPreview && <PreviewPane />}
  </div></DraftCtx.Provider>;
}

function PublishBar({ dirty, saving, savedAt, onPublished }) {
  const { doc, user, status, base, setDoc } = useDraft(); const toast = useToast();
  const [modal, setModal] = useState(null); const [warnings, setWarnings] = useState(null); const [note, setNote] = useState(''); const [busy, setBusy] = useState(false);
  const validate = async () => { setBusy(true); setModal('publish'); setWarnings(null); const r = await api('/config/validate', { method: 'POST', body: { doc, check_links: true }, timeout: 60000 }); setBusy(false); setWarnings(r.data || [{ level: 'error', msg: r.error }]); };
  const publish = async () => { setBusy(true); const r = await api('/config/publish', { method: 'POST', body: { doc, note }, timeout: 60000 }); setBusy(false); if (r.error) return toast(r.error, 'bad'); toast(`Published v${r.data.version}. Live on the next page load, within 5 minutes everywhere.`); setModal(null); setNote(''); onPublished(); };
  const discard = async () => { const r = await api('/config/live'); if (r.data) { setDoc(() => { const d = { ...r.data }; ['seasons_active', 'segments', 'experiments', 'served_at'].forEach(k => delete d[k]); return d; }); setModal(null); toast('Draft reset to the live version'); } };
  const errors = (warnings || []).filter(w => w.level === 'error');
  return <div className="card tight row wrap" style={{ marginBottom: 16, position: 'sticky', top: 0, zIndex: 5 }}>
    <span><b>Live:</b> v{status?.live_version ?? '…'}{status?.kill_switch && <Pill kind="bad" style={{ marginLeft: 6 }}>KILL SWITCH ON</Pill>}</span>
    <span className="muted small">Draft {dirty ? 'has unpublished changes' : 'matches what you last saved'} · {saving ? 'saving…' : savedAt ? 'saved ' + ago(savedAt) : ''} · seasons live: {(status?.seasons_active || []).join(', ') || 'none'}</span>
    <div className="right row">
      <button className="btn sm" onClick={() => setModal('discard')}>Reset draft to live</button>
      <button className="btn sm" onClick={() => api('/config/preview-token', { method: 'POST', body: { doc } }).then(r => { if (r.data) { navigator.clipboard?.writeText(r.data.url); setModal({ link: r.data.url }); } })}>Share preview link</button>
      {user.can_publish && <button className="btn sm gold" onClick={validate}>Check & publish</button>}
      {user.can_publish && <button className={'btn sm ' + (status?.kill_switch ? 'primary' : 'danger')} onClick={() => setModal('kill')}>{status?.kill_switch ? 'Turn scripted rails back on' : 'Kill switch'}</button>}
    </div>
    {modal === 'publish' && <Modal title="Check & publish" onClose={() => setModal(null)} footer={<><button className="btn" onClick={() => setModal(null)}>Cancel</button><button className="btn primary" disabled={busy || !warnings || errors.length > 0} onClick={publish}>Publish as v{(status?.live_version || 0) + 1}</button></>}>
      {!warnings ? <div className="muted">Checking rails against the catalog, images for alt text, links for 404s, seasons for overlaps…</div> : warnings.length === 0 ? <div className="notice good">No warnings. Ready to publish.</div> : <div className="stack">{warnings.map((w, i) => <div key={i} className={'notice ' + (w.level === 'error' ? 'bad' : 'warn')}><b>{w.level}</b> · {w.msg} <span className="muted tiny">({w.where})</span></div>)}{errors.length === 0 && <div className="muted small">Warnings do not block publishing.</div>}</div>}
      <Field label="Note for the versions list"><Text value={note} onChange={setNote} placeholder="What changed and why" /></Field>
    </Modal>}
    {modal === 'discard' && <Confirm text="Replace the shared draft with the live version? Unpublished edits by anyone are lost." yes="Reset draft" onYes={discard} onNo={() => setModal(null)} />}
    {modal === 'kill' && <Confirm text={status?.kill_switch ? 'Turn the scripted rails back on? Publishes a new version.' : 'Kill switch: the storefront renders the server spine only (hero, bestsellers, just landed, hub, trust, SEO) and skips every scripted rail until you turn it off. Publishes a new version.'} yes={status?.kill_switch ? 'Turn back on' : 'Turn kill switch ON'} onYes={async () => { const r = await api('/config/kill-switch', { method: 'POST', body: { on: !status?.kill_switch } }); toast(r.error || `v${r.data.version} published`); setModal(null); onPublished(); }} onNo={() => setModal(null)} />}
    {modal && modal.link && <Modal title="Preview link" onClose={() => setModal(null)}><p>Copied to the clipboard. Anyone with the link sees the current draft on the real storefront for 72 hours, no sign-in needed.</p><input type="text" readOnly value={modal.link} onFocus={e => e.target.select()} /></Modal>}
  </div>;
}

function OverviewPage() {
  const { status, doc, user } = useDraft(); const [audit, setAudit] = useState([]);
  useEffect(() => { api('/audit?limit=12').then(r => setAudit(r.data || [])); }, [status]);
  const on = (doc.sections || []).filter(s => s.enabled);
  return <div><div className="topbar"><h1>Overview</h1></div>
    <div className="kpi"><div className="card"><div className="v">v{status?.live_version ?? '—'}</div><div className="l">Live version</div></div><div className="card"><div className="v">{on.length}</div><div className="l">Sections on in draft</div></div><div className="card"><div className="v">{(status?.seasons_active || []).length}</div><div className="l">Seasons live now</div></div><div className="card"><div className="v">{fmt(status?.signers || 0)}</div><div className="l">Signers in the graph</div></div><div className="card"><div className="v">{fmt(status?.events?.events?.n || 0)}</div><div className="l">Raw events ({status?.events?.driver || '…'})</div></div></div>
    <div className="grid2" style={{ marginTop: 12 }}>
      <div className="card"><h2>How this works</h2><ul className="small" style={{ paddingLeft: 18, lineHeight: 1.7 }}><li>Everything you edit here is a <b>shared draft</b> that autosaves. The storefront only changes when a publisher clicks <b>Check & publish</b>.</li><li>A publish is live on the next page load and everywhere within 5 minutes. No deploy, no theme upload.</li><li><b>Seasons</b> go live from their window on their own; they do not need a publish.</li><li><b>Relationships</b> take effect within 5 minutes of saving a rule.</li><li>Products, prices, stock and images always come from BigCommerce and Searchspring. The editor owns words, order, art and rules.</li><li>Something wrong? <b>Versions → Revert</b> is one click, and the <b>kill switch</b> shows the plain server page.</li></ul></div>
      <div className="card"><h2>Recent activity</h2><Table cols={[{ k: 'created_at', l: 'When', f: a => ago(a.created_at) }, { k: 'actor', l: 'Who' }, { k: 'action', l: 'What' }, { k: 'target', l: 'Target' }]} rows={audit} /></div>
    </div>
    <div className="card" style={{ marginTop: 12 }}><h2>Crawlable seasonal content</h2><span className="muted small">On publish the leading season rail and promo blocks are rendered as static HTML into a BigCommerce widget (mid promo region). {status?.widget?.enabled ? <Pill kind="good">enabled</Pill> : <Pill kind="warn">dark until HOME_WIDGET_PUBLISH=1 is set on Railway</Pill>} {status?.widget?.placement_uuid ? ` · placement ${status.widget.placement_uuid}` : ''}</span></div>
  </div>;
}

function VersionsPage() {
  const { user, refreshLive, setDoc } = useDraft(); const toast = useToast(); const [list, setList] = useState([]); const [confirm, setConfirm] = useState(null); const [view, setView] = useState(null);
  const load = () => api('/config/versions').then(r => setList(r.data || []));
  useEffect(() => { load(); }, []);
  const revert = async (v) => { const r = await api(`/config/revert/${v}`, { method: 'POST' }); if (r.error) return toast(r.error, 'bad'); toast(`Published v${r.data.version} (a copy of v${v}). History is never rewritten.`); setConfirm(null); load(); refreshLive(); };
  const loadIntoDraft = async (v) => { const r = await api(`/config/versions/${v}`); if (r.data) { setDoc(() => r.data.doc); toast(`v${v} loaded into the draft (not published)`); } };
  return <div><div className="topbar"><h1>Versions</h1><span className="muted">Every publish is a version. Revert publishes the older one as a new version.</span></div>
    <div className="card tight"><Table cols={[{ k: 'version', l: 'Version', f: v => <span><b>v{v.version}</b> {v.is_live ? <Pill kind="good">live</Pill> : null}</span> }, { k: 'created_at', l: 'When', f: v => new Date(v.created_at).toLocaleString() }, { k: 'author_name', l: 'Who' }, { k: 'note', l: 'Note' }, { k: 'changes', l: 'What changed', f: v => <span className="diffs">{(v.changes || []).slice(0, 8).map((c, i) => <span key={i}>{c}</span>)}{v.changes && v.changes.length > 8 ? '…' : ''}</span> }, { k: 'a', l: '', f: v => <div className="row"><button className="btn sm" onClick={() => loadIntoDraft(v.version)}>Load into draft</button>{user.can_publish && !v.is_live && <button className="btn sm gold" onClick={() => setConfirm(v.version)}>Revert to this</button>}</div> }]} rows={list} /></div>
    {confirm && <Confirm text={`Publish a copy of v${confirm} as the new live version?`} yes="Revert" onYes={() => revert(confirm)} onNo={() => setConfirm(null)} />}
  </div>;
}

function UsersPage() {
  const { user } = useDraft(); const toast = useToast(); const [list, setList] = useState([]); const [form, setForm] = useState(null);
  const load = () => api('/users').then(r => setList(r.data || []));
  useEffect(() => { load(); }, []);
  const save = async () => { const r = await api('/users', { method: 'POST', body: form }); if (r.error) return toast(r.error, 'bad'); toast(r.data.existed ? 'Existing account given home access' : 'User created'); setForm(null); load(); };
  const toggle = async (u, publisher) => { const r = await api(`/users/${u.id}`, { method: 'PUT', body: { publisher } }); if (r.error) return toast(r.error, 'bad'); load(); };
  const removeHome = async (u) => { if (!confirm(`Remove ${u.email}'s access to the home editor?`)) return; const r = await api(`/users/${u.id}`, { method: 'PUT', body: { remove_home: true } }); if (r.error) return toast(r.error, 'bad'); load(); };
  const reset = async (u) => { const pw = prompt(`New password for ${u.email} (10+ characters)`); if (!pw) return; const r = await api(`/users/${u.id}`, { method: 'PUT', body: { new_password: pw } }); toast(r.error || 'Password updated', r.error ? 'bad' : ''); };
  return <div><div className="topbar"><h1>Users</h1><span className="muted">Editors draft and preview. Publishers publish, revert, run jobs and manage users. Nobody here can reach the auctions admin with this login.</span>{user.can_publish && <button className="btn primary right" onClick={() => setForm({ email: '', name: '', password: '', publisher: false })}>+ User</button>}</div>
    <div className="card tight"><Table cols={[{ k: 'name', l: 'Name' }, { k: 'email', l: 'Email' }, { k: 'role', l: 'Home role', f: u => <Pill kind={u.scopes.includes('home:publish') ? 'gold' : 'navy'}>{u.scopes.includes('home:publish') ? 'Publisher' : 'Editor'}</Pill> }, { k: 'other', l: 'Other access', f: u => u.scopes.filter(s => !s.startsWith('home')).join(', ') || '—' }, { k: 'last_login', l: 'Last sign-in', f: u => u.last_login ? ago(u.last_login) : 'never' }, { k: 'a', l: '', f: u => user.can_publish && u.id !== user.id ? <div className="row"><button className="btn sm" onClick={() => toggle(u, !u.scopes.includes('home:publish'))}>{u.scopes.includes('home:publish') ? 'Make editor' : 'Make publisher'}</button><button className="btn sm" onClick={() => reset(u)}>Reset password</button><button className="btn sm danger" onClick={() => removeHome(u)}>Remove</button></div> : null }]} rows={list} /></div>
    {form && <Modal title="New home editor" onClose={() => setForm(null)} footer={<><button className="btn" onClick={() => setForm(null)}>Cancel</button><button className="btn primary" onClick={save} disabled={!form.email || !form.name || form.password.length < 10}>Create</button></>}>
      <div className="stack"><Field label="Name"><Text value={form.name} onChange={v => setForm({ ...form, name: v })} /></Field><Field label="Email"><input type="email" value={form.email} onChange={e => setForm({ ...form, email: e.target.value })} /></Field><Field label="Password" hint="10+ characters; share it out of band"><input type="password" value={form.password} onChange={e => setForm({ ...form, password: e.target.value })} /></Field><Toggle on={form.publisher} onChange={v => setForm({ ...form, publisher: v })} label="Publisher (can publish and revert)" /></div></Modal>}
  </div>;
}

function PreviewPane() {
  const { doc } = useDraft(); const [arrival, setArrival] = useState('new'); const [phone, setPhone] = useState(false); const [token, setToken] = useState(null); const [stamp, setStamp] = useState(0); const [busy, setBusy] = useState(false);
  const refresh = async () => { setBusy(true); const r = await api('/config/preview-token', { method: 'POST', body: { doc } }); setBusy(false); if (r.data) { setToken(r.data.token); setStamp(Date.now()); } };
  useEffect(() => { refresh(); }, []);
  const a = ARRIVALS.find(x => x.key === arrival);
  const src = token ? `${STORE}/?pc_preview=${token}&${a.params}&pc_ts=${stamp}` : 'about:blank';
  return <aside className="preview-pane">
    <div className="tabs"><div className="row" style={{ width: '100%' }}><b className="small">Preview</b><span className="muted tiny">the real storefront with the draft applied</span><button className="btn sm right" onClick={refresh} disabled={busy}>{busy ? '…' : 'Refresh with draft'}</button><button className="btn sm" onClick={() => setPhone(!phone)}>{phone ? 'Desktop' : 'Phone'}</button></div>{ARRIVALS.map(x => <button key={x.key} className={'tab ' + (arrival === x.key ? 'active' : '')} onClick={() => setArrival(x.key)}>{x.label}</button>)}</div>
    <div className={'frame-wrap ' + (phone ? 'phone' : '')}><iframe title="Storefront preview" src={src} /></div>
  </aside>;
}
