// scenes-9.jsx — "The window's edges" + "Generating the answer (decode + reasoning)"

// ════════════════════════════════════════════════════════════════════════════
// SCENE — THE WINDOW'S EDGES  (length 18s)
// ════════════════════════════════════════════════════════════════════════════
function SceneWindowEdges() {
  const { localTime: lt } = useSprite();
  const setup = ramp(lt, 0.2, 1.0);

  // window frame
  const fx = 560, fw = 460, fTop = 200, fh = 600;
  const limitY = fTop + 40;                       // limit line just inside top

  // blocks stack from bottom up; each ~ height. capacity ~ 6 blocks to limit.
  const bh = 78, bgap = 10;
  const labels = ['system + tools', 'turn 1', 'turn 2', 'turn 3', 'turn 4', 'turn 5', 'turn 6', 'turn 7'];
  const cols = [COLORS.inkDim, COLORS.blue, COLORS.green, COLORS.blue, COLORS.green, COLORS.blue, COLORS.green, COLORS.blue];

  // how many blocks have arrived
  const arriveAt = (i) => 1.6 + i*0.62;
  const nArrived = labels.filter((_,i)=> lt > arriveAt(i)).length;

  // overflow begins ~7.4 : oldest (bottom-most = index0) starts dropping
  const overflow = lt > 7.4 && lt < 11.0;
  const compact = lt > 11.0;                       // collapse oldest 3 into summary

  // visible set
  let visible;
  if (compact) {
    visible = [{ label:'summary of 1–3', col:COLORS.purple, summary:true }, ...labels.slice(3).map((l,i)=>({label:l,col:cols[i+3]}))];
  } else if (overflow) {
    const drop = clamp(Math.floor((lt-7.4)/1.2)+1, 0, 2);   // drop up to 2 oldest
    visible = labels.slice(drop, nArrived).map((l,i)=>({label:l,col:cols[i+drop]}));
  } else {
    visible = labels.slice(0, nArrived).map((l,i)=>({label:l,col:cols[i]}));
  }

  // stack from bottom (fTop+fh) upward
  const baseY = fTop + fh - 16;

  return (
    <>
      <Bg accent={COLORS.coral} />
      <Eyebrow lt={lt} a={0.4} b={18} n="07" label="The window has edges" color={COLORS.coral} />

      {/* frame */}
      <div style={{ position:'absolute', left:fx, top:fTop, width:fw, height:fh, opacity:setup,
        border:`2px solid ${COLORS.inkFaint}`, borderRadius:16, background:'rgba(255,255,255,0.015)' }} />
      {/* limit line */}
      <div style={{ position:'absolute', left:fx-6, top:limitY, width:fw+12, height:0,
        borderTop:`2.5px dashed ${COLORS.coral}`, opacity:setup }} />
      <div style={{ position:'absolute', left:fx+fw+18, top:limitY, transform:'translateY(-50%)', opacity:setup,
        fontFamily:FONTS.sans, fontSize:20, fontWeight:600, color:COLORS.coral, width:240, lineHeight:1.25 }}>
        context window limit<br/><span style={{ color:COLORS.inkDim, fontFamily:FONTS.mono, fontSize:17 }}>e.g. 200K tokens</span>
      </div>
      <div style={{ position:'absolute', left:fx, top:fTop-44, opacity:setup, fontFamily:FONTS.mono, fontSize:18, letterSpacing:'0.12em', textTransform:'uppercase', color:COLORS.inkDim }}>
        the context window
      </div>

      {/* blocks (bottom-anchored) */}
      {visible.map((b, idx) => {
        const fromBottom = visible.length - 1 - idx;
        const y = baseY - (fromBottom+1)*bh - fromBottom*bgap;
        const dropping = overflow && idx===0;
        const o = b.summary ? ramp(lt, 11.0, 11.8, Easing.easeOutBack) : (dropping ? 0.4 : 1);
        return (
          <div key={b.label} style={{
            position:'absolute', left:fx+14, top:y, width:fw-28, height:bh, opacity:o*setup,
            background: b.col+'22', border:`1.6px solid ${b.col}`, borderRadius:10,
            display:'flex', alignItems:'center', paddingLeft:16, gap:10,
            fontFamily:FONTS.mono, fontSize:20, color:COLORS.ink, transition:'none',
            boxShadow: b.summary?`0 0 22px ${b.col}44`:'none',
          }}>
            <span style={{ width:10, height:10, borderRadius:'50%', background:b.col }} />
            {b.label}
            {b.summary && <span style={{ marginLeft:'auto', marginRight:14, fontFamily:FONTS.sans, fontSize:15, color:b.col }}>compacted ✓</span>}
          </div>
        );
      })}

      {/* overflow flash */}
      {overflow && (
        <div style={{ position:'absolute', left:fx+fw/2, top:limitY-34, transform:'translate(-50%,0)',
          opacity:pulse(lt,7.4,11.0,0.3), fontFamily:FONTS.sans, fontSize:22, fontWeight:700, color:COLORS.coral }}>
          ⚠ window full — dropping oldest
        </div>
      )}

      <Caption lt={lt} a={1.2} b={4.2}>
        Context isn't infinite — every model has a hard <b style={{color:COLORS.coral}}>window limit</b>.
      </Caption>
      <Caption lt={lt} a={4.4} b={7.2}>
        Long agent runs fill it up, turn after turn.
      </Caption>
      <Caption lt={lt} a={7.6} b={10.8} color={COLORS.coral}>
        Overflow it and the <b>oldest turns fall off</b> — the agent simply forgets them.
      </Caption>
      <Caption lt={lt} a={11.2} b={14.2} color={COLORS.purple}>
        Better: <b>compact</b> old turns into a summary — keep the meaning, drop the tokens.
      </Caption>
      <Caption lt={lt} a={14.6} b={18}>
        Managing the window is managing both <b>memory</b> and <b>cost</b>.
      </Caption>
    </>
  );
}

