const { useState, useEffect, useRef } = React;

// ─── API ────────────────────────────────────────────────────────────────────
const API = {
  token: localStorage.getItem('fayztoo.jwt') || '',
  name:  localStorage.getItem('fayztoo.name') || '',
  setAuth(t, n) { this.token=t; this.name=n; localStorage.setItem('fayztoo.jwt',t); localStorage.setItem('fayztoo.name',n); },
  clear() { this.token=''; this.name=''; localStorage.removeItem('fayztoo.jwt'); localStorage.removeItem('fayztoo.name'); },
  async req(path, opts={}) {
    const h={'Content-Type':'application/json',...(opts.headers||{})};
    if(this.token) h['Authorization']=`Bearer ${this.token}`;
    const r=await fetch(path,{...opts,headers:h});
    if(!r.ok){ let err=`HTTP ${r.status}`; try{const j=await r.json();err=j.detail||err;}catch{} throw new Error(err); }
    return r.json();
  }
};
const md = t => !t ? '' : t.replace(/\*\*(.+?)\*\*/g,'<strong>$1</strong>').replace(/(^|[^*])\*([^*]+?)\*/g,'$1<em>$2</em>');
const initials = n => (n||'').split(/\s+/).filter(Boolean).slice(0,2).map(s=>s[0]).join('').toUpperCase() || 'F';
const fmtDate = s => { if(!s) return ''; try{ return new Date(s).toLocaleDateString('en-GB',{day:'numeric',month:'short',year:'numeric'}); } catch{ return s; } };
const fmtTime = s => { if(!s) return ''; try{ return new Date(s).toLocaleTimeString('en-GB',{hour:'2-digit',minute:'2-digit'}); } catch{ return s; } };

// ─── Login ──────────────────────────────────────────────────────────────────
function Login({ onLoggedIn, synthetics }) {
  const [name,setName]=useState(''); const [email,setEmail]=useState('');
  const [agreed,setAgreed]=useState(false);
  const [err,setErr]=useState(''); const [busy,setBusy]=useState(false);

  async function submit(e) {
    e.preventDefault(); setErr(''); setBusy(true);
    try { const r=await API.req('/api/session/start',{method:'POST',body:JSON.stringify({name,email})});
      API.setAuth(r.token,r.name); onLoggedIn(r);
    } catch(e){ setErr(e.message); } finally { setBusy(false); }
  }
  async function loadSynthetic(key) {
    if(!agreed){ setErr('Please tick the acknowledgement first.'); return; }
    setBusy(true); setErr('');
    try { const r=await API.req(`/api/synthetic_members/${key}/load`,{method:'POST'});
      API.setAuth(r.token,r.name); onLoggedIn(r);
    } catch(e){ setErr(e.message); } finally { setBusy(false); }
  }

  return (
    <div className="login-shell">
      <div className="login-card">
        <div className="brand">
          <div className="mark">f</div>
          <span>Fayztoo</span>
          <span className="tag">Wellbeing Companion</span>
        </div>
        <h1>Your Wellbeing Compass for the second half of life</h1>
        <p>A premium, private wellbeing companion. Begin with the Wellbeing Compass across four dimensions — Purpose, Physical, Mental, Connections. Continue with the AI Wellbeing Companion grounded in a knowledge library written by qualified domain experts. Always on, always remembering, always private.</p>

        <div className="non-medical-card">
          <strong>About this service</strong>
          <ul>
            <li>Fayztoo is a <strong>reflective wellbeing companion</strong>, not therapy and not medical advice.</li>
            <li>It does <strong>not diagnose</strong>, prescribe, or replace a qualified clinician.</li>
            <li>For any medical concern, please speak to your GP.</li>
            <li>If you are in distress: <strong>Samaritans 116 123</strong> (UK, free, 24/7). If life is at risk: <strong>999</strong>.</li>
            <li>Your conversations are private. Data is never sold or used to train any foundation model.</li>
          </ul>
          <label className="non-medical-consent">
            <input type="checkbox" checked={agreed} onChange={e=>setAgreed(e.target.checked)} />
            <span>I understand Fayztoo is a reflective companion, not medical advice or therapy.</span>
          </label>
        </div>

        <form onSubmit={submit}>
          <input autoFocus placeholder="Your name" value={name} onChange={e=>setName(e.target.value)} required />
          <input type="email" placeholder="Your email" value={email} onChange={e=>setEmail(e.target.value)} required />
          {err && <div className="banner handoff">{err}</div>}
          <button className="primary" disabled={busy||!name||!email||!agreed}>{busy?'Beginning…':'Begin'}</button>
        </form>
        {synthetics && synthetics.length > 0 && (
          <div style={{marginTop:30,paddingTop:24,borderTop:'1px solid var(--border-soft)'}}>
            <div className="login-divider-label">Or step into a seeded member journey</div>
            <div className="synthetic-grid">
              {synthetics.map(m => (
                <div key={m.key} className="synthetic-card" onClick={()=>loadSynthetic(m.key)}>
                  <div className="synthetic-arch">{m.archetype || 'demo journey'}</div>
                  <div className="synthetic-name">{m.name}</div>
                  <div className="synthetic-role">{m.role}</div>
                </div>
              ))}
            </div>
            <p className="subtle" style={{marginTop:16,fontSize:11.5}}>Each shows the Wellbeing Companion responding to a different pattern. Tick the acknowledgement first.</p>
          </div>
        )}
      </div>
    </div>
  );
}

