// scenes-5.jsx — How prompt caching works (prefix saved once, reused cheaply)

// a stacked context block used in the caching scene
function CtxBlock({ x, y, w, label, state, o = 1 }) {
  // state: 'idle' | 'fresh' | 'cached'
  const col = state === 'cached' ? COLORS.green : state === 'fresh' ? COLORS.blue : COLORS.inkFaint;
  const tagText = state === 'cached' ? 'cache hit · ×0.1' : state === 'fresh' ? 'fresh · ×1' : '';
  return (
    <div style={{
      position:'absolute', left:x, top:y, width:w, opacity:o,
      transform:`translate(0,${(1-o)*-16}px)`,
    }}>
      <div style={{
        height:64, borderRadius:11, display:'flex', alignItems:'center', gap:12, paddingLeft:16,
        background: col+'1f', border:`1.6px solid ${col}`,
        boxShadow: state!=='idle' ? `0 0 18px ${col}44` : 'none',
        fontFamily: FONTS.mono, fontSize:20, color: COLORS.ink, transition:'none',
      }}>
        <span style={{ width:10, height:10, borderRadius:'50%', background:col, flexShrink:0 }} />
        {label}
        {tagText && (
          <span style={{ marginLeft:'auto', marginRight:14, fontFamily:FONTS.sans, fontSize:16, fontWeight:600, color:col, whiteSpace:'nowrap' }}>{tagText}</span>
        )}
      </div>
    </div>
  );
}

// the cache store — a little database cylinder
function CacheStore({ x, y, scale = 1, fill = 0, glow = 0, label = 'prefix cache' }) {
  const w = 188, h = 150, ry = 26;
  const col = COLORS.green;
  return (
    <div style={{ position:'absolute', left:x, top:y, transform:`translate(-50%,-50%) scale(${scale})` }}>
      <div style={{
        position:'absolute', left:'50%', top:'50%', transform:'translate(-50%,-50%)',
        width:w*1.7, height:w*1.7, borderRadius:'50%',
        background:`radial-gradient(circle, ${col}33, transparent 68%)`, opacity:glow,
      }} />
      <svg width={w} height={h+ry} viewBox={`0 0 ${w} ${h+ry}`} style={{ display:'block' }}>
        <defs>
          <clipPath id="cylclip"><path d={`M0 ${ry} a ${w/2} ${ry} 0 0 0 ${w} 0 v ${h} a ${w/2} ${ry} 0 0 1 ${-w} 0 Z`} /></clipPath>
        </defs>
        {/* body */}
        <path d={`M0 ${ry} a ${w/2} ${ry} 0 0 0 ${w} 0 v ${h} a ${w/2} ${ry} 0 0 1 ${-w} 0 Z`}
          fill={COLORS.bgPanel} stroke={col+'88'} strokeWidth="2.5" />
        {/* fill level */}
        <rect x="0" y={ry + h*(1-fill)} width={w} height={h*fill + ry} fill={col+'33'} clipPath="url(#cylclip)" />
        {/* top ellipse */}
        <ellipse cx={w/2} cy={ry} rx={w/2} ry={ry} fill={COLORS.bgPanel} stroke={col} strokeWidth="2.5" />
        {fill > 0.02 && <ellipse cx={w/2} cy={ry + h*(1-fill)} rx={w/2} ry={ry} fill={col+'55'} stroke={col} strokeWidth="1.5" />}
      </svg>
      <div style={{ textAlign:'center', marginTop:10, fontFamily:FONTS.sans, fontSize:21, fontWeight:600, color:col }}>{label}</div>
    </div>
  );
}

