/* Lojo workspace shell: the API helper, sign-in, navigation, shared pieces,
   module previews and the delegate page.

   State the shell owns lives in 24-app.jsx; everything here takes props.
   Types (JSDoc) mirror scripts/lojo/schema.py:
     @typedef {{id:string, name:string, role:string, is_demo?:boolean}} OrgRef
     @typedef {{id:string, title:string, status:'enabled'|'preview', tab:string, tagline:string, preview:object}} ModuleView
*/

const USER_TOKEN_KEY = 'lojo.user.token';
function readUserToken() { try { return sessionStorage.getItem(USER_TOKEN_KEY) || localStorage.getItem(USER_TOKEN_KEY) || ''; } catch (e) { return ''; } }
function writeUserToken(t, remember) {
  try {
    sessionStorage.removeItem(USER_TOKEN_KEY); localStorage.removeItem(USER_TOKEN_KEY);
    if (t) (remember ? localStorage : sessionStorage).setItem(USER_TOKEN_KEY, t);
  } catch (e) { /* private mode */ }
}

/* One call shape for the whole platform. */
async function lojo(token, payload) {
  const headers = { 'Content-Type': 'application/json' };
  if (token) headers.Authorization = 'Bearer ' + token;
  return apiCall('/api/lojo', { method: 'POST', headers, body: JSON.stringify(payload) });
}

function money(n, opts) {
  if (n === null || n === undefined || n === '' || Number.isNaN(Number(n))) return '—';
  const v = Number(n);
  const abs = Math.abs(v);
  const compact = opts && opts.compact;
  let s;
  if (compact && abs >= 1e6) s = (abs / 1e6).toFixed(1) + 'M';
  else if (compact && abs >= 1e4) s = Math.round(abs / 1e3) + 'k';
  else s = abs.toLocaleString('en-US', { maximumFractionDigits: 0 });
  return (v < 0 ? '−$' : '$') + s;
}
function pctOf(a, b) { return b ? Math.round((a / b) * 100) : 0; }
function shortDate(s) { return s ? String(s).slice(0, 10) : ''; }
function titleCase(s) { return String(s || '').replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()); }

const KIND_LABEL = { keep: 'Keep', optimize: 'Optimize', consolidate: 'Consolidate', replace: 'Replace', automate: 'Automate', eliminate: 'Eliminate', redesign: 'Redesign', other: 'Option' };
function KindPill({ kind }) { return <span className={'kind-pill kind-' + kind}>{KIND_LABEL[kind] || kind}</span>; }

function Confidence({ c }) {
  if (!c || !c.grade) return <span style={{ color: 'var(--fg-subtle)' }}>—</span>;
  const tone = c.grade === 'A' ? 'good' : c.grade === 'B' ? 'accent' : 'warn';
  return <span title={(c.basis || []).join(' · ')} style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}>
    <Pill tone={tone}>{c.grade}</Pill><span className="mono" style={{ fontSize: 11.5, color: 'var(--fg-muted)' }}>{Math.round((c.score || 0) * 100)}%</span>
  </span>;
}
function Effort({ e }) {
  if (!e || !e.band) return <span style={{ color: 'var(--fg-subtle)' }}>—</span>;
  return <span className="mono" style={{ fontSize: 12 }}>{e.band} · {e.days}d</span>;
}
function Stat({ label, value, note, tone }) {
  return <div className="stat"><div className="label">{label}</div><div className="value" style={tone ? { color: `var(--${tone})` } : {}}>{value}</div>{note && <div className="note">{note}</div>}</div>;
}
function Bar({ value, max }) { return <div className="bar"><span style={{ width: Math.min(100, pctOf(value, max)) + '%' }}></span></div>; }

function Drawer({ title, onClose, children, width }) {
  React.useEffect(() => { const f = (e) => { if (e.key === 'Escape') onClose(); }; window.addEventListener('keydown', f); return () => window.removeEventListener('keydown', f); }, [onClose]);
  return (
    <React.Fragment>
      <div className="drawer-backdrop" onClick={onClose}></div>
      <div className="drawer" style={width ? { width: `min(${width}px, 100vw)` } : {}} role="dialog" aria-label={title}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12, marginBottom: 16 }}>
          <h2 className="display" style={{ fontSize: 22, margin: 0 }}>{title}</h2>
          <button className="app-btn small" onClick={onClose}><Icon name="x" size={14} /> Close</button>
        </div>
        {children}
      </div>
    </React.Fragment>);
}