// ─── Sidebar ────────────────────────────────────────────────────────────────
function Sidebar({ view, setView, hasResults, archetype }) {
  const nav = [
    { id: 'dashboard', label: 'Dashboard', icon: '◆' },
    { id: 'baseline', label: 'Baseline assessment', icon: 'B' },
    ...(hasResults ? [{ id: 'baseline-results', label: 'Your results', icon: '▣' }] : []),
    { id: 'chat', label: 'AI Wellbeing Companion', icon: '◉' },
    { id: 'timeline', label: 'Memory timeline', icon: '◈' },
    { id: 'privacy', label: 'Privacy settings', icon: '🔒' },
    { id: 'admin', label: 'Library & escalations', icon: '☰' },
  ];
  return (
    <aside className="sidebar">
      <div className="sidebar-section">
        <div className="sidebar-label">Member</div>
        {nav.map(n => (
          <div key={n.id} className={`sidebar-item ${view===n.id ? 'active' : ''}`} onClick={()=>setView(n.id)}>
            <div className="sidebar-item-icon">{n.icon}</div>
            <span>{n.label}</span>
          </div>
        ))}
      </div>

      <div className="sidebar-section">
        <div className="sidebar-label">Your Wellbeing Team</div>
        <div className="sidebar-team">
          <div className="team-companion">
            <div className="team-dot pi"></div>
            <div className="team-name">Purpose &amp; Identity</div>
            <div className="team-status active">Active</div>
          </div>
          <div className="team-companion">
            <div className="team-dot mindset"></div>
            <div className="team-name">Mental</div>
            <div className="team-status active">Active</div>
          </div>
          <div className="team-companion">
            <div className="team-dot physical"></div>
            <div className="team-name">Physical</div>
            <div className="team-status active">Active</div>
          </div>
          <div className="team-companion">
            <div className="team-dot connection"></div>
            <div className="team-name">Connections</div>
            <div className="team-status active">Active</div>
          </div>
        </div>
      </div>

      {archetype && (
        <div className="sidebar-cta">
          <strong>Your archetype</strong>
          {archetype.name}
          <div style={{fontSize:11,marginTop:8,color:'var(--text-2)',lineHeight:1.5}}>{archetype.tagline}</div>
        </div>
      )}
    </aside>
  );
}

// ─── Dashboard ──────────────────────────────────────────────────────────────
function Dashboard({ profile, setView }) {
  const baseline = profile?.assessments?.find(a=>a.kind==='baseline');
  const pi = profile?.assessments?.find(a=>a.kind==='pi');
  return (
    <div>
      <h2 className="page-title">Welcome{profile?.name ? `, ${profile.name.split(' ')[0]}` : ''}.</h2>
      <p className="page-subtitle">Your private space for the second half of life. Begin where you are.</p>

      <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:18,marginBottom:22}}>
        <div className="card">
          <h3 className="display">Total Wellbeing Assessment</h3>
          <p className="subtle" style={{marginBottom:18}}>Five minutes, four pillars. Your baseline starts here.</p>
          <button className={baseline ? '' : 'primary'} onClick={()=>setView(baseline?'baseline-results':'baseline')}>
            {baseline ? 'See your baseline' : 'Take the assessment'}
          </button>
        </div>
        <div className="card">
          <h3 className="display">Purpose &amp; Identity, deeper</h3>
          <p className="subtle" style={{marginBottom:18}}>About 15&ndash;20 minutes. Goes beyond the surface to your phase, pattern, and pathway.</p>
          <button className={pi ? '' : 'primary'} onClick={()=>setView(pi?'pathway':'pi')}>
            {pi ? 'See your pathway' : 'Begin the diagnostic'}
          </button>
        </div>
      </div>

      <div className="card">
        <h3 className="display">Open the Wellbeing Companion</h3>
        <p className="subtle" style={{marginBottom:18}}>Reflective conversation, grounded in a knowledge library written by qualified domain experts. Held within the six Constitutional Safety rules. Always private.</p>
        <button className="primary" onClick={()=>setView('chat')}>Open the conversation</button>
      </div>
    </div>
  );
}

