// v2 App shell: auth, store with audit, routing, demo mode
const LS2 = 'soflolg-v2-state';
const loadState2 = () => { try { const s = JSON.parse(localStorage.getItem(LS2)); if (s && s.orders && s.audit) return seedBilling(s); } catch (e) {} return seedBilling(seedState2()); };
const userFromKey = (st, key) => { if (key.startsWith('client:')) { const c = st.clients.find(x => x.id === key.slice(7)); return { id: c.id, name: c.contact, role: 'client', clientId: c.id, key }; } const u = st.users.find(x => x.role === key && x.active) || st.users.find(x => x.id === key); return { ...u, key: u.role }; };

const Wordmark2 = ({ name }) => <span className="wordmark"><span className="wm-mark">S</span>{name || 'SofloLG'}</span>;

const Login2 = ({ st, onLogin, onDemo }) => {
  const [email, setEmail] = useState(''); const [pw, setPw] = useState(''); const [err, setErr] = useState('');
  const accounts = [...st.users.filter(u => u.active).map(u => ({ email: u.email, pw: u.password, label: `${u.name} · ${ROLE_LABEL[u.role]}` })), ...st.clients.slice(0, 2).map(c => ({ email: c.email, pw: c.password, label: `${c.contact} · ${c.name}` }))];
  const go = (e, p) => { const u = st.users.find(x => x.email === e && x.password === p && x.active); const c = st.clients.find(x => x.email === e && x.password === p);
    if (u) onLogin({ ...u, key: u.role }); else if (c) onLogin({ id: c.id, name: c.contact, role: 'client', clientId: c.id, key: 'client:' + c.id }); else setErr('No active account with that email and password.'); };
  return <div className="login">
    <div className="login-brand"><Wordmark2 name={st.settings.company}></Wordmark2><div><h1>Where is my container?<br></br>Answered before they ask.</h1><p>{t('Sign in to see where your materials are, what you owe, and what has shipped — without calling.')}</p></div>
      <button className="demo-cta" onClick={onDemo} data-tour="start-demo">{I.sparkle}<span><b>{t('Start demo')}</b><span>Guided tours, journey map and a 70-second auto-play across all roles</span></span>{I.right}</button></div>
    <form className="login-card" onSubmit={e => { e.preventDefault(); go(email, pw); }}>
      <h2>{t('Sign in')}</h2>
      <Field label="Email"><input className="inp" type="email" value={email} onChange={e => setEmail(e.target.value)} placeholder="you@company.com"></input></Field>
      <Field label="Password"><input className="inp" type="password" value={pw} onChange={e => setPw(e.target.value)} placeholder="••••••"></input></Field>
      {err && <div className="note note-warn">{err}</div>}
      <Btn v="primary" type="submit">Sign in</Btn>
      <div className="login-demo"><span className="kicker">{t('Demo accounts')}</span>{accounts.map(a => <button type="button" key={a.email} onClick={() => go(a.email, a.pw)}>{a.label}<span className="mono mute">{a.email}</span></button>)}</div>
    </form>
  </div>;
};