function Lines({ lines }) {
  if (!lines || !lines.length) return <div style={{ fontSize: 13, color: 'var(--fg-subtle)' }}>No cash terms: this option changes nothing.</div>;
  return <div className="lines">{lines.map((l, i) =>
    <div key={i}><span>{l.label}<br /><span className="note">{l.note}</span></span><span style={{ color: l.usd < 0 ? 'var(--crit)' : 'var(--good)' }}>{money(l.usd)}</span></div>)}</div>;
}

/* The label wraps its control, so it is associated without ids. */
function Field({ label, children, hint }) {
  return <label className="app-field"><span className="app-label">{label}</span>{children}{hint && <span style={{ display: 'block', fontSize: 11.5, color: 'var(--fg-subtle)', marginTop: 3 }}>{hint}</span>}</label>;
}
/* A captioned group whose children carry their own labels (checkbox lists). */
function Group({ label, children }) {
  return <div className="app-field"><span className="app-label">{label}</span>{children}</div>;
}
function Input(props) { return <input {...props} className={'app-input ' + (props.className || '')} />; }
function Select({ options, value, onChange, ...rest }) {
  return <select className="app-input" value={value} onChange={(e) => onChange(e.target.value)} {...rest}>
    {options.map((o) => Array.isArray(o) ? <option key={o[0]} value={o[0]}>{o[1]}</option> : <option key={o} value={o}>{o}</option>)}
  </select>;
}
function ViewToggle({ view, onChange }) {
  return <div className="view-toggle" title="Executive: findings and money. Implementation: evidence, owners and tasks.">
    {[['exec', 'Executive'], ['impl', 'Implementation']].map(([v, l]) => <button key={v} className={view === v ? 'on' : ''} onClick={() => onChange(v)}>{l}</button>)}
  </div>;
}

/* ---------------------------------------------------------------- sign in */
function SignIn({ onIn, demoOrg }) {
  const [mode, setMode] = React.useState('login');
  const [f, setF] = React.useState({ email: '', name: '', password: '', org_name: '', remember: true });
  const [busy, setBusy] = React.useState(false);
  const [error, setError] = React.useState('');
  const set = (k) => (e) => setF({ ...f, [k]: e.target.type === 'checkbox' ? e.target.checked : e.target.value });
  const submit = async (e) => {
    e.preventDefault(); setBusy(true); setError('');
    const d = await lojo('', { module: 'auth', action: mode, ...f });
    setBusy(false);
    if (!d.ok) { setError(d.message || 'That did not work.'); return; }
    writeUserToken(d.token, f.remember);
    onIn(d);
  };
  return (
    <div className="wrap" style={{ maxWidth: 440, padding: '70px var(--s6)' }}>
      <div style={{ marginBottom: 22 }}><a href="/" style={{ display: 'inline-flex' }}><Wordmark size={18} /></a></div>
      <h1 className="display" style={{ fontSize: 30, margin: '0 0 6px' }}>{mode === 'login' ? 'Sign in to your workspace' : 'Create your workspace'}</h1>
      <p style={{ color: 'var(--fg-muted)', fontSize: 14.5, margin: '0 0 22px', lineHeight: 1.6 }}>
        {mode === 'login' ? 'Your organization’s stack, opportunities and roadmap.' : 'An organization is yours to invite people into. You can join others by invitation.'}
      </p>
      <form onSubmit={submit}>
        {mode === 'signup' && <Field label="Your name"><Input value={f.name} onChange={set('name')} required /></Field>}
        <Field label="Work email"><Input type="email" value={f.email} onChange={set('email')} required autoComplete="username" /></Field>
        <Field label="Password" hint={mode === 'signup' ? 'At least 10 characters.' : ''}><Input type="password" value={f.password} onChange={set('password')} required autoComplete={mode === 'login' ? 'current-password' : 'new-password'} /></Field>
        {mode === 'signup' && <Field label="Organization name" hint="Leave blank if you were invited to one."><Input value={f.org_name} onChange={set('org_name')} /></Field>}
        <label style={{ display: 'flex', gap: 8, alignItems: 'center', fontSize: 13, color: 'var(--fg-muted)', marginBottom: 14 }}>
          <input type="checkbox" checked={f.remember} onChange={set('remember')} /> Keep me signed in on this device
        </label>
        {error && <div className="flash warn">{error}</div>}
        <button className="btn btn-primary" style={{ width: '100%', justifyContent: 'center' }} disabled={busy}>{busy ? 'One moment…' : mode === 'login' ? 'Sign in' : 'Create workspace'}</button>
      </form>
      <div style={{ marginTop: 16, fontSize: 13.5, color: 'var(--fg-muted)', display: 'flex', justifyContent: 'space-between', gap: 10, flexWrap: 'wrap' }}>
        <button onClick={() => { setMode(mode === 'login' ? 'signup' : 'login'); setError(''); }} style={{ background: 'none', border: 'none', color: 'var(--accent)', cursor: 'pointer', font: 'inherit', padding: 0 }}>
          {mode === 'login' ? 'New here? Create a workspace' : 'Have an account? Sign in'}
        </button>
        <a href={'/app/?demo=1'} style={{ color: 'var(--accent)' }}>Explore the demo →</a>
      </div>
      <div style={{ marginTop: 28, fontSize: 12, color: 'var(--fg-subtle)', lineHeight: 1.6 }}>
        Lojo team and agency sign-ins also work here with their existing tokens. Customer data is isolated per organization,
        encrypted where it is sensitive, and never used to train shared models.
      </div>
    </div>);
}