// ─── Wellbeing Compass survey ────────────────────────────────────────────────
function WellbeingCompass({ onDone }) {
  const [compass,setCompass]=useState(null);
  const [sectionIdx,setSectionIdx]=useState(0);
  const [answers,setAnswers]=useState({});
  const [busy,setBusy]=useState(false); const [err,setErr]=useState('');

  useEffect(()=>{ API.req('/api/assessment/baseline').then(d=>setCompass(d.compass)).catch(e=>setErr(e.message)); },[]);

  if(!compass) return <div className="subtle" style={{padding:40,textAlign:'center'}}>{err||'Loading…'}</div>;

  const sections = compass.sections;
  const closing = compass.closing;
  const isClosing = sectionIdx === sections.length;
  const section = !isClosing ? sections[sectionIdx] : null;

  const allScoreableIds = sections.flatMap(s => s.items.filter(i=>i.type==='likert').map(i=>i.id));
  const answeredCount = allScoreableIds.filter(id=>answers[id]).length;
  const progress = allScoreableIds.length > 0 ? (answeredCount / allScoreableIds.length) * 100 : 0;

  function setAnswer(id, val) { setAnswers(prev=>({...prev,[id]:val})); }
  function toggleCheckbox(id, opt) {
    const cur = answers[id] || [];
    const next = cur.includes(opt) ? cur.filter(o=>o!==opt) : [...cur, opt];
    setAnswers(prev=>({...prev,[id]:next}));
  }

  function sectionComplete() {
    if(isClosing) return true;
    return section.items.every(item => {
      if(item.type==='likert') return answers[item.id] !== undefined;
      if(item.type==='reflection') return (answers[item.id]||'').trim().length > 0;
      return true;
    });
  }

  async function submit() {
    setBusy(true); setErr('');
    try { const r=await API.req('/api/assessment/baseline',{method:'POST',body:JSON.stringify({answers})}); onDone(r); }
    catch(e){ setErr(e.message); } finally { setBusy(false); }
  }

  function renderItem(item) {
    const score = answers[item.id];
    const showFollowup = item.followup && score !== undefined && score <= 3;
    return (
      <div className="q" key={item.id} style={{marginBottom:28}}>
        {item.type==='reflection' && (
          <>
            <span className="q-prompt" style={{display:'block',marginBottom:8}}>{item.prompt}</span>
            {item.hint && <span className="subtle" style={{display:'block',fontSize:12.5,marginBottom:8,fontStyle:'italic'}}>{item.hint}</span>}
            <textarea
              rows={4}
              style={{width:'100%',boxSizing:'border-box',padding:'10px 12px',border:'1px solid var(--border-soft)',borderRadius:8,fontFamily:'var(--font-ui)',fontSize:14,lineHeight:1.6,color:'var(--ink)',background:'var(--surface)',resize:'vertical'}}
              placeholder="Write your thoughts here…"
              value={answers[item.id]||''}
              onChange={e=>setAnswer(item.id, e.target.value)}
            />
          </>
        )}
        {item.type==='likert' && (
          <>
            <span className="q-prompt" style={{display:'block',marginBottom:10}}>{item.prompt}</span>
            <div className="likert">
              {[1,2,3,4,5,6].map(v=>(
                <button key={v} className={answers[item.id]===v?'selected':''} onClick={()=>setAnswer(item.id,v)}>{v}</button>
              ))}
            </div>
            {item.anchors && (
              <div className="likert-labels">
                <span style={{fontSize:11,color:'var(--text-2)',maxWidth:'38%',lineHeight:1.3}}>{item.anchors[0]}</span>
                <span style={{fontSize:11,color:'var(--text-2)',maxWidth:'38%',lineHeight:1.3,textAlign:'right'}}>{item.anchors[1]}</span>
              </div>
            )}
            {showFollowup && (
              <div style={{marginTop:14,paddingLeft:12,borderLeft:'2px solid var(--oxblood)'}}>
                <span className="subtle" style={{fontSize:12.5,fontStyle:'italic',display:'block',marginBottom:6}}>{item.followup.prompt}</span>
                <textarea
                  rows={2}
                  style={{width:'100%',boxSizing:'border-box',padding:'8px 10px',border:'1px solid var(--border-soft)',borderRadius:6,fontFamily:'var(--font-ui)',fontSize:13,color:'var(--ink)',background:'var(--surface)',resize:'vertical'}}
                  placeholder="Share your thoughts…"
                  value={answers[item.followup.id]||''}
                  onChange={e=>setAnswer(item.followup.id, e.target.value)}
                />
              </div>
            )}
          </>
        )}
        {item.type==='checkbox' && (
          <>
            <span className="q-prompt" style={{display:'block',marginBottom:10}}>{item.prompt}</span>
            <div style={{display:'flex',flexWrap:'wrap',gap:8}}>
              {item.options.map(opt=>{
                const checked=(answers[item.id]||[]).includes(opt);
                return (
                  <button key={opt}
                    onClick={()=>toggleCheckbox(item.id,opt)}
                    style={{
                      padding:'7px 14px',borderRadius:20,fontSize:13,cursor:'pointer',transition:'all 0.15s',
                      background:checked?'var(--oxblood)':'var(--bone)',
                      color:checked?'white':'var(--ink)',
                      border:`1px solid ${checked?'var(--oxblood)':'var(--border-soft)'}`,
                      fontFamily:'var(--font-ui)'
                    }}
                  >{opt}</button>
                );
              })}
            </div>
          </>
        )}
      </div>
    );
  }

  return (
    <div>
      <h2 className="page-title">Your Wellbeing Compass</h2>
      <p className="page-subtitle">Voice or text &middot; approx. 5 minutes &middot; 100% private. There are no right answers. 1 = low &middot; 6 = high.</p>
      <div className="progress-bar"><div className="progress-fill" style={{width:`${progress}%`}} /></div>

      <div style={{display:'flex',gap:6,marginBottom:20,flexWrap:'wrap'}}>
        {sections.map((s,i)=>(
          <div key={s.id} onClick={()=>setSectionIdx(i)} style={{
            padding:'5px 14px',borderRadius:20,fontSize:12.5,cursor:'pointer',fontFamily:'var(--font-ui)',
            background:sectionIdx===i?'var(--oxblood)':'var(--bone)',
            color:sectionIdx===i?'white':'var(--text-2)',
            border:`1px solid ${sectionIdx===i?'var(--oxblood)':'var(--border-soft)'}`,
            fontWeight:sectionIdx===i?600:400,
          }}>{s.name}</div>
        ))}
        <div onClick={()=>setSectionIdx(sections.length)} style={{
          padding:'5px 14px',borderRadius:20,fontSize:12.5,cursor:'pointer',fontFamily:'var(--font-ui)',
          background:isClosing?'var(--oxblood)':'var(--bone)',
          color:isClosing?'white':'var(--text-2)',
          border:`1px solid ${isClosing?'var(--oxblood)':'var(--border-soft)'}`,
          fontWeight:isClosing?600:400,
        }}>Closing</div>
      </div>

      <div className="card">
        {!isClosing && (
          <>
            <div style={{marginBottom:20}}>
              <span className="q-aspect-tag">{section.name}</span>
              <p className="subtle" style={{marginTop:6,fontSize:13.5,lineHeight:1.6}}>{section.description}</p>
            </div>
            {section.items.map(renderItem)}
          </>
        )}
        {isClosing && (
          <>
            <div style={{marginBottom:20}}>
              <h3 className="display">One last question</h3>
              <p className="subtle">Optional — but often the most revealing moment.</p>
            </div>
            {renderItem(closing)}
          </>
        )}
        {err && <div className="banner handoff">{err}</div>}
        <div style={{display:'flex',gap:10,marginTop:16}}>
          {sectionIdx > 0 && (
            <button onClick={()=>setSectionIdx(i=>i-1)} style={{padding:'10px 20px'}}>← Back</button>
          )}
          {!isClosing && (
            <button className="primary" style={{flex:1,padding:10}} onClick={()=>setSectionIdx(i=>i+1)}>
              Next: {sectionIdx < sections.length-1 ? sections[sectionIdx+1].name : 'Closing'} →
            </button>
          )}
          {isClosing && (
            <button className="primary" style={{flex:1,padding:12}} onClick={submit} disabled={busy||answeredCount===0}>
              {busy?'Reading your answers…':'See your Wellbeing Compass results'}
            </button>
          )}
        </div>
      </div>
    </div>
  );
}