const App2 = () => {
  const [st, setSt] = useState(loadState2);
  const [user, setUser] = useState(() => { try { return JSON.parse(sessionStorage.getItem('soflolg-v2-user')); } catch (e) { return null; } });
  const [route, setRoute] = useState({ view: 'home', params: {} });
  const [q, setQ] = useState(''); const [toast, show] = useToast();
  const [demo, setDemo] = useState(() => sessionStorage.getItem('soflolg-v2-demo') === '1'); const [mode, setMode] = useState(null); const [speed, setSpeed] = useState(5);
  const [lang, setLangState] = useState(window.__lang); const setLang = (l) => { window.__lang = l; localStorage.setItem('soflolg-lang', l); setLangState(l); };
  window.__settings = st.settings;
  useEffect(() => { localStorage.setItem(LS2, JSON.stringify(st)); }, [st]);
  useEffect(() => { sessionStorage.setItem('soflolg-v2-user', JSON.stringify(user)); }, [user]);
  useEffect(() => { sessionStorage.setItem('soflolg-v2-demo', demo ? '1' : '0'); }, [demo]);
  const goRef = useRef(); goRef.current = (k, v, p) => go(k, v, p);
  useEffect(() => { const apply = () => { const h = location.hash.slice(1); if (!h) return; const { role, view, tour, play, ...params } = Object.fromEntries(new URLSearchParams(h)); if (!role) return; setDemo(true); setMode(null); goRef.current(role, view || 'home', params); if (tour) setTimeout(() => setMode({ kind: 'tour', role }), 400); if (play) setTimeout(() => setMode({ kind: 'play' }), 400); }; apply(); addEventListener('hashchange', apply); return () => removeEventListener('hashchange', apply); }, []);
  useEffect(() => { if (demo && !window.__dnav) { window.__dnav = true; mountDemoNav({ app: true }); } }, [demo]);
  useEffect(() => { if (!mode) window.scrollTo(0, 0); }, [route]);
  const nav = (view, params = {}) => { setRoute({ view, params }); setQ(''); };
  const go = (key, view = 'home', params = {}) => { setUser(userFromKey(st, key)); setRoute({ view, params }); setQ(''); };
  const now = () => new Date(TODAY.getTime() + (Date.now() % 86400000)).toISOString();
  const by = () => user?.name || 'System'; const role = () => user?.role || 'system';
  const upd = (fn) => setSt(s => { const n = JSON.parse(JSON.stringify(s)); const log = (action, target, kind) => n.audit.unshift({ at: now(), by: by(), role: role(), action, target, kind }); fn(n, log); return n; });
  // billing helpers used inside updaters
  const ensureInvoice = (s, o, log) => { if (s.invoices.some(i => i.orderId === o.id)) return; const at = now(); const n = Math.max(40, ...s.invoices.map(i => +i.id.split('-')[1])) + 1; s.invoices.push({ id: `INV-${n}`, orderId: o.id, clientId: o.clientId, amount: +o.split.total.toFixed(2), issuedAt: at, sentAt: at, dueAt: addDays(at, s.settings.netDays || 14), payments: [], void: false }); log(`Invoice INV-${n} issued · ${fmt(o.split.total)} · due in ${s.settings.netDays || 14}d`, `INV-${n}`, 'payment'); o.history.push({ at, event: `Invoice INV-${n} issued · ${fmt(o.split.total)}`, by: by() }); };
  const applyPayment = (s, inv, p, log) => { const at = now(); const id = 'PMT-' + (100 + s.invoices.reduce((c, i) => c + i.payments.length, 0)); inv.payments.push({ id, at: p.at ? new Date(p.at + 'T12:00:00').toISOString() : at, amount: +(+p.amount).toFixed(2), method: p.method || 'Wire', ref: p.ref || '', note: p.note || '', by: by() }); const o = s.orders.find(x => x.id === inv.orderId); const bal = invBalance(inv); log(`Payment ${id} · ${fmt(p.amount)} on ${inv.id}${bal > 0 ? ` · balance ${fmt(bal)}` : ' · paid in full'}`, inv.id, 'payment');
    if (o) { o.updatedAt = at; o.history.push({ at, event: bal > 0 ? `Partial payment ${fmt(p.amount)} · ${fmt(bal)} outstanding` : `Client payment received · ${fmt(o.split.total)} → supplier ${fmt(o.split.supplier)}, margin ${fmt(o.split.margin)}`, by: by() });
      if (bal <= 0.005 && !o.paid) { o.paid = true; o.paidAt = at; const pn = 'PAY-' + (100 + s.payables.length); s.payables.push({ id: pn, orderId: o.id, supplierId: o.supplierId, amount: +o.split.supplier.toFixed(2), createdAt: at, dueAt: addDays(at, 7), status: 'Pending', paidAt: null, ref: '' }); log(`Payable ${pn} created · ${fmt(o.split.supplier)} to supplier`, pn, 'payment'); } } };
  const act = {
    setStatus: (id, status) => { upd((s, log) => { const o = s.orders.find(x => x.id === id); const at = now(); const ev = status === 'Confirmed' && !o.split ? 'Confirmed · split locked' : status === 'Purchased' ? 'Marked as purchased' : status === 'Stuck' ? 'Status set to Stuck' : status; if (status === 'Confirmed' && !o.split) o.split = { ...orderTotals(o), lockedAt: at }; o.history.push({ at, event: ev, by: by() }); o.status = status; o.updatedAt = at; log(`${o.status === 'Confirmed' ? ev : 'Status → ' + status}`, id, 'order'); if (status === 'Confirmed') ensureInvoice(s, o, log); }); show(status === 'Confirmed' ? 'Order confirmed, split locked' : `Status: ${t(status)}`); },
    markPaid: (id) => { upd((s, log) => { const o = s.orders.find(x => x.id === id); ensureInvoice(s, o, log); const inv = s.invoices.find(i => i.orderId === id && !i.void); applyPayment(s, inv, { amount: invBalance(inv), method: 'Wire', ref: 'manual' }, log); }); show('Payment recorded, split filed'); },
    recordPayment: (invId, p) => { upd((s, log) => { const inv = s.invoices.find(i => i.id === invId); applyPayment(s, inv, p, log); }); show('Payment saved'); },
    sendInvoice: (invId) => { upd((s, log) => { const inv = s.invoices.find(i => i.id === invId); inv.sentAt = now(); log(`Invoice ${invId} sent to client`, invId, 'payment'); }); show('Invoice marked as sent'); },
    remind: (invId) => { upd((s, log) => { const inv = s.invoices.find(i => i.id === invId); inv.reminders = (inv.reminders || 0) + 1; log(`Reminder #${inv.reminders} logged for ${invId} · ${fmt(invBalance(inv))} overdue`, invId, 'payment'); }); show('Reminder logged'); },
    voidInvoice: (invId) => { upd((s, log) => { const inv = s.invoices.find(i => i.id === invId); inv.void = true; log(`Invoice ${invId} voided`, invId, 'admin'); }); show('Invoice voided'); },
    payableStatus: (pid, status, ref) => { upd((s, log) => { const p = s.payables.find(x => x.id === pid); p.status = status; if (status === 'Paid') { p.paidAt = now(); p.ref = ref || p.ref; const o = s.orders.find(x => x.id === p.orderId); if (o) o.history.push({ at: now(), event: `Supplier paid ${fmt(p.amount)} · ref ${p.ref}`, by: by() }); } log(`Payable ${pid} → ${status}${ref ? ' · ref ' + ref : ''}`, pid, 'payment'); }); show(status === 'Paid' ? 'Supplier payment recorded' : 'Payable scheduled'); },
    linkContainer: (id, cid) => { upd((s, log) => { const o = s.orders.find(x => x.id === id); const at = now(); o.containerId = cid; o.updatedAt = at; const ev = cid ? `Linked to container ${cid}` : 'Unlinked from container'; o.history.push({ at, event: ev, by: by() }); log(ev, id, 'order'); }); show(cid ? `Linked to ${cid}` : 'Container unlinked'); },
    saveOrder: (o, confirm) => { let nid = o.id || 'SO-' + (Math.max(...st.orders.map(x => +x.id.split('-')[1])) + 1); const isNew = !o.id; upd((s, log) => { const at = now(); if (isNew) { s.orders.push({ ...o, id: nid, status: 'Draft', paid: false, split: null, createdAt: at, updatedAt: at, history: [{ at, event: 'Order created', by: by() }] }); log('Order created', nid, 'order'); } else log('Order edited', nid, 'order');
      const tg = s.orders.find(x => x.id === nid); Object.assign(tg, { clientId: o.clientId, supplierId: o.supplierId, priority: o.priority, requestedDate: o.requestedDate, containerId: o.containerId, items: o.items, updatedAt: at });
      if (confirm) { tg.split = { ...orderTotals(tg), lockedAt: at }; tg.status = 'Confirmed'; tg.history.push({ at, event: 'Confirmed · split locked', by: by() }); log('Confirmed · split locked', nid, 'order'); ensureInvoice(s, tg, log); } }); show(confirm ? 'Order confirmed, split locked' : 'Draft saved'); return nid; },
    saveContainer: (c, id) => { upd((s, log) => { const at = now(); if (id) { Object.assign(s.containers.find(x => x.id === id), { origin: c.origin, destination: c.destination, etd: c.etd, eta: c.eta, status: c.status, updatedAt: at }); log('Container edited', id, 'container'); } else { s.containers.push({ ...c, updatedAt: at, notes: [{ at, text: 'Container created', by: by() }] }); log('Container created', c.id, 'container'); } }); show('Container saved'); return id || c.id; },
    updateContainer: (id, status, text) => { upd((s, log) => { const c = s.containers.find(x => x.id === id); const at = now(); const changed = status !== c.status; c.status = status; c.updatedAt = at; const note = (changed ? `Status → ${status}. ` : '') + text; c.notes.push({ at, text: note, by: by() }); log(note, id, 'container');
      if (changed) s.orders.filter(o => o.containerId === id && !CLOSED.includes(o.status)).forEach(o => { o.updatedAt = at; o.history.push({ at, event: `Container ${id}: ${status}`, by: by() }); }); }); show('Update posted'); },
    cancelOrder: (id) => { upd((s, log) => { const o = s.orders.find(x => x.id === id); const at = now(); o.status = 'Cancelled'; o.updatedAt = at; o.history.push({ at, event: 'Cancelled by admin', by: by() }); log('Order cancelled', id, 'admin'); }); show('Order cancelled'); },
    deleteOrder: (id) => { upd((s, log) => { s.orders = s.orders.filter(x => x.id !== id); log('Order permanently deleted', id, 'admin'); }); show('Order deleted'); },
    editSplit: (id, supplier, reason) => { upd((s, log) => { const o = s.orders.find(x => x.id === id); const at = now(); const prev = o.split.supplier; o.split = { ...o.split, supplier, margin: o.split.total - supplier, editedAt: at }; o.updatedAt = at; const ev = `Split edited by admin: supplier ${fmt(prev)} → ${fmt(supplier)}, margin now ${fmt(o.split.margin)}. Reason: ${reason}`; o.history.push({ at, event: ev, by: by() }); log(ev, id, 'admin'); }); show('Split updated and logged'); },
    inviteUser: (u) => { upd((s, log) => { if (s.users.some(x => x.email === u.email)) return; const id = 'u' + (s.users.length + 1); s.users.push({ id, name: u.name, email: u.email, role: u.role, password: 'demo', active: true, joined: now().slice(0, 10), invited: true }); log(`Invited ${u.name} as ${ROLE_LABEL[u.role]}`, id, 'team'); }); show(`Invite sent to ${u.email}`); },
    toggleUser: (id) => { upd((s, log) => { const u = s.users.find(x => x.id === id); u.active = !u.active; log(`${u.active ? 'Reactivated' : 'Deactivated'} user ${u.name}`, id, 'team'); }); },
    setRole: (id, r) => { upd((s, log) => { const u = s.users.find(x => x.id === id); log(`Role of ${u.name}: ${ROLE_LABEL[u.role]} → ${ROLE_LABEL[r]}`, id, 'team'); u.role = r; }); show('Role updated'); },
    saveParty: (p) => { upd((s, log) => { const list = p.kind === 'client' ? s.clients : s.suppliers; if (p.id) { Object.assign(list.find(x => x.id === p.id), p); log(`${p.kind} edited: ${p.name}`, p.id, 'team'); } else { const id = (p.kind === 'client' ? 'c' : 's') + (list.length + 1); list.push({ ...p, id, password: 'client' }); log(`${p.kind} created: ${p.name}`, id, 'team'); } }); show('Saved'); },
    saveSettings: (v) => { upd((s, log) => { const diff = Object.keys(v).filter(k => v[k] !== s.settings[k]).map(k => `${k}: ${s.settings[k]} → ${v[k]}`).join(', '); s.settings = v; log(`Settings changed · ${diff}`, 'settings', 'settings'); }); show('Settings saved'); },
    exportAll: () => { const o = st.orders; download('soflolg-orders.csv', toCSV([['Order','Client','Supplier','Status','Priority','Requested','Container','Paid','Client total','Supplier amount','Margin'], ...o.map(x => [x.id, st.clients.find(c => c.id === x.clientId)?.name, st.suppliers.find(s => s.id === x.supplierId)?.name, x.status, x.priority, x.requestedDate, x.containerId || '', x.paid ? 'yes' : 'no', x.split?.total ?? '', x.split?.supplier ?? '', x.split?.margin ?? ''])]));
      setTimeout(() => download('soflolg-containers.csv', toCSV([['Container','Origin','Destination','ETD','ETA','Status','Last update'], ...st.containers.map(c => [c.id, c.origin, c.destination, c.etd, c.eta, c.status, c.updatedAt])])), 300);
      setTimeout(() => { const a = document.createElement('a'); a.href = URL.createObjectURL(new Blob([JSON.stringify(st, null, 2)], { type: 'application/json' })); a.download = 'soflolg-snapshot.json'; a.click(); }, 600);
      upd((s, log) => log('Full data export', 'workspace', 'admin')); show('Exporting 3 files'); },
    markRead: (ids) => upd(s => ids.forEach(id => s.readNotes[id] = true)),
    reset: () => { setSt(seedBilling(seedState2())); nav('home'); show('Demo data reset'); },
  };
  const startDemo = () => { setDemo(true); go('manager', 'journey'); };
  const exitDemo = () => { setDemo(false); setMode(null); setUser(null); nav('home'); };
  if (!user) return <Login2 st={st} onLogin={u => { setUser(u); nav('home'); }} onDemo={startDemo}></Login2>;
  const r = user.role; const { view, params } = route; const isStaff = r !== 'client';
  const navItems = r === 'client' ? [['home','Your orders',I.orders],['invoices','Invoices',I.dollar],['inbox','Inbox',I.bell]] : [['home','Dashboard',I.grid],['live','Live map',I.map],['orders','Orders',I.orders],['containers','Containers',I.box],['billing','Billing',I.dollar],['finance','Finance',I.chart],['analytics','Analytics',I.chart]];
  if (r === 'admin') navItems.push(['admin','Admin',I.shield]);
  if (r === 'finance') navItems.splice(0, 1);
  const adminViews = ['team','directory','audit','settings'];
  const activeNav = ['order','order-new','order-edit'].includes(view) ? 'orders' : ['container','container-new','container-edit'].includes(view) ? 'containers' : adminViews.includes(view) ? 'admin' : view;
  const unread = r === 'client' ? clientNotifications(st, user.clientId).filter(n => !st.readNotes[n.id]).length : 0;
  let body;
  if (view === 'journey') body = <JourneyMap go={go} startTour={(k) => { go(k, 'home'); setMode({ kind: 'tour', role: k }); }}></JourneyMap>;
  else if (view === 'live' && isStaff) body = <LiveMap nav={nav}></LiveMap>;
  else if (r === 'client' && view === 'invoices') body = <ClientInvoices st={st} client={st.clients.find(c => c.id === user.clientId)} nav={nav}></ClientInvoices>;
  else if (r === 'client') body = <ClientPortal st={st} client={st.clients.find(c => c.id === user.clientId)} view={view} params={params} nav={nav} act={act}></ClientPortal>;
  else switch (view) {
    case 'orders': body = <Orders key={JSON.stringify(params)} st={st} nav={nav} params={params} q={q}></Orders>; break;
    case 'order': body = <OrderDetail key={params.id} st={st} id={params.id} nav={nav} act={act} user={user}></OrderDetail>; break;
    case 'order-new': case 'order-edit': body = r !== 'finance' ? <OrderEdit key={params.id || 'new'} st={st} id={params.id} nav={nav} act={act}></OrderEdit> : <Empty>Managers only.</Empty>; break;
    case 'containers': body = <Containers key={JSON.stringify(params)} st={st} nav={nav} params={params} q={q}></Containers>; break;
    case 'container': body = <ContainerDetail key={params.id} st={st} id={params.id} nav={nav} act={act} user={{ ...user, role: r === 'admin' ? 'manager' : r }}></ContainerDetail>; break;
    case 'container-new': case 'container-edit': body = r !== 'finance' ? <ContainerEdit key={params.id || 'new'} st={st} id={params.id} nav={nav} act={act}></ContainerEdit> : <Empty>Managers only.</Empty>; break;
    case 'billing': body = <Billing key={JSON.stringify(params)} st={st} nav={nav} act={act} user={user} q={q} params={params}></Billing>; break;
    case 'finance': body = <Finance st={st} nav={nav} q={q}></Finance>; break;
    case 'analytics': body = <Analytics st={st} nav={nav}></Analytics>; break;
    case 'admin': case 'team': body = r === 'admin' ? <Team st={st} act={act} user={user}></Team> : <Empty>Admins only.</Empty>; break;
    case 'directory': body = r === 'admin' ? <Directory st={st} act={act} nav={nav}></Directory> : <Empty>Admins only.</Empty>; break;
    case 'audit': body = r === 'admin' ? <AuditLog st={st} nav={nav} q={q}></AuditLog> : <Empty>Admins only.</Empty>; break;
    case 'settings': body = r === 'admin' ? <Settings key={JSON.stringify(st.settings)} st={st} act={act}></Settings> : <Empty>Admins only.</Empty>; break;
    default: body = r === 'finance' ? <Billing st={st} nav={nav} act={act} user={user} q={q}></Billing> : <><BillingAlerts st={st} nav={nav}></BillingAlerts><Dashboard st={st} nav={nav} q={q}></Dashboard></>;
  }
  return <div className={`shell role-${r}${demo ? ' has-demo' : ''}`}>
    {demo && <DemoBanner roleKey={user.key} go={(k, v) => { setMode(null); go(k, v); }} onTour={() => setMode({ kind: 'tour', role: user.key })} onMap={() => { setMode(null); nav('journey'); }} onPlay={() => setMode({ kind: 'play' })} onExit={exitDemo} lang={lang} setLang={setLang}></DemoBanner>}
    <aside className="side">
      <div className="side-top"><Wordmark2 name={st.settings.company}></Wordmark2></div>
      <nav className="side-nav">{navItems.map(([k, l, ic]) => <button key={k} className={activeNav === k ? 'on' : ''} onClick={() => nav(k)}>{ic}<span>{t(l)}</span>{k === 'inbox' && unread > 0 && <span className="cnt">{unread}</span>}</button>)}
        {r === 'admin' && activeNav === 'admin' && <div className="side-sub">{[['team','Team'],['directory','Clients & suppliers'],['audit','Audit log'],['settings','Settings']].map(([k, l]) => <button key={k} className={view === k || (view === 'admin' && k === 'team') ? 'on' : ''} onClick={() => nav(k)}>{t(l)}</button>)}</div>}
      </nav>
      <div className="side-bot">
        <div className="side-user">{I.user}<span><b>{user.name}</b><span className="mute">{r === 'client' ? st.clients.find(c => c.id === user.clientId).name : ROLE_LABEL[r]}</span></span></div>
        <div className="seg seg-sm">{['en','es'].map(l => <button key={l} className={lang === l ? 'on' : ''} onClick={() => setLang(l)}>{l.toUpperCase()}</button>)}</div>
        {!demo && <button className="side-link" onClick={startDemo}>{I.sparkle}{t('Demo mode')}</button>}
        {isStaff && <button className="side-link" onClick={act.reset}>{t('Reset demo data')}</button>}
        <button className="side-link" onClick={() => { setUser(null); nav('home'); }}>{I.logout}{t('Sign out')}</button>
      </div>
    </aside>
    <div className="main">
      <header className="top">
        {isStaff && <label className="search">{I.search}<input placeholder={t('Search client, order ID or container…')} value={q} onChange={e => { setQ(e.target.value); if (!['home','orders','containers','finance','audit','billing'].includes(view)) setRoute({ view: 'home', params: {} }); }}></input>{q && <button onClick={() => setQ('')} aria-label="Clear">{I.x}</button>}</label>}
        {!isStaff && <span className="top-note">{t('Read-only portal · call us for changes')}</span>}
        <div className="top-role"><span className="kicker">{t('Signed in as')}</span><Select value={user.key} onChange={v => go(v, 'home')} options={[...st.users.filter(u => u.active).map(u => ({ id: u.role === user.role ? u.role : u.role, name: `${u.name} (${ROLE_LABEL[u.role]})` })).filter((x, i, a) => a.findIndex(y => y.id === x.id) === i), ...st.clients.map(c => ({ id: 'client:' + c.id, name: `${c.contact} (${c.name})` }))]}></Select></div>
      </header>
      {body}
      <Toast msg={toast}></Toast>
    </div>
    {mode?.kind === 'tour' && <Tour key={mode.role} role={mode.role} go={go} onClose={() => setMode(null)}></Tour>}
    {mode?.kind === 'play' && <AutoPlay go={go} act={act} speed={speed} setSpeed={setSpeed} onClose={() => setMode(null)}></AutoPlay>}
  </div>;
};

ReactDOM.createRoot(document.getElementById('root')).render(<App2></App2>);