// ════════════════════════════════════════════════════════════════════════════
// SCENE — GENERATING THE ANSWER (decode + reasoning)  (length 21s)
// ════════════════════════════════════════════════════════════════════════════
function SceneDecode() {
  const { localTime: lt } = useSprite();
  const setup = ramp(lt, 0.2, 1.0);

  // mini KV strip at top (prefill already done -> all green)
  const kvX = 150, kvY = 250, cell = 30, gap = 6, kcols = 10, krows = 4;

  // output tokens emitted one at a time
  const out = ['It', "'s", ' 18', '°C', ' and', ' clear', ' in', ' Tokyo', '.'];
  const emitAt = (i) => 2.6 + i*0.62;
  const nOut = out.filter((_,i)=> lt > emitAt(i)).length;

  // reasoning tokens (hidden) appear in a separate band BEFORE the visible answer (conceptually)
  const reasonIn = pulse(lt, 11.2, 21, 0.4);
  const reasonCount = 14;

  const oy = 470;        // output row baseline
  const ox0 = 150;

  return (
    <>
      <Bg accent={COLORS.yellow} />
      <Eyebrow lt={lt} a={0.4} b={21} n="11" label="Generating the answer" color={COLORS.yellow} />

      {/* prefill KV strip (context, all cached/green) */}
      <div style={{ position:'absolute', left:kvX, top:kvY-40, opacity:setup, fontFamily:FONTS.sans, fontSize:18, color:COLORS.inkDim }}>
        context (prefilled) →
      </div>
      <div style={{ position:'absolute', left:kvX, top:kvY, display:'flex', gap, opacity:setup }}>
        {Array.from({length:kcols}).map((_,c)=>(
          <div key={c} style={{ display:'flex', flexDirection:'column', gap }}>
            {Array.from({length:krows}).map((_,r)=>(
              <div key={r} style={{ width:cell, height:cell, borderRadius:5, background:COLORS.green+'22', border:`1.4px solid ${COLORS.green}` }} />
            ))}
          </div>
        ))}
        {/* the live decode column grows to the right */}
        {Array.from({length:nOut}).map((_,c)=>{
          const isNewest = c===nOut-1;
          const col = isNewest ? COLORS.yellow : COLORS.blue;
          return (
            <div key={'o'+c} style={{ display:'flex', flexDirection:'column', gap, marginLeft: c===0?10:0 }}>
              {Array.from({length:krows}).map((_,r)=>(
                <div key={r} style={{ width:cell, height:cell, borderRadius:5, background:col+'22', border:`1.4px solid ${col}`,
                  boxShadow: isNewest?`0 0 12px ${col}`:'none' }} />
              ))}
            </div>
          );
        })}
      </div>

      {/* forward-pass counter */}
      {nOut>0 && lt<11 && (
        <div style={{ position:'absolute', left: 1340, top: kvY+20, transform:'translate(-50%,-50%)', textAlign:'center' }}>
          <div style={{ fontFamily:FONTS.sans, fontSize:18, color:COLORS.inkDim, letterSpacing:'0.06em', textTransform:'uppercase' }}>forward passes</div>
          <div style={{ fontFamily:FONTS.math, fontSize:96, fontWeight:700, color:COLORS.yellow, lineHeight:1 }}>{nOut}</div>
          <div style={{ fontFamily:FONTS.sans, fontSize:18, color:COLORS.inkDim }}>one per output token</div>
        </div>
      )}

      {/* emitted answer text */}
      <div style={{ position:'absolute', left:ox0, top:oy, width:1400, opacity:setup, fontFamily:FONTS.mono, fontSize:34, color:COLORS.ink, minHeight:50 }}>
        {out.slice(0,nOut).join('')}<span style={{ opacity: (Math.floor(lt*2)%2)&&nOut<out.length?1:0, color:COLORS.yellow }}>▌</span>
      </div>

      {/* reasoning tokens band */}
      {reasonIn > 0.01 && (
        <div style={{ position:'absolute', left:ox0, top:580, opacity:reasonIn, width:1500 }}>
          <div style={{ fontFamily:FONTS.sans, fontSize:20, color:COLORS.purple, fontWeight:600, marginBottom:12, display:'flex', alignItems:'center', gap:10 }}>
            <span>🔒 hidden reasoning</span>
            <span style={{ fontFamily:FONTS.sans, fontSize:17, color:COLORS.inkDim, fontWeight:400 }}>— you never see these, but you pay for them as output</span>
          </div>
          <div style={{ display:'flex', flexWrap:'wrap', gap:7, maxWidth:1400 }}>
            {Array.from({length:reasonCount}).map((_,i)=>{
              const o = ramp(lt, 11.4+i*0.05, 11.7+i*0.05);
              return <div key={i} style={{ width:46, height:26, borderRadius:6, background:COLORS.purple+'22', border:`1.4px solid ${COLORS.purple}66`, opacity:o,
                display:'flex', alignItems:'center', justifyContent:'center', fontFamily:FONTS.mono, fontSize:14, color:COLORS.purple }}>•••</div>;
            })}
            <div style={{ display:'flex', alignItems:'center', paddingLeft:10, fontFamily:FONTS.mono, fontSize:22, color:COLORS.yellow }}>→ visible answer</div>
          </div>
        </div>
      )}

      <Caption lt={lt} a={1.0} b={2.6}>
        The prefill is parallel — but <b style={{color:COLORS.yellow}}>generating</b> the reply is not.
      </Caption>
      <Caption lt={lt} a={2.8} b={6.2}>
        Output is produced <b>one token at a time</b>, each fed back to predict the next.
      </Caption>
      <Caption lt={lt} a={6.6} b={10.8} color={COLORS.yellow}>
        N output tokens = N forward passes. That's <b>why output costs ~5× input</b>.
      </Caption>
      <Caption lt={lt} a={11.2} b={15.4} color={COLORS.purple}>
        "Thinking" models also emit <b>hidden reasoning tokens</b> before the answer…
      </Caption>
      <Caption lt={lt} a={15.6} b={21} color={COLORS.coral}>
        …billed as output, and often the <b>biggest</b> slice of the bill.
      </Caption>
    </>
  );
}

Object.assign(window, { SceneWindowEdges, SceneDecode });