// ─── Radar ──────────────────────────────────────────────────────────────────
function Radar({ aspects }) {
  const aspectOrder=['purpose','physical','mental','connections'];
  const cx=160, cy=160, R=110;
  const pts=aspectOrder.map((name,i)=>{
    const angle=-Math.PI/2 + i*(2*Math.PI/aspectOrder.length);
    const v=aspects[name]||0;
    const r=R*(v/6);
    return { name, value: v, x: cx+r*Math.cos(angle), y: cy+r*Math.sin(angle), lx: cx+(R+24)*Math.cos(angle), ly: cy+(R+24)*Math.sin(angle), angle };
  });
  const polyPoints=pts.map(p=>`${p.x},${p.y}`).join(' ');
  return (
    <div className="radar-wrap">
      <svg className="radar" viewBox="0 0 320 320" xmlns="http://www.w3.org/2000/svg">
        {[1,2,3,4,6].map(i=><polygon key={i} className="radar-grid" points={aspectOrder.map((_,j)=>{const a=-Math.PI/2+j*(2*Math.PI/aspectOrder.length);const r=R*(i/6);return `${cx+r*Math.cos(a)},${cy+r*Math.sin(a)}`;}).join(' ')} />)}
        {aspectOrder.map((_,i)=>{const a=-Math.PI/2+i*(2*Math.PI/aspectOrder.length);return <line key={i} className="radar-axis" x1={cx} y1={cy} x2={cx+R*Math.cos(a)} y2={cy+R*Math.sin(a)} />;})}
        <polygon className="radar-poly" points={polyPoints} />
        {pts.map(p => <circle key={p.name} className="radar-dot" cx={p.x} cy={p.y} r="4" />)}
        {pts.map(p => (
          <g key={p.name}>
            <text className="radar-label" x={p.lx} y={p.ly-6} textAnchor="middle">{{purpose:'Purpose',physical:'Physical',mental:'Mental',connections:'Connections'}[p.name]||p.name}</text>
            <text className="radar-value" x={p.lx} y={p.ly+10} textAnchor="middle">{p.value.toFixed(1)}</text>
          </g>
        ))}
      </svg>
    </div>
  );
}

// ─── Baseline results ──────────────────────────────────────────────────────
function BaselineResults({ data, onContinue, onChat }) {
  const phaseNames={pre_transition:"Pre-transition",ending:"The Ending",neutral_zone:"Neutral Zone",consolidation:"Consolidation"};
  return (
    <div>
      <h2 className="page-title">Your Wellbeing Compass</h2>
      <p className="page-subtitle">Your reflection across four dimensions. 1 = low &middot; 6 = high. The Wellbeing Companion will use this to ground every conversation.</p>

      <div className="card">
        <h3 className="display">Your four dimensions</h3>
        <Radar aspects={data.aspect_means} />
      </div>

      {data.archetype && (
        <div className="card">
          <h3 className="display">Your pattern</h3>
          <div style={{display:'flex',alignItems:'center',gap:14,marginBottom:14}}>
            <div className="user-chip-avatar" style={{width:40,height:40,fontSize:14}}>{initials(data.archetype.name.replace('The ',''))}</div>
            <div>
              <div style={{fontFamily:'var(--font-display)',fontSize:22,fontWeight:500,letterSpacing:'-0.2px'}}>{data.archetype.name}</div>
              <div className="subtle">{data.archetype.tagline}</div>
            </div>
          </div>
          <p>{data.archetype.description}</p>
        </div>
      )}

      <div className="card">
        <h3 className="display">What your answers say</h3>
        <div className="insights">
          {data.insights.map((i,idx)=>(
            <div className={`insight ${i.tone==='warning'?'warning':i.tone==='somatic'?'somatic':i.tone==='pattern'?'pattern':i.tone==='mindset'?'mindset':''}`} key={idx}>
              <div className="tone-label">{i.tone}</div>
              <div dangerouslySetInnerHTML={{__html:md(i.text)}} />
            </div>
          ))}
        </div>
      </div>

      <button className="primary" style={{width:'100%',padding:14}} onClick={onChat}>Open the Wellbeing Companion</button>
    </div>
  );
}

function ChunkPill({ id }) {
  const [data,setData]=useState(null);
  useEffect(()=>{ API.req(`/api/chunks/${id}`).then(setData).catch(()=>{}); },[id]);
  if(!data) return <span className="chunk-pill">{id}</span>;
  const statusLabel=data.status==='practitioner_voice'?'real voice':data.status==='practitioner_voice_synthetic'?'synthetic voice':'stub';
  const cls=data.status==='practitioner_voice'?'real':data.status==='practitioner_voice_synthetic'?'synthetic':'stub';
  return (
    <div className={`chunk-pill ${cls}`}>
      <code>{id}</code>
      <span className="chunk-pill-title">{data.title}</span>
      <span className="chunk-pill-status">{statusLabel}</span>
    </div>
  );
}