/* -------------------------------------------------------------------- nav */
function AppTop({ me, org, orgs, modules, moduleId, onModule, onOrg, view, onView, onSignOut, readOnly }) {
  const [open, setOpen] = React.useState(false);
  return (
    <div className="app-top">
      <div className="app-top-row">
        <a href="/" style={{ display: 'inline-flex' }}><Wordmark size={16} /></a>
        <span style={{ color: 'var(--fg-subtle)' }}>/</span>
        <div style={{ position: 'relative' }}>
          <button className="app-btn small" onClick={() => setOpen(!open)} aria-expanded={open}>
            {org ? org.name : 'Choose organization'} <Icon name="arrow-down" size={12} />
          </button>
          {open &&
          <div style={{ position: 'absolute', top: '110%', left: 0, minWidth: 260, background: 'var(--navy)', border: '1px solid var(--line-strong)', borderRadius: 'var(--r-md)', padding: 6, zIndex: 50, boxShadow: '0 20px 50px -10px rgba(0,0,0,.6)' }}>
            {(orgs || []).map((o) =>
            <button key={o.id} onClick={() => { setOpen(false); onOrg(o.id); }} style={{ display: 'flex', justifyContent: 'space-between', width: '100%', gap: 10, textAlign: 'left', background: o.id === (org && org.id) ? 'var(--navy-3)' : 'transparent', border: 'none', color: 'var(--fg)', font: 'inherit', fontSize: 13.5, padding: '8px 10px', borderRadius: 8, cursor: 'pointer' }}>
              <span>{o.name}</span><span className="mono" style={{ fontSize: 10.5, color: 'var(--fg-subtle)' }}>{o.role}</span>
            </button>)}
            <button onClick={() => { setOpen(false); onOrg('__demo'); }} style={{ display: 'flex', justifyContent: 'space-between', width: '100%', gap: 10, textAlign: 'left', background: 'transparent', border: 'none', color: 'var(--fg-muted)', font: 'inherit', fontSize: 13.5, padding: '8px 10px', borderRadius: 8, cursor: 'pointer', borderTop: '1px solid var(--line)' }}>
              <span>Meridian Outdoor Co. (demo)</span><span className="mono" style={{ fontSize: 10.5, color: 'var(--fg-subtle)' }}>read-only</span>
            </button>
            {me && me.actor && me.actor.actor_type === 'user' &&
            <button onClick={() => { setOpen(false); onOrg('__new'); }} style={{ width: '100%', textAlign: 'left', background: 'transparent', border: 'none', color: 'var(--accent)', font: 'inherit', fontSize: 13.5, padding: '8px 10px', cursor: 'pointer' }}>+ New organization</button>}
          </div>}
        </div>
        <div style={{ flex: 1 }}></div>
        <ViewToggle view={view} onChange={onView} />
        {me && <span style={{ fontSize: 13, color: 'var(--fg-muted)' }}>{me.actor.name || me.actor.email || me.actor.actor_type}</span>}
        <button className="app-btn small" onClick={onSignOut}>Sign out</button>
      </div>
      <div className="app-tabs">
        {(modules || []).map((m) =>
        <button key={m.id} className={'app-tab' + (m.id === moduleId ? ' on' : '')} onClick={() => onModule(m.id)} title={m.tagline}>
          {m.status === 'preview' && <span className="preview-dot"></span>}{m.tab}
        </button>)}
        <button className={'app-tab' + (moduleId === 'org' ? ' on' : '')} onClick={() => onModule('org')} style={{ marginLeft: 'auto' }}><Icon name="shield" size={13} /> Organization</button>
      </div>
      {readOnly &&
      <div className="demo-banner">
        <Icon name="eye" size={14} /> <b>Demo organization.</b> Meridian Outdoor Co. is fictional; every number is invented. It is read-only, and separate from customer data.
        <a href="/app/" style={{ color: 'var(--accent)', marginLeft: 'auto' }}>Set up your own →</a>
      </div>}
    </div>);
}

