// Story Weaver — Auto-Weave Studio (the new PageWeaver).
// A playable pipeline that emulates the semantic engine turning raw memories
// into a Playbook: Inbox → Scan → Cluster → Propose → Arc → Draft → Formed.
(function () {
const { useState, useEffect, useRef, useCallback } = React;
const { wash } = LMTXUtil;
const I = window.LMTIcons;
const Page = window.LMTXPage;
const { SCATTER, CANDIDATES, STAGES } = window.WEAVER;
const ev = window.WEAVER.ev;
const WV = window.WV;
const leafFor = LMT.leafFor;

const KEYS = Object.keys(SCATTER); // e01..e12 stable order
const idx = (key) => STAGES.findIndex(s => s.key === key);
const PROPOSE_I = idx('propose'), ARC_I = idx('arc');

// dwell time per stage when auto-playing (ms, before speed)
function stageMs(key, cand) {
  switch (key) {
    case 'inbox':   return 1300;
    case 'scan':    return 2500;
    case 'cluster': return 2100;
    case 'propose': return 2800;
    case 'arc':     return 2200;
    case 'draft':   return cand.draft.length * 820 + 1100;
    default:        return 1500;
  }
}

// smooth path through % points
function smoothPath(pts) {
  if (pts.length < 2) return '';
  let d = `M ${pts[0].x} ${pts[0].y}`;
  for (let i = 1; i < pts.length; i++) {
    const p = pts[i-1], c = pts[i];
    d += ` Q ${p.x} ${p.y} ${(p.x+c.x)/2} ${(p.y+c.y)/2}`;
  }
  const l = pts[pts.length-1];
  d += ` L ${l.x} ${l.y}`;
  return d;
}

// ── compute per-event target position for a stage ───────────────────────────
function positionsFor(stageKey, cand, order) {
  const memberIds = order;
  const memberSet = new Set(memberIds);
  const ignored = KEYS.filter(k => !memberSet.has(k));
  const n = memberIds.length;
  const map = {};
  KEYS.forEach((id) => {
    const sc = SCATTER[id];
    const isMember = memberSet.has(id);
    if (stageKey === 'inbox' || stageKey === 'scan') {
      map[id] = { x: sc.x, y: sc.y, scale: 1, op: 1, role: 'scatter' };
    } else if (stageKey === 'cluster' || stageKey === 'propose') {
      if (isMember) {
        const mi = memberIds.indexOf(id);
        const a = (-90 + mi * (360 / n)) * Math.PI / 180;
        map[id] = { x: 33 + Math.cos(a) * 19, y: 48 + Math.sin(a) * 25, scale: 1.08, op: 1, role: 'cluster' };
      } else {
        const j = ignored.indexOf(id);
        map[id] = { x: 91, y: 12 + j * (76 / Math.max(1, ignored.length - 1)), scale: 0.6, op: 0.85, role: 'ignored' };
      }
    } else { // arc / draft / formed
      if (isMember) {
        const mi = memberIds.indexOf(id);
        const beat = cand.beats.find(b => b.id === id);
        map[id] = { x: 9 + mi * (82 / Math.max(1, n - 1)), y: beat ? beat.h : 45, scale: 1, op: 1, role: 'arc' };
      } else {
        map[id] = { x: 50, y: 50, scale: 0.3, op: 0, role: 'gone' };
      }
    }
  });
  return map;
}

// ── Constellation viz ───────────────────────────────────────────────────────
function Constellation({ stageKey, cand, order, scan, hover, setHover }) {
  const pos = positionsFor(stageKey, cand, order);
  const showCluster = stageKey === 'cluster' || stageKey === 'propose';
  const showArc = stageKey === 'arc' || stageKey === 'draft' || stageKey === 'formed';
  const arcPts = showArc ? order.map(id => ({ x: pos[id].x, y: pos[id].y })) : [];
  const scanIdx = scan - 1; // index in KEYS currently being read

  return (
    <div style={{ position:'relative', height:'clamp(280px, 38vh, 372px)', borderRadius:'calc(var(--radius) - 2px)', overflow:'hidden',
      background:'var(--tree-bg)', border:'1px solid var(--hair)',
      backgroundImage:'radial-gradient(circle at 1px 1px, var(--hair) 1px, transparent 0)', backgroundSize:'22px 22px' }}>
      {/* SVG overlay: links / arc */}
      <svg viewBox="0 0 100 100" preserveAspectRatio="none" style={{ position:'absolute', inset:0, width:'100%', height:'100%', pointerEvents:'none' }}>
        {showCluster && order.map(id => (
          <line key={id} x1={pos[id].x} y1={pos[id].y} x2={33} y2={48} stroke="var(--accent)" strokeWidth="1" vectorEffect="non-scaling-stroke" opacity="0.28"/>
        ))}
        {showCluster && <circle cx="33" cy="48" r="2" fill="var(--accent)" opacity="0.5"/>}
        {showArc && (
          <path d={smoothPath(arcPts)} fill="none" stroke="var(--accent)" strokeWidth="2" vectorEffect="non-scaling-stroke"
            strokeLinecap="round" strokeLinejoin="round" opacity="0.55" style={{ transition:'all .6s' }}/>
        )}
      </svg>

      {/* thread label at cluster centroid */}
      {showCluster && (
        <div className="pop" style={{ position:'absolute', left:'33%', top:'48%', transform:'translate(-50%, calc(-50% - 64px))', zIndex:6, pointerEvents:'none' }}>
          <span className="pill pill-accent" style={{ fontSize:11, boxShadow:'var(--shadow-pop)' }}><I.Branch style={{ fontSize:11 }}/>{cand.title}</span>
        </div>
      )}

      {/* nodes */}
      {KEYS.map((id, i) => {
        const p = pos[id];
        const e = ev(id);
        const reading = stageKey === 'scan' && i === scanIdx;
        const read = stageKey === 'scan' ? i < scan : true;
        const isMember = p.role === 'cluster' || p.role === 'arc';
        const beat = cand.beats.find(b => b.id === id);
        const hovered = hover === id;
        return (
          <div key={id} onMouseEnter={() => setHover(id)} onMouseLeave={() => setHover(null)} title={e.title}
            style={{ position:'absolute', left:`${p.x}%`, top:`${p.y}%`, zIndex: hovered ? 9 : (isMember ? 4 : 2),
              transform:`translate(-50%,-50%) scale(${(p.scale)*(hovered?1.16:1)})`, opacity:p.op,
              transition:'left .7s cubic-bezier(.5,0,.2,1), top .7s cubic-bezier(.5,0,.2,1), opacity .5s ease, transform .3s cubic-bezier(.2,.8,.2,1)',
              filter: (stageKey==='scan' && !read) ? 'grayscale(0.85) opacity(0.5)' : (p.role==='ignored' ? 'grayscale(0.5)' : 'none'),
              cursor:'default' }}>
            {(reading || hovered) && <span style={{ position:'absolute', inset:-9, borderRadius:'50%', border:'2px solid var(--accent)', opacity:0.6 }} className={reading ? 'breathe' : ''}/>}
            <LeafShape color={leafFor(e.daysSince)} size={ isMember ? 30 : 24 } rotate={-20 + (i*23)%40} glow={hovered}/>
            {e.fragile && <span style={{ position:'absolute', top:-1, right:-1, width:7, height:7, borderRadius:'50%', background:'#dc2626', border:'1.5px solid var(--tree-bg)' }}/>}
            {/* arc role + label */}
            {p.role === 'arc' && (
              <div style={{ position:'absolute', left:'50%', top:'100%', transform:'translateX(-50%)', marginTop:7, textAlign:'center', width:104, pointerEvents:'none' }}>
                <div className="mono" style={{ fontSize:8, letterSpacing:'0.06em', textTransform:'uppercase', color:'var(--accent)' }}>{beat?.role}</div>
                <div style={{ fontSize:9.5, color:'var(--muted)', lineHeight:1.2, marginTop:1 }}>{e.title.split(/[—·]/)[0].trim()}</div>
              </div>
            )}
            {/* reading signal chip */}
            {reading && (
              <div className="pop" style={{ position:'absolute', left:'50%', top:-30, transform:'translateX(-50%)', whiteSpace:'nowrap', zIndex:9 }}>
                <span className="mono" style={{ fontSize:9, padding:'2px 7px', borderRadius:6, background:'var(--ink)', color:'var(--bg2)' }}>+{(e.people[0]||e.tags[0]||'signal')}</span>
              </div>
            )}
          </div>
        );
      })}

      {/* stage caption */}
      <div style={{ position:'absolute', left:14, bottom:12, right:14, display:'flex', alignItems:'center', gap:8, pointerEvents:'none' }}>
        <span className="mono" style={{ fontSize:10, color:'var(--faint)', background:'var(--glass)', backdropFilter:'blur(6px)', padding:'4px 9px', borderRadius:7, border:'1px solid var(--hair)' }}>
          {stageKey === 'scan' ? `reading ${Math.min(scan,12)} / 12` :
           showCluster ? `${order.length} linked · ${KEYS.length - order.length} set aside` :
           showArc ? `${order.length} beats on the arc` : '32 memories queued'}
        </span>
      </div>
    </div>
  );
}

// ── Transport bar ───────────────────────────────────────────────────────────
function Transport({ si, playing, onPlay, onStep, onJump, speed, setSpeed, atEnd }) {
  return (
    <div className="card-r" style={{ display:'flex', alignItems:'center', gap:14, padding:'10px 14px', flexWrap:'wrap' }}>
      <div style={{ display:'flex', alignItems:'center', gap:6, flex:'0 0 auto' }}>
        <button onClick={() => onStep(-1)} disabled={si===0} className="icon-btn press" title="Step back" style={{ opacity:si===0?0.35:1 }}><I.ChevronRight style={{ transform:'rotate(180deg)' }}/></button>
        <button onClick={onPlay} className="btn btn-pri press" style={{ width:44, height:44, padding:0, borderRadius:'50%' }} title={playing?'Pause':'Play'}>
          {playing ? <PauseGlyph/> : (atEnd ? <I.Undo style={{ fontSize:18 }}/> : <PlayGlyph/>)}
        </button>
        <button onClick={() => onStep(1)} disabled={atEnd} className="icon-btn press" title="Step forward" style={{ opacity:atEnd?0.35:1 }}><I.ChevronRight/></button>
      </div>
      {/* scrubber */}
      <div style={{ flex:1, minWidth:240, display:'flex', alignItems:'center', position:'relative' }}>
        <div style={{ position:'absolute', left:11, right:11, top:'50%', height:2, background:'var(--hair)', transform:'translateY(-50%)' }}/>
        <div style={{ position:'absolute', left:11, top:'50%', height:2, background:'var(--accent)', transform:'translateY(-50%)', width:`calc((100% - 22px) * ${si/(STAGES.length-1)})`, transition:'width .5s cubic-bezier(.2,.8,.2,1)' }}/>
        <div style={{ display:'flex', justifyContent:'space-between', width:'100%', position:'relative', zIndex:1 }}>
          {STAGES.map((s,i) => {
            const done = i < si, active = i === si;
            return (
              <button key={s.key} onClick={() => onJump(i)} className="press" title={s.label} style={{ display:'flex', flexDirection:'column', alignItems:'center', gap:5, border:'none', background:'none', cursor:'pointer', padding:0 }}>
                <span style={{ width:active?20:14, height:active?20:14, borderRadius:'50%', display:'grid', placeItems:'center',
                  background: (done||active) ? 'var(--accent)' : 'var(--raised)', border:'2px solid', borderColor:(done||active)?'var(--accent)':'var(--edge-strong)',
                  transition:'all .3s', boxShadow: active?'0 0 0 4px var(--accent-wash)':'none' }}>
                  {done && <I.Check style={{ fontSize:9, color:'var(--accent-fg)' }}/>}
                </span>
                <span className="mono" style={{ fontSize:8.5, letterSpacing:'0.04em', textTransform:'uppercase', color: active?'var(--accent)':'var(--faint)', fontWeight:active?700:500 }}>{s.label}</span>
              </button>
            );
          })}
        </div>
      </div>
      <div style={{ display:'flex', gap:3, padding:3, background:'var(--sunken)', borderRadius:9, border:'1px solid var(--hair)', flex:'0 0 auto' }}>
        {[0.5,1,2].map(sp => (
          <button key={sp} onClick={() => setSpeed(sp)} className="press" style={{ padding:'4px 9px', borderRadius:6, border:'none', cursor:'pointer', fontFamily:'JetBrains Mono, monospace', fontSize:11, fontWeight:600,
            background: speed===sp?'var(--raised)':'transparent', color: speed===sp?'var(--ink)':'var(--muted)', boxShadow: speed===sp?'var(--shadow-pop)':'none' }}>{sp}×</button>
        ))}
      </div>
    </div>
  );
}
function PlayGlyph(){return <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M7 5v14l12-7z"/></svg>;}
function PauseGlyph(){return <svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="5" width="4" height="14" rx="1"/><rect x="14" y="5" width="4" height="14" rx="1"/></svg>;}

// ── Engine monitor rail ─────────────────────────────────────────────────────
function EngineRail({ stage, cand, playing, atEnd }) {
  const showConf = ['cluster','propose','arc','draft','formed'].includes(stage.key);
  return (
    <div className="card-r" style={{ padding:'16px 18px' }}>
      <div style={{ display:'flex', alignItems:'center', gap:10, marginBottom:12 }}>
        <span style={{ width:34, height:34, borderRadius:10, background:'linear-gradient(150deg, var(--accent), color-mix(in oklab, var(--accent) 55%, #000))', display:'grid', placeItems:'center', color:'var(--accent-fg)', flex:'0 0 auto' }} className={playing && !atEnd ? 'breathe' : ''}><I.Sparkles style={{ fontSize:17 }}/></span>
        <div style={{ flex:1, minWidth:0 }}>
          <div className="disp" style={{ fontSize:14, fontWeight:600 }}>Weaver engine</div>
          <div style={{ fontSize:11.5, color:'var(--accent)' }}>{stage.verb}</div>
        </div>
        {showConf && <WV.Confidence value={cand.confidence} size={50} label={false}/>}
      </div>
      <p style={{ margin:'0 0 13px', fontSize:12.5, color:'var(--muted)', lineHeight:1.5 }}>{stage.blurb}</p>
      {/* terminal monitor */}
      <div className="mono" style={{ background:'var(--sunken)', border:'1px solid var(--hair)', borderRadius:10, padding:'11px 12px', fontSize:10.5, lineHeight:1.7, color:'var(--muted)', minHeight:96 }}>
        {stage.think.map((t,i) => (
          <div key={stage.key+i} className="fade" style={{ display:'flex', gap:7, animationDelay:`${i*0.12}s` }}>
            <span style={{ color:'var(--accent)', flex:'0 0 auto' }}>›</span>
            <span style={{ color: i===stage.think.length-1 ? 'var(--ink)' : 'var(--muted)' }}>{t}{i===stage.think.length-1 && playing && !atEnd && <span className="breathe" style={{ marginLeft:3 }}>▍</span>}</span>
          </div>
        ))}
      </div>
    </div>
  );
}

// ── API trace (network-inspector) ───────────────────────────────────────────
function ApiTrace({ si }) {
  return (
    <div className="card-r" style={{ padding:'14px 16px' }}>
      <div style={{ display:'flex', alignItems:'center', gap:8, marginBottom:12 }}>
        <span style={{ fontSize:15, color:'var(--accent)' }}><I.Bolt/></span>
        <div className="disp" style={{ fontSize:13.5, fontWeight:600, flex:1 }}>API trace</div>
        <span className="mono" style={{ fontSize:9.5, color:'var(--faint)' }}>weaver.life/api</span>
      </div>
      <div style={{ display:'flex', flexDirection:'column', gap:10 }}>
        {STAGES.map((s,i) => {
          if (i > si) return null;
          const active = i === si;
          return (
            <div key={s.key} style={{ opacity: active ? 1 : 0.6 }}>
              <div className="mono" style={{ fontSize:8.5, letterSpacing:'0.1em', textTransform:'uppercase', color: active?'var(--accent)':'var(--faint)', marginBottom:5, display:'flex', alignItems:'center', gap:6 }}>
                {active && <span className="breathe" style={{ width:5, height:5, borderRadius:'50%', background:'var(--accent)' }}/>}
                {s.label}
              </div>
              <div style={{ display:'flex', flexDirection:'column', gap:5 }}>
                {s.api.map((a,j) => (
                  <div key={j} style={{ marginLeft: a.sub ? 12 : 0 }}>
                    <div style={{ display:'flex', alignItems:'center', gap:7 }}>
                      <WV.MethodBadge m={a.m}/>
                      <span className="mono" style={{ fontSize:10.5, color:'var(--ink)', overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{a.path}</span>
                    </div>
                    {active && (
                      <div style={{ marginTop:4, marginLeft:2 }}>
                        <div style={{ fontSize:11, color:'var(--muted)', lineHeight:1.4, marginBottom: (a.req||a.res)?5:0 }}>{a.note}</div>
                        {a.req && <Pre label="req" body={a.req}/>}
                        {a.res && <Pre label="res" body={a.res}/>}
                      </div>
                    )}
                  </div>
                ))}
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );
}
function Pre({ label, body }) {
  return (
    <div style={{ display:'flex', gap:7, marginBottom:4 }}>
      <span className="mono" style={{ fontSize:8.5, color:'var(--faint)', flex:'0 0 auto', marginTop:3, textTransform:'uppercase', letterSpacing:'0.06em' }}>{label}</span>
      <pre className="mono scroll" style={{ margin:0, flex:1, fontSize:10, lineHeight:1.5, color:'var(--muted)', background:'var(--sunken)', border:'1px solid var(--hair)', borderRadius:7, padding:'7px 9px', overflowX:'auto', whiteSpace:'pre-wrap', wordBreak:'break-word' }}>{body}</pre>
    </div>
  );
}

// ── Main page ───────────────────────────────────────────────────────────────
function PageWeaver({ go, candidate }) {
  const [si, setSi] = useState(0);
  const [playing, setPlaying] = useState(false);
  const [speed, setSpeed] = useState(1);
  const [candId, setCandId] = useState(candidate || 'cand_snow');
  const [accepted, setAccepted] = useState(false);
  const [order, setOrder] = useState([]);
  const [scan, setScan] = useState(0);
  const [draftN, setDraftN] = useState(0);
  const [hover, setHover] = useState(null);
  const [showApi, setShowApi] = useState(true);

  const cand = CANDIDATES.find(c => c.id === candId) || CANDIDATES[0];
  const stage = STAGES[si];
  const atEnd = si === STAGES.length - 1;

  // sync candidate from Tweak
  useEffect(() => { if (candidate && candidate !== candId) setCandId(candidate); }, [candidate]);

  // when candidate changes → reset order + un-accept; bounce back if past propose
  useEffect(() => {
    setOrder(cand.beats.map(b => b.id));
    setAccepted(false);
    setSi(s => s > PROPOSE_I ? PROPOSE_I : s);
  }, [candId]);

  // auto-advance
  useEffect(() => {
    if (!playing) return;
    if (atEnd) { setPlaying(false); return; }
    const t = setTimeout(() => {
      if (stage.key === 'propose') {
        if (cand.belowThreshold) { setPlaying(false); return; }
        if (!accepted) setAccepted(true);
      }
      setSi(s => Math.min(s + 1, STAGES.length - 1));
    }, stageMs(stage.key, cand) / speed);
    return () => clearTimeout(t);
  }, [playing, si, speed, accepted, candId]);

  // scan sweep
  useEffect(() => {
    if (stage.key !== 'scan') return;
    if (!playing) { setScan(12); return; }
    setScan(0);
    const iv = setInterval(() => setScan(s => (s >= 12 ? (clearInterval(iv), 12) : s + 1)), 150 / speed);
    return () => clearInterval(iv);
  }, [stage.key, playing, speed]);

  // draft reveal
  useEffect(() => {
    if (stage.key !== 'draft') return;
    const total = cand.draft.length;
    if (!playing) { setDraftN(total); return; }
    setDraftN(0);
    const iv = setInterval(() => setDraftN(n => (n >= total ? (clearInterval(iv), total) : n + 1)), 800 / speed);
    return () => clearInterval(iv);
  }, [stage.key, playing, speed, candId]);

  const jump = useCallback((i) => {
    setPlaying(false);
    if (i >= ARC_I && !accepted) {
      if (cand.belowThreshold) { i = PROPOSE_I; }
      else setAccepted(true);
    }
    setSi(i);
  }, [accepted, cand]);

  const step = useCallback((d) => jump(Math.max(0, Math.min(STAGES.length - 1, si + d))), [si, jump]);

  const togglePlay = useCallback(() => {
    if (atEnd) { setSi(0); setAccepted(false); setOrder(cand.beats.map(b => b.id)); setPlaying(true); return; }
    setPlaying(p => !p);
  }, [atEnd, cand]);

  const startFromInbox = useCallback(() => { setSi(1); setPlaying(true); }, []);

  const accept = useCallback(() => { setAccepted(true); setSi(ARC_I); }, []);
  const advance = useCallback(() => step(1), [step]);
  const moveBeat = useCallback((i, d) => setOrder(o => {
    const j = i + d; if (j < 0 || j >= o.length) return o;
    const n = o.slice(); [n[i], n[j]] = [n[j], n[i]]; return n;
  }), []);
  const reshuffle = useCallback(() => setOrder(cand.beats.map(b => b.id)), [cand]);
  const replay = useCallback(() => { setSi(0); setAccepted(false); setOrder(cand.beats.map(b => b.id)); setPlaying(false); }, [cand]);

  // which stage detail panel
  let panel = null;
  if (stage.key === 'inbox') panel = <WV.InboxPanel onStart={startFromInbox}/>;
  else if (stage.key === 'scan') panel = <WV.ScanPanel frac={Math.min(scan,12)/12}/>;
  else if (stage.key === 'cluster') panel = <WV.ClusterPanel candId={candId} onPick={setCandId}/>;
  else if (stage.key === 'propose') panel = <WV.ProposePanel cand={cand} accepted={accepted} onAccept={accept} onSelect={setCandId} onAdvance={advance} onHover={setHover}/>;
  else if (stage.key === 'arc') panel = <WV.ArcPanel cand={cand} order={order} onMove={moveBeat} onReshuffle={reshuffle} onAdvance={advance} onHover={setHover}/>;
  else if (stage.key === 'draft') panel = <WV.DraftPanel cand={cand} shown={draftN} total={cand.draft.length} onAdvance={advance} onHover={setHover}/>;
  else if (stage.key === 'formed') panel = <WV.FormedPanel cand={cand} onReplay={replay}/>;

  return (
    <Page max={1320}>
      <PageHead kicker="Create" icon={<I.Branch/>} title="Story Weaver"
        sub="Watch the Weaver turn scattered memories into a Playbook — it links, proposes, arranges, and drafts. You stay in control at every step."
        right={
          <button onClick={() => setShowApi(a => !a)} className={showApi ? 'btn btn-pri press' : 'btn btn-out press'}>
            <I.Bolt style={{ fontSize:15 }}/>{showApi ? 'Hide API' : 'Show API'}
          </button>
        }/>

      <Transport si={si} playing={playing} onPlay={togglePlay} onStep={step} onJump={jump} speed={speed} setSpeed={setSpeed} atEnd={atEnd}/>

      <div style={{ display:'grid', gridTemplateColumns: showApi ? 'minmax(0,1fr) 312px' : 'minmax(0,1fr) 280px', gap:18, alignItems:'start' }} className="lx-weave-grid">
        <div style={{ minWidth:0, display:'flex', flexDirection:'column', gap:16 }}>
          <Constellation stageKey={stage.key} cand={cand} order={order.length ? order : cand.beats.map(b=>b.id)} scan={scan} hover={hover} setHover={setHover}/>
          {panel}
        </div>
        <div style={{ display:'flex', flexDirection:'column', gap:14, position:'sticky', top:0 }}>
          <EngineRail stage={stage} cand={cand} playing={playing} atEnd={atEnd}/>
          {showApi && <ApiTrace si={si}/>}
        </div>
      </div>
    </Page>
  );
}

window.PageWeaver = PageWeaver;
})();