// ─── Chat (with voice-first capture) ───────────────────────────────────────
function Chat() {
  const [messages,setMessages]=useState([]); const [input,setInput]=useState('');
  const [busy,setBusy]=useState(false); const [err,setErr]=useState('');
  const [listening,setListening]=useState(false);
  const scroller=useRef(null);
  const recognitionRef=useRef(null);

  useEffect(()=>{ API.req('/api/chat/history').then(d=>setMessages(d.messages||[])); },[]);
  useEffect(()=>{ if(scroller.current) scroller.current.scrollTop=scroller.current.scrollHeight; },[messages]);

  // Voice setup
  const hasSpeech = !!(window.SpeechRecognition || window.webkitSpeechRecognition);
  function toggleVoice() {
    if(!hasSpeech) return;
    if(listening) {
      recognitionRef.current?.stop(); setListening(false); return;
    }
    const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
    const r = new SR();
    r.lang='en-GB'; r.continuous=false; r.interimResults=false;
    r.onresult = e => {
      const t=e.results[0][0].transcript;
      setInput(prev => (prev ? prev+' '+t : t).trim());
    };
    r.onend = () => setListening(false);
    r.onerror = () => setListening(false);
    recognitionRef.current=r;
    r.start(); setListening(true);
  }

  async function send() { if(!input.trim()||busy) return;
    const msg=input.trim(); setInput(''); setErr('');
    setMessages(m=>[...m,{role:'user',content:msg}]); setBusy(true);
    try{ const r=await API.req('/api/chat',{method:'POST',body:JSON.stringify({message:msg})});
      setMessages(m=>[...m,{role:'assistant',content:r.reply,retrieved_chunks:r.retrieved_chunks.map(c=>c.chunk_id)}]);
      if(r.escalation) setErr(`Constitutional Safety: ${r.escalation.kind} (${r.escalation.severity}) detected. A real person would be the right channel for this.`);
    } catch(e){ setErr(e.message); } finally { setBusy(false); } }
  async function resetChat() { if(!confirm('Clear this conversation?')) return;
    await API.req('/api/chat/reset',{method:'POST'}); setMessages([]); }
  function keyDown(e) { if(e.key==='Enter' && !e.shiftKey) { e.preventDefault(); send(); } }

  return (
    <div>
      <div style={{display:'flex',justifyContent:'space-between',alignItems:'baseline'}}>
        <div>
          <h2 className="page-title">Your Wellbeing Companion</h2>
          <p className="page-subtitle">Reflective conversation, not therapy. Held within the Constitutional Safety framework. Always private.</p>
        </div>
        <button onClick={resetChat} className="subtle-btn">Clear conversation</button>
      </div>
      {err && <div className="banner handoff">{err}</div>}
      <div className="chat-companion-strip">
        <div className="chat-companion-avatar">◉</div>
        <div>
          <div className="chat-companion-name">Wellbeing Companion</div>
          <div className="chat-companion-role">Grounded in 14 chunks of qualified domain knowledge across all four wellbeing dimensions.</div>
        </div>
      </div>
      <div className="chat">
        <div className="chat-messages" ref={scroller}>
          {messages.length===0 && <div className="subtle" style={{textAlign:'center',padding:40,fontStyle:'italic'}}>"I'm stuck and I don't know why" is a valid start. Open with whatever's on top.</div>}
          {messages.map((m,i)=>(
            <div className={`msg ${m.role}`} key={i}>
              <div>
                <div className="msg-bubble" dangerouslySetInnerHTML={{__html:md(m.content).replace(/\n/g,'<br/>')}} />
                {m.role==='assistant' && m.retrieved_chunks && m.retrieved_chunks.length>0 && (
                  <div className="msg-chunks">drawn from: {m.retrieved_chunks.map(c=><code key={c}>{c}</code>)}</div>
                )}
              </div>
            </div>
          ))}
          {busy && <div className="msg assistant"><div className="msg-bubble subtle">…</div></div>}
        </div>
        <div className="chat-input">
          <textarea value={input} onChange={e=>setInput(e.target.value)} onKeyDown={keyDown} placeholder={listening ? '🎙 Listening…' : "What's on top right now?"} rows={2} />
          <div style={{display:'flex',flexDirection:'column',gap:6}}>
            {hasSpeech && (
              <button
                onClick={toggleVoice}
                title={listening ? 'Stop listening' : 'Speak your message'}
                style={{
                  background: listening ? 'var(--oxblood)' : 'var(--bone)',
                  color: listening ? 'white' : 'var(--ink)',
                  border: '1px solid var(--border-soft)',
                  borderRadius: 6,
                  padding: '7px 10px',
                  cursor: 'pointer',
                  fontSize: 16,
                  lineHeight: 1,
                  transition: 'background 0.2s',
                }}
              >{listening ? '⏹' : '🎙'}</button>
            )}
            <button className="primary" onClick={send} disabled={busy||!input.trim()}>Send</button>
          </div>
        </div>
      </div>
      <p className="subtle" style={{marginTop:14,textAlign:'center'}}>
        Reflective companion only &mdash; not medical advice, not therapy. For anything medical, your GP. In a crisis, Samaritans 116 123 (UK, 24/7) or 999.
      </p>
    </div>
  );
}

// ─── Memory Timeline (Diamond Age memory view) ─────────────────────────────
function Timeline() {
  const [items,setItems]=useState(null); const [err,setErr]=useState('');
  const [expandedConvs,setExpandedConvs]=useState(new Set());
  useEffect(()=>{
    API.req('/api/chat/timeline')
      .then(d=>setItems(d.timeline||[]))
      .catch(e=>setErr(e.message));
  },[]);

  function toggleConv(id) {
    setExpandedConvs(prev => {
      const next = new Set(prev);
      next.has(id) ? next.delete(id) : next.add(id);
      return next;
    });
  }

  const aspectColours = {purpose:'var(--oxblood)',physical:'#6B7E99',mental:'#5E7A6B',connections:'#8B6B5E'};

  return (
    <div>
      <h2 className="page-title">Memory timeline</h2>
      <p className="page-subtitle">
        Your full wellbeing history in one view &mdash; every assessment milestone, every conversation, ordered as they happened.
        This is the Diamond Age memory: the Companion carries all of this into every exchange.
      </p>

      {err && <div className="banner handoff">{err}</div>}

      {items === null && !err && (
        <div className="subtle" style={{textAlign:'center',padding:40}}>Loading your timeline…</div>
      )}

      {items && items.length === 0 && (
        <div className="card">
          <p className="subtle" style={{textAlign:'center',padding:24}}>
            Nothing here yet. Complete the baseline assessment or open a conversation with the Companion — your history will appear here.
          </p>
        </div>
      )}

      {items && items.length > 0 && (
        <div className="timeline-feed">
          {items.map((item, idx) => {
            if(item.kind === 'assessment') {
              const subkind = item.subkind === 'baseline' ? 'Wellbeing Compass' : 'Assessment';
              const scores = item.scores || {};
              const scoreEntries = Object.entries(scores).filter(([k])=>k!=='overall');
              return (
                <div key={`assessment-${idx}`} className="timeline-item timeline-assessment">
                  <div className="timeline-marker assessment-marker">A</div>
                  <div className="timeline-card">
                    <div className="timeline-meta">
                      <span className="timeline-kind-label">{subkind}</span>
                      <span className="timeline-date">{fmtDate(item.created_at)}</span>
                    </div>
                    {scoreEntries.length > 0 && (
                      <div className="timeline-scores">
                        {scoreEntries.map(([aspect, score])=>(
                          <div key={aspect} className="timeline-score-pill" style={{borderColor: aspectColours[aspect]||'var(--hairline)'}}>
                            <span className="timeline-score-aspect" style={{color: aspectColours[aspect]||'var(--text-2)'}}>{aspect}</span>
                            <span className="timeline-score-val">{typeof score==='number'?score.toFixed(1):score}</span>
                          </div>
                        ))}
                      </div>
                    )}
                    {item.insights && item.insights.length > 0 && (
                      <div className="timeline-insights">
                        {item.insights.slice(0,3).map((ins,i)=>(
                          <div key={i} className="timeline-insight-line">
                            <span className="timeline-insight-tone">{ins.tone}</span>
                            <span className="timeline-insight-text" dangerouslySetInnerHTML={{__html:md(ins.text)}} />
                          </div>
                        ))}
                        {item.insights.length > 3 && <div className="subtle" style={{fontSize:11.5,marginTop:4}}>+{item.insights.length-3} more insights</div>}
                      </div>
                    )}
                  </div>
                </div>
              );
            }

            if(item.kind === 'conversation') {
              const msgs = item.messages || [];
              const firstUser = msgs.find(m=>m.role==='user');
              const isExpanded = expandedConvs.has(item.conversation_id);
              return (
                <div key={`conv-${item.conversation_id}`} className="timeline-item timeline-conversation">
                  <div className="timeline-marker conv-marker">C</div>
                  <div className="timeline-card">
                    <div className="timeline-meta">
                      <span className="timeline-kind-label">Conversation</span>
                      <span className="timeline-date">{fmtDate(item.started_at)} · {fmtTime(item.started_at)}</span>
                      <span className="timeline-turn-count">{item.turn_count} {item.turn_count===1?'exchange':'exchanges'}</span>
                    </div>
                    {firstUser && (
                      <div className="timeline-opening">
                        <span className="timeline-opening-label">Opened with</span>
                        <span className="timeline-opening-text">{firstUser.content.length>120 ? firstUser.content.slice(0,120)+'…' : firstUser.content}</span>
                      </div>
                    )}
                    <button
                      className="subtle-btn"
                      style={{marginTop:8,fontSize:11.5}}
                      onClick={()=>toggleConv(item.conversation_id)}
                    >
                      {isExpanded ? 'Hide conversation ↑' : `Read full conversation (${Math.floor(item.turn_count/2)} exchanges) ↓`}
                    </button>
                    {isExpanded && (
                      <div className="timeline-conv-expanded">
                        {msgs.map((m,mi)=>(
                          <div key={mi} className={`timeline-msg ${m.role}`}>
                            <div className="timeline-msg-role">{m.role==='user'?'You':'Companion'}</div>
                            <div className="timeline-msg-content" dangerouslySetInnerHTML={{__html:md(m.content).replace(/\n/g,'<br/>')}} />
                            <div className="timeline-msg-time">{fmtTime(m.created_at)}</div>
                          </div>
                        ))}
                      </div>
                    )}
                  </div>
                </div>
              );
            }
            return null;
          })}
        </div>
      )}

      {/* Timeline styles live in app.css */}
    </div>
  );
}

// ─── Privacy Settings ─────────────────────────────────────────────────────
function PrivacySettings() {
  const [tier, setTier] = useState(null);
  const [saving, setSaving] = useState(false);
  const [saved, setSaved] = useState(false);
  const [err, setErr] = useState('');

  useEffect(()=>{
    API.req('/api/privacy').then(d=>setTier(d.privacy_tier)).catch(e=>setErr(e.message));
  },[]);

  async function choose(t) {
    setSaving(true); setSaved(false); setErr('');
    try {
      await API.req('/api/privacy',{method:'POST',body:JSON.stringify({privacy_tier:t})});
      setTier(t); setSaved(true); setTimeout(()=>setSaved(false),2500);
    } catch(e){ setErr(e.message); } finally { setSaving(false); }
  }

  const tiers = [
    {
      id: 'fully_private',
      label: 'Fully Private',
      icon: '🔒',
      desc: 'Your data lives only in this session. No human eyes, ever. The Companion works from your conversation history alone. No practitioner can access your record.',
      note: 'You lose the ability to escalate to a human Wellbeing Guide with context. The Companion will still refer you to external resources if needed.',
    },
    {
      id: 'protected',
      label: 'Protected',
      icon: '🛡',
      desc: 'Default. Your conversations are private, encrypted, and never shared. Only in a constitutionally-triggered escalation event does a qualified practitioner see anything — and only the relevant excerpts, with your awareness.',
      note: 'This is the recommended setting. It preserves full privacy while keeping the human safety net intact.',
    },
    {
      id: 'expert_handoff',
      label: 'Expert Handoff',
      icon: '🤝',
      desc: 'You actively want a qualified Wellbeing Guide to review your pattern and be available for direct consultation. Your full assessment and conversation history is shared with the practitioner you choose.',
      note: "Use this when you're ready to work with a human Guide. You can switch back to Protected at any time.",
    },
  ];

  return (
    <div>
      <h2 className="page-title">Privacy settings</h2>
      <p className="page-subtitle">Three tiers. You decide how much human involvement you want. You can change this at any time.</p>

      {err && <div className="banner handoff">{err}</div>}
      {saved && <div className="banner info">Privacy setting updated.</div>}

      {tier === null && !err && <div className="subtle" style={{padding:20}}>Loading…</div>}

      {tier !== null && (
        <div style={{display:'flex',flexDirection:'column',gap:16}}>
          {tiers.map(t => (
            <div
              key={t.id}
              onClick={()=>!saving && choose(t.id)}
              style={{
                border: `2px solid ${tier===t.id ? 'var(--oxblood)' : 'var(--border-soft)'}`,
                borderRadius: 10,
                padding: '18px 20px',
                cursor: saving ? 'not-allowed' : 'pointer',
                background: tier===t.id ? 'var(--bone)' : 'var(--surface)',
                transition: 'all 0.15s',
              }}
            >
              <div style={{display:'flex',alignItems:'center',gap:12,marginBottom:8}}>
                <span style={{fontSize:22}}>{t.icon}</span>
                <div>
                  <div style={{fontFamily:'var(--font-display)',fontSize:17,fontWeight:500,color:'var(--ink)'}}>{t.label}</div>
                  {tier===t.id && <div style={{fontFamily:'var(--font-ui)',fontSize:10,fontWeight:700,color:'var(--oxblood)',textTransform:'uppercase',letterSpacing:'0.06em',marginTop:2}}>Current setting</div>}
                </div>
              </div>
              <p style={{margin:'0 0 6px',fontSize:13.5,lineHeight:1.6,color:'var(--ink)'}}>{t.desc}</p>
              <p style={{margin:0,fontSize:12,color:'var(--text-2)',fontStyle:'italic',lineHeight:1.5}}>{t.note}</p>
            </div>
          ))}
        </div>
      )}

      <div className="card" style={{marginTop:24}}>
        <h3 className="display" style={{fontSize:13}}>Our data promise</h3>
        <p className="subtle" style={{fontSize:12.5,lineHeight:1.6,margin:0}}>
          Your conversations are stored only on Fayztoo infrastructure. They are never used to train any foundation model.
          They are never sold. Under no setting does Fayztoo share data with third parties, advertisers, or researchers.
          In an Expert Handoff setting, your data is shared only with the qualified practitioner you have chosen — not with Fayztoo staff.
        </p>
      </div>
    </div>
  );
}

// ─── Admin (chunks library, escalations, constitutional rules) ────────────
function Admin() {
  const [tab,setTab]=useState('chunks');
  const [chunks,setChunks]=useState([]); const [selected,setSelected]=useState(null);
  const [escalations,setEscalations]=useState([]);
  useEffect(()=>{ API.req('/api/chunks').then(d=>setChunks(d.chunks)); API.req('/api/escalations').then(d=>setEscalations(d.escalations)); },[]);

  const rules = [
    { name:'Self-Harm Prohibition', text:'Absolute. No response leads toward self-harm or thoughts of not being here. Fires before any other consideration. Cannot be member-overridden, prompt-overridden, or context-adjusted.', severity:'high' },
    { name:'Clinical Impersonation Prohibition', text:'The Wellbeing Companion is not a therapist, psychiatrist, or doctor. Cannot diagnose, prescribe, or treat. When conversation moves into clinical territory, the rule triggers transparent acknowledgement and routing to a qualified professional.', severity:'high' },
    { name:'Dependency Prevention', text:'The Companion actively monitors for signs the member is substituting it for human connection. Excessive contact patterns or avoidance of real-world engagement trigger gentle reorientation toward real-person channels.', severity:'medium' },
    { name:'Manipulation Prohibition', text:'No psychological pressure, urgency framing, or reward/punishment dynamics. Offers perspective; does not manufacture motivation.', severity:'medium' },
    { name:'Scope Boundary', text:'Questions outside the domain are not answered with improvisation. Legal, medical, financial, politically or religiously contentious questions trigger the scope boundary and route to qualified professionals.', severity:'medium' },
    { name:'Escalation Protocol', text:'When acute distress signals are detected, the protocol fires: specific resources, warm handoff to a real person, no continuation as routine.', severity:'high' },
  ];

  return (
    <div>
      <h2 className="page-title">Library &amp; safety</h2>
      <p className="page-subtitle">Behind the member surface: the knowledge corpus, escalation events, and the Constitutional Safety framework.</p>
      <div className="nav-pills">
        <button className={`nav-pill ${tab==='chunks'?'active':''}`} onClick={()=>setTab('chunks')}>Chunks library ({chunks.length})</button>
        <button className={`nav-pill ${tab==='escalations'?'active':''}`} onClick={()=>setTab('escalations')}>Escalations ({escalations.length})</button>
        <button className={`nav-pill ${tab==='safety'?'active':''}`} onClick={()=>setTab('safety')}>Constitutional Safety</button>
      </div>

      {tab==='chunks' && (
        <div className="admin-grid">
          <div className="admin-list">
            {chunks.map(c=>(
              <div key={c.chunk_id} className={`admin-list-item ${selected?.chunk_id===c.chunk_id?'active':''}`} onClick={()=>API.req(`/api/chunks/${c.chunk_id}`).then(setSelected)}>
                <div className="admin-item-id">{c.chunk_id}</div>
                <div className="admin-item-title">{c.title}</div>
                <div className={`admin-item-status ${c.status}`}>{c.status==='practitioner_voice'?'REAL':c.status==='practitioner_voice_synthetic'?'SYNTHETIC':'STUB'}</div>
              </div>
            ))}
          </div>
          <div className="admin-detail">
            {selected ? <>
              <h3>{selected.chunk_id} &mdash; {selected.title}</h3>
              <div className={`admin-item-status ${selected.status}`} style={{display:'inline-block',marginBottom:12}}>
                {selected.status==='practitioner_voice'?"REAL — Sarah's exemplar":selected.status==='practitioner_voice_synthetic'?'SYNTHETIC — awaiting domain expert review':'STUB'}
              </div>
              {selected.assessment_link && <div className="admin-meta"><div className="admin-meta-label">Assessment link</div>{selected.assessment_link}</div>}
              {selected.cross_pillar && <div className="admin-meta"><div className="admin-meta-label">Cross-pillar</div>{selected.cross_pillar}</div>}
              {selected.escalation_trigger && <div className="admin-meta escalation"><div className="admin-meta-label">Escalation rule</div>{selected.escalation_trigger}</div>}
              <div className="admin-body" dangerouslySetInnerHTML={{__html: selected.body
                .replace(/\n\n/g,'<p></p>')
                .replace(/\n/g,'<br/>')
                .replace(/\*\*(.+?)\*\*/g,'<strong>$1</strong>')
                .replace(/^### (.+)$/gm,'<h4>$1</h4>')
                .replace(/^## (.+)$/gm,'<h3>$1</h3>')}} />
            </> : <p className="subtle">Select a chunk on the left to read it fully.</p>}
          </div>
        </div>
      )}

      {tab==='escalations' && (
        <div className="card">
          <h3>Escalation log</h3>
          {escalations.length===0 ? <p className="subtle">No escalations logged yet. Escalations fire when the diagnostic detects patterns like the decision-trap, or when inbound chat trips a constitutional safety rule.</p> : (
            <div className="escalation-list">
              {escalations.map(e=>(
                <div key={e.id} className={`escalation-item sev-${e.severity}`}>
                  <div className="escalation-head">
                    <span className="escalation-kind">{e.kind.replace(/_/g,' ')}</span>
                    <span className="escalation-sev">{e.severity}</span>
                    <span className="escalation-date">{new Date(e.created_at).toLocaleString()}</span>
                  </div>
                  <div className="escalation-text">{e.trigger_text}</div>
                </div>
              ))}
            </div>
          )}
        </div>
      )}

      {tab==='safety' && (
        <div className="card">
          <h3 className="display">The six Constitutional Safety rules</h3>
          <p className="subtle" style={{marginBottom:18}}>Architectural, not rhetorical. Evaluated before every Companion response. Cannot be overridden by member request, prompt, or context.</p>
          <div className="insights">
            {rules.map((r,i)=>(
              <div key={i} className={`insight ${r.severity==='high'?'warning':''}`}>
                <div className="tone-label">Rule {i+1} &mdash; {r.severity} severity</div>
                <strong>{r.name}</strong>
                <div style={{marginTop:6}}>{r.text}</div>
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
}

// ─── Root ──────────────────────────────────────────────────────────────────
function App() {
  const [user,setUser]=useState(API.token ? { name: API.name } : null);
  const [view,setView]=useState('dashboard');
  const [profile,setProfile]=useState(null);
  const [baselineResult,setBaselineResult]=useState(null);
  const [_pathway,_setPathway]=useState(null);
  const [synthetics,setSynthetics]=useState([]);

  useEffect(()=>{ API.req('/api/synthetic_members').then(d=>setSynthetics(d.members||[])); },[]);

  useEffect(()=>{
    if(user && API.token) {
      API.req('/api/profile').then(p=>{
        setProfile(p);
        const baseline=p.assessments?.find(a=>a.kind==='baseline');
        if(baseline) {
          setBaselineResult({
            aspect_means: baseline.scores,
            section_means: baseline.scores,
            insights: baseline.insights,
            archetype: null
          });
          setView('chat');
        }
      }).catch(()=>{ API.clear(); setUser(null); });
    }
  },[user && user.name]);

  if(!user) return <Login onLoggedIn={u=>{setUser(u); setView('dashboard');}} synthetics={synthetics} />;
  function logout() { API.clear(); setUser(null); setView('dashboard'); setProfile(null); setBaselineResult(null); setPathway(null); }

  return (
    <div className="app">
      <div className="header">
        <div className="brand">
          <div className="mark">f</div>
          <span>Fayztoo</span>
          <span className="tag">Wellbeing Companion</span>
          <span className="demo-badge">Working demo</span>
        </div>
        <div style={{display:'flex',alignItems:'center',gap:12}}>
          <div className="user-chip">
            <div className="user-chip-avatar">{initials(user.name)}</div>
            <span>{user.name}</span>
          </div>
          <button className="ghost" onClick={logout}>Log out</button>
        </div>
      </div>
      <div className="non-medical-strip">
        <strong>Reflective wellbeing only.</strong> Not therapy, not medical advice. For anything medical, your GP. In a crisis, Samaritans <a href="tel:116123">116 123</a> (UK, 24/7), or 999 if life is at risk.
      </div>
      <Sidebar view={view} setView={setView} hasResults={!!baselineResult} archetype={baselineResult?.archetype || profile?.assessments?.find(a=>a.kind==='baseline')?.archetype} />
      <div className="main">
        {view==='dashboard' && <Dashboard profile={profile} setView={setView} />}
        {view==='baseline' && <WellbeingCompass onDone={r=>{setBaselineResult(r); setView('baseline-results');}} />}
        {view==='baseline-results' && baselineResult && <BaselineResults data={baselineResult} onContinue={()=>setView('pi')} onChat={()=>setView('chat')} />}

        {view==='chat' && <Chat />}
        {view==='timeline' && <Timeline />}
        {view==='privacy' && <PrivacySettings />}
        {view==='admin' && <Admin />}
      </div>
      <div className="footer">
        <span>Fayztoo · 14 chunks (1 Sarah exemplar · 13 synthetic awaiting review) · Wellbeing Compass: Purpose · Physical · Mental · Connections</span>
        <span>Claude Sonnet 4.5 · sentence-transformers · FastAPI · Constitutional Safety v1</span>
      </div>
    </div>
  );
}

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