/* -------------------------------------------------------------- previews */
function ModulePreview({ m }) {
  const p = m.preview || {};
  const [view, setView] = React.useState('demo');
  if (view === 'demo' && typeof ModuleDemo === 'function') {
    return <div>
      <div style={{ display: 'flex', gap: 6, marginBottom: 16 }}>
        <button className="tour-tab on" style={{ padding: '7px 13px', fontSize: 13 }}>Demo on a fictional company</button>
        <button className="tour-tab" style={{ padding: '7px 13px', fontSize: 13 }} onClick={() => setView('about')}>About this module</button>
      </div>
      <ModuleDemo m={m} />
    </div>;
  }
  return (
    <div style={{ maxWidth: 820 }}>
      <div style={{ display: 'flex', gap: 6, marginBottom: 16 }}>
        <button className="tour-tab" style={{ padding: '7px 13px', fontSize: 13 }} onClick={() => setView('demo')}>Demo on a fictional company</button>
        <button className="tour-tab on" style={{ padding: '7px 13px', fontSize: 13 }}>About this module</button>
      </div>
      <div className="overline" style={{ marginBottom: 10 }}>Coming soon · beta pricing</div>
      <h1 className="display" style={{ fontSize: 'clamp(26px,4vw,38px)', margin: '0 0 10px' }}>Lojo {m.title}</h1>
      <p style={{ fontSize: 16, color: 'var(--fg-muted)', lineHeight: 1.65, margin: '0 0 16px' }}>{m.tagline}</p>
      {typeof TalkToUs === 'function' && <div style={{ marginBottom: 22 }}><TalkToUs moduleId={m.id} /></div>}
      <div className="app-panel" style={{ marginBottom: 14 }}>
        <h3>What it will do</h3>
        <ul style={{ margin: 0, paddingLeft: 18, color: 'var(--fg)', fontSize: 14.5, lineHeight: 1.7 }}>{(p.will || []).map((w) => <li key={w}>{w}</li>)}</ul>
      </div>
      <div className="app-grid cols-2">
        <div className="app-panel"><h3>Built on what you already capture</h3>
          <div style={{ fontSize: 14, color: 'var(--fg-muted)', lineHeight: 1.6 }}>{(p.uses || []).map((u) => <span key={u} className="chip" style={{ marginRight: 6, marginBottom: 6 }}>{titleCase(u)}</span>)}<div style={{ marginTop: 8 }}>{p.demo}</div></div></div>
        <div className="app-panel"><h3>{p.policy ? 'Policy' : 'Status'}</h3>
          <div style={{ fontSize: 14, color: 'var(--fg-muted)', lineHeight: 1.6 }}>
            {p.policy ? 'This module will never evaluate, rank, pay, promote or terminate individuals, and every consequential action will need a named approver.' : 'Nothing on this page is computed yet. When the module is enabled for your organization, its workflows appear here with the same evidence, confidence and money conventions as Stack and Execute.'}
          </div></div>
      </div>
    </div>);
}