// ════════════════════════════════════════════════════════════════════════════
// SCENE — HOW CACHING WORKS  (length 24s)
// ════════════════════════════════════════════════════════════════════════════
function SceneCaching() {
  const { localTime: lt } = useSprite();
  const setup = ramp(lt, 0.2, 1.0);

  // ── timeline phases ────────────────────────────────────────────────────────
  // A  2.4–6.0  : first call, everything fresh, flowing to model
  // A2 6.0–8.4  : prefix written to cache (cylinder fills)
  // B  9.6–13.0 : second call — prefix served from cache (turns green)
  // B2 13.0–16.4: only new tail flows fresh
  // C  17.0–24  : price tally
  const colX = 150, colW = 470, top = 250, slot = 80;
  const mx = 1120, my = top + slot*1.5 + 8;   // model centred on prefix stack
  const cacheXY = [1610, my];

  // prefix blocks (stable across calls) + the changing tail
  const prefix = [
    { label:'system prompt' },
    { label:'tool definitions' },
    { label:'turns 1–4 history' },
  ];

  const turn2 = lt >= 8.8;                 // second call begins (tail appears)
  const cacheFill = clamp(ramp(lt, 6.2, 8.2), 0, 1);
  const cacheGlow = pulse(lt, 6.0, 16.4, 0.6);

  // per-prefix-block state
  const prefixState = (i) => {
    if (lt < 2.4 + i*0.3) return 'idle';
    if (!turn2) return 'fresh';
    // in turn 2, prefix served from cache after 9.6 + stagger
    if (lt >= 9.6 + i*0.3) return 'cached';
    return 'fresh';
  };
  const tailState = lt >= 13.0 ? 'fresh' : (lt >= 8.8 ? 'idle' : 'idle');

  // flow pulses model-ward
  const flowA = (lt > 2.6 && lt < 6.0);
  const flowWrite = (lt > 6.2 && lt < 8.2);
  const flowRead  = (turn2 && lt > 9.6 && lt < 13.0);
  const flowTail  = (turn2 && lt > 13.0 && lt < 16.4);

  const blockMidY = (i) => top + i*slot + 32;

  // price tally
  const showPrice = pulse(lt, 17.0, 24, 0.45);
  // numbers: prefix 4600 tok, tail 700 tok
  const PFX = 4600, TAIL = 700;
  const freshAll = (PFX+TAIL)/1e6*PRICE.input;            // if nothing cached
  const cachedRun = PFX/1e6*PRICE.cached + TAIL/1e6*PRICE.input;
  const saveReveal = ramp(lt, 19.4, 20.6);

  return (
    <>
      <Bg accent={COLORS.green} />
      <Eyebrow lt={lt} a={0.4} b={24} n="09" label="How caching works" color={COLORS.green} />

      {/* call badge */}
      <div style={{ position:'absolute', left: colX + colW/2, top: 168, transform:'translateX(-50%)', opacity:setup, textAlign:'center' }}>
        <div style={{ fontFamily:FONTS.mono, fontSize:20, letterSpacing:'0.14em', textTransform:'uppercase',
          color: turn2 ? COLORS.green : COLORS.blue, transition:'none' }}>
          {turn2 ? 'second call' : 'first call'}
        </div>
      </div>

      {/* prefix bracket label */}
      {setup > 0.5 && (
        <div style={{ position:'absolute', left: colX - 8, top: top, height: slot*3 - 16, width: 6,
          borderLeft:`3px solid ${COLORS.green}88`, opacity: setup }}>
          <div style={{ position:'absolute', left:-104, top:'50%', width:96, transform:'translateY(-50%) rotate(-90deg)',
            transformOrigin:'center', textAlign:'center', fontFamily:FONTS.sans, fontSize:18, fontWeight:600, color:COLORS.green, letterSpacing:'0.06em', whiteSpace:'nowrap' }}>
            stable prefix
          </div>
        </div>
      )}

      {/* context blocks */}
      {prefix.map((b, i) => (
        <CtxBlock key={i} x={colX} y={top + i*slot} w={colW} label={b.label} state={prefixState(i)} o={ramp(lt, 0.6 + i*0.18, 1.4 + i*0.18)} />
      ))}
      {turn2 && (
        <CtxBlock x={colX} y={top + 3*slot} w={colW} label="new user message" state={tailState}
          o={ramp(lt, 8.8, 9.6, Easing.easeOutBack)} />
      )}

      {/* wires from blocks to model */}
      {prefix.map((b, i) => {
        const st = prefixState(i);
        const c = st === 'cached' ? COLORS.green : st === 'fresh' ? COLORS.blue : COLORS.inkFaint;
        const active = (flowA && st==='fresh') || (flowRead && st==='cached');
        const fl = active ? ((lt % 0.9) / 0.9) : null;
        return <Wire key={i} from={[colX+colW, blockMidY(i)]} to={[mx-86, my]} color={c+ (active?'':'55')} width={2} draw={setup} flow={fl} />;
      })}
      {turn2 && (() => {
        const c = tailState==='fresh' ? COLORS.blue : COLORS.inkFaint;
        const fl = flowTail ? ((lt % 0.9)/0.9) : null;
        return <Wire from={[colX+colW, blockMidY(3)]} to={[mx-86, my]} color={c+(flowTail?'':'55')} width={2} draw={ramp(lt,9.0,9.8)} flow={fl} />;
      })()}

      {/* model */}
      <ModelNode x={mx} y={my} r={74} scale={setup} glow={setup}
        thinking={(flowA||flowTail) ? lt : 0} color={turn2 ? COLORS.green : COLORS.blue} />

      {/* model <-> cache wire */}
      <Wire from={[mx+78, my]} to={[cacheXY[0]-96, cacheXY[1]]} color={COLORS.green + ((flowWrite||flowRead)?'':'44')} width={2.5}
        draw={ramp(lt, 5.6, 6.4)}
        flow={flowWrite ? ((lt%0.8)/0.8) : (flowRead ? (1-((lt%0.8)/0.8)) : null)} />
      <div style={{ position:'absolute', left:(mx+cacheXY[0])/2, top: my - 34, transform:'translate(-50%,-50%)',
        fontFamily:FONTS.sans, fontSize:17, fontWeight:600, color:COLORS.green,
        opacity: (flowWrite?1:0)*pulse(lt,6.2,8.2,0.3) + (flowRead?1:0)*pulse(lt,9.6,13.0,0.3) }}>
        {flowWrite ? 'write ↘' : 'read ↖'}
      </div>

      <CacheStore x={cacheXY[0]} y={cacheXY[1]} scale={ramp(lt,4.6,5.6,Easing.easeOutBack)} fill={cacheFill} glow={cacheGlow} />

      {/* price tally panel (phase C) */}
      {showPrice > 0.01 && (
        <div style={{ position:'absolute', left: colX+colW/2, top: 730, transform:'translate(-50%,0)', opacity:showPrice, textAlign:'center', width: 540 }}>
          <div style={{ fontFamily:FONTS.math, fontSize:33, color:COLORS.ink, marginBottom:6 }}>
            <span style={{color:COLORS.green}}>{fmtNum(PFX)}</span> cached <span style={{color:COLORS.inkFaint}}>×</span> $0.30
            <span style={{color:COLORS.inkFaint}}> + </span>
            <span style={{color:COLORS.blue}}>{TAIL}</span> fresh <span style={{color:COLORS.inkFaint}}>×</span> $3
          </div>
          <div style={{ fontFamily:FONTS.math, fontStyle:'italic', fontSize:22, color:COLORS.inkDim }}>
            vs {fmtUSD(freshAll)} with no cache
          </div>
        </div>
      )}

      {/* savings callout near cache */}
      {saveReveal > 0.01 && (
        <div style={{ position:'absolute', left: cacheXY[0], top: cacheXY[1]+180, transform:'translate(-50%,0)', opacity:saveReveal, textAlign:'center' }}>
          <div style={{ fontFamily:FONTS.sans, fontSize:20, color:COLORS.inkDim, letterSpacing:'0.06em' }}>reused prefix</div>
          <div style={{ fontFamily:FONTS.math, fontSize:64, fontWeight:700, color:COLORS.green, lineHeight:1.1 }}>~10× cheaper</div>
        </div>
      )}

      <Caption lt={lt} a={2.4} b={6.0}>
        First call: every token is processed <b style={{color:COLORS.blue}}>fresh</b>.
      </Caption>
      <Caption lt={lt} a={6.2} b={8.6} color={COLORS.green}>
        The provider saves the processed <b>stable prefix</b> to a cache.
      </Caption>
      <Caption lt={lt} a={9.0} b={12.8}>
        Next call: the unchanged prefix is <b style={{color:COLORS.green}}>read from cache</b>, not recomputed.
      </Caption>
      <Caption lt={lt} a={13.2} b={16.8}>
        Only the <b style={{color:COLORS.blue}}>new tokens</b> are processed fresh.
      </Caption>
      <Caption lt={lt} a={17.2} b={24} color={COLORS.green}>
        Keep the prefix <b>stable &amp; up front</b> → most of every turn is cache-priced.
      </Caption>
    </>
  );
}

Object.assign(window, { SceneCaching, CtxBlock, CacheStore });