/* --------------------------------------------------------- delegate page */
function DelegatePage({ token }) {
  const [data, setData] = React.useState(null);
  const [error, setError] = React.useState('');
  const [answers, setAnswers] = React.useState({});
  const [flash, setFlash] = React.useState('');
  const [done, setDone] = React.useState(false);
  const orgId = (token.split('.')[1] || '');
  const call = (p) => lojo(token, { ...p, org: orgId, _token: token });
  React.useEffect(() => { call({ module: 'stack', action: 'delegate-view' }).then((d) => { if (d.ok) setData(d); else setError(d.message || 'This link is not valid any more.'); }); }, [token]);
  if (error) return <div className="wrap" style={{ padding: '90px var(--s6)', color: 'var(--fg-muted)' }}>{error}</div>;
  if (!data) return <div className="wrap" style={{ padding: '90px var(--s6)', color: 'var(--fg-subtle)' }}>Loading…</div>;
  const save = async (section, q, value) => {
    const d = await call({ module: 'stack', action: 'delegate-answer', section_id: section, question_id: q.id, value });
    setFlash(d.ok ? 'Saved' : (d.message || 'Could not save'));
    setTimeout(() => setFlash(''), 1500);
  };
  const upload = async (section, q, file) => {
    const b64 = await fileToBase64(file);
    const d = await call({ module: 'stack', action: 'delegate-evidence', filename: file.name, content_type: file.type, content_base64: b64, title: file.name });
    if (d.ok) { await call({ module: 'stack', action: 'delegate-answer', section_id: section, question_id: q.id, value: { filename: file.name }, evidence_ids: [d.evidence.id] }); setFlash('Uploaded'); setTimeout(() => setFlash(''), 1500); }
    else setFlash(d.message || 'Upload failed');
  };
  const complete = async () => { const d = await call({ module: 'stack', action: 'delegate-complete' }); if (d.ok) setDone(true); else setFlash(d.message || 'Could not submit'); };
  return (
    <div className="wrap" style={{ maxWidth: 760, padding: '48px var(--s6) 100px' }}>
      <div style={{ marginBottom: 18 }}><Wordmark size={16} /></div>
      <div className="overline" style={{ marginBottom: 8 }}>{data.organization.name} · {data.assessment.title}</div>
      <h1 className="display" style={{ fontSize: 'clamp(24px,4vw,34px)', margin: '0 0 8px' }}>{data.request.to_name ? `${data.request.to_name}, your part of the review` : 'Your part of the review'}</h1>
      <p style={{ color: 'var(--fg-muted)', fontSize: 15, lineHeight: 1.6, margin: '0 0 8px' }}>{data.request.message || 'Please answer what you know. Answers save as you go; submit when you are done.'}</p>
      <p style={{ color: 'var(--fg-subtle)', fontSize: 12.5, margin: '0 0 22px' }}>Only the sections below are open to this link. No account is needed.</p>
      {done ? <div className="flash good">Thank you. Your answers are submitted and the link is now closed.</div> :
      <React.Fragment>
        {data.sections.map((s) =>
        <div key={s.id} className="app-panel" style={{ marginBottom: 14 }}>
          <h3>{s.title}</h3>
          {s.intro && <p style={{ fontSize: 13.5, color: 'var(--fg-muted)', margin: '0 0 12px', lineHeight: 1.6 }}>{s.intro}</p>}
          {s.questions.map((q) => <QuestionInput key={q.id} q={q} initial={(data.responses[s.id + '/' + q.id] || {}).value} onSave={(v) => save(s.id, q, v)} onUpload={(file) => upload(s.id, q, file)} />)}
        </div>)}
        <div style={{ display: 'flex', gap: 10, alignItems: 'center' }}>
          <button className="btn btn-primary" onClick={complete}>Submit my answers</button>
          {flash && <span style={{ fontSize: 13, color: 'var(--fg-muted)' }}>{flash}</span>}
        </div>
      </React.Fragment>}
    </div>);
}

function fileToBase64(file) {
  return new Promise((resolve, reject) => {
    const r = new FileReader();
    r.onload = () => resolve(String(r.result).split(',')[1] || '');
    r.onerror = reject;
    r.readAsDataURL(file);
  });
}

/* One question, any type. Saves on blur / change. */
function QuestionInput({ q, initial, onSave, onUpload, disabled }) {
  const init = initial && typeof initial === 'object' && 'v' in initial ? initial.v : initial;
  const [v, setV] = React.useState(init === undefined || init === null ? (q.type === 'multi' || q.type === 'list' ? [] : q.type === 'bool' ? null : '') : init);
  React.useEffect(() => { const i = initial && typeof initial === 'object' && 'v' in initial ? initial.v : initial; if (i !== undefined && i !== null) setV(i); }, [JSON.stringify(initial)]);
  const commit = (val) => { setV(val); if (!disabled) onSave(val); };
  const label = <div style={{ fontSize: 14.5, fontWeight: 600, marginBottom: 4 }}>{q.text}{q.required && <span style={{ color: 'var(--accent)' }}> *</span>}</div>;
  const help = q.help && <div style={{ fontSize: 12.5, color: 'var(--fg-subtle)', marginBottom: 6 }}>{q.help}</div>;
  if (q.type === 'upload') {
    const name = v && typeof v === 'object' ? v.filename : '';
    return <div className="q-card">{label}{help}
      <div style={{ display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap' }}>
        <input type="file" disabled={disabled} onChange={(e) => { const f = e.target.files && e.target.files[0]; if (f) { onUpload(f); setV({ filename: f.name }); } }} style={{ fontSize: 13, color: 'var(--fg-muted)' }} />
        {name && <span className="chip"><Icon name="doc" size={12} /> {name}</span>}
      </div>
      {q.evidence_hint && <div style={{ fontSize: 12, color: 'var(--fg-subtle)', marginTop: 6 }}>{q.evidence_hint}</div>}
    </div>;
  }
  if (q.type === 'choice') return <div className="q-card">{label}{help}
    <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>{(q.options || []).map((o) =>
      <button key={o} disabled={disabled} className={'app-btn small' + (v === o ? ' primary' : '')} onClick={() => commit(o)}>{o}</button>)}</div></div>;
  if (q.type === 'bool') return <div className="q-card">{label}{help}
    <div style={{ display: 'flex', gap: 6 }}>{[[true, 'Yes'], [false, 'No']].map(([b, l]) => <button key={l} disabled={disabled} className={'app-btn small' + (v === b ? ' primary' : '')} onClick={() => commit(b)}>{l}</button>)}</div></div>;
  if (q.type === 'list' || q.type === 'multi') {
    const text = Array.isArray(v) ? v.join('\n') : String(v || '');
    return <div className="q-card">{label}{help}
      <textarea className="app-input" rows={3} disabled={disabled} defaultValue={text} placeholder="One per line" onBlur={(e) => commit(e.target.value.split('\n').map((x) => x.trim()).filter(Boolean))} /></div>;
  }
  if (q.type === 'number' || q.type === 'money') return <div className="q-card">{label}{help}
    <Input type="number" disabled={disabled} defaultValue={v} onBlur={(e) => commit(e.target.value === '' ? '' : Number(e.target.value))} style={{ maxWidth: 240 }} /></div>;
  return <div className="q-card">{label}{help}
    <textarea className="app-input" rows={q.type === 'text' && String(q.text).length > 60 ? 3 : 2} disabled={disabled} defaultValue={v} onBlur={(e) => commit(e.target.value)} /></div>;
}

Object.assign(window, { lojo, readUserToken, writeUserToken, money, pctOf, shortDate, titleCase, KIND_LABEL, KindPill, Confidence, Effort, Stat, Bar, Drawer, Lines, Field, Group, Input, Select, ViewToggle, SignIn, AppTop, ModulePreview, DelegatePage, QuestionInput, fileToBase64 });
