// scenes-12.jsx — "Inside the vector DB: embeddings & similarity" (RAG mechanism)

function EmbedBars({ x, y, n = 18, grow = 1, color = COLORS.purple }) {
  const bars = [];
  for (let i = 0; i < n; i++) {
    const h = 8 + (Math.sin(i * 1.7) * 0.5 + 0.5) * 40;     // deterministic
    const on = i < n * grow;
    bars.push(<div key={i} style={{ width: 7, height: h, borderRadius: 3, background: on ? color : color+'22',
      alignSelf: 'center', transition:'none' }} />);
  }
  return <div style={{ position:'absolute', left:x, top:y, display:'flex', gap:4, alignItems:'center', height:60 }}>{bars}</div>;
}

// 2D projection of the vector space — doc points, one query point
const DOCS = [
  { nx:0.22, ny:0.30, k:'pricing'   }, { nx:0.30, ny:0.42, k:'plans' },
  { nx:0.70, ny:0.24, k:'refund #1', hit:1 }, { nx:0.78, ny:0.34, k:'refund #2', hit:1 },
  { nx:0.83, ny:0.20, k:'returns',  hit:1 }, { nx:0.55, ny:0.72, k:'shipping' },
  { nx:0.40, ny:0.78, k:'careers'  }, { nx:0.18, ny:0.62, k:'api docs' },
];
const QUERY = { nx:0.80, ny:0.28 };

function SceneVectorDB() {
  const { localTime: lt } = useSprite();
  const setup = ramp(lt, 0.2, 1.0);

  // pipeline (left)
  const qIn   = ramp(lt, 1.2, 1.9, Easing.easeOutBack);
  const embIn = ramp(lt, 2.2, 2.9, Easing.easeOutBack);
  const vecGrow = ramp(lt, 3.2, 4.6);

  // plane
  const px = 660, py = 250, pw = 520, ph = 470, pad = 46;
  const PX = (nx) => px + pad + nx*(pw-2*pad);
  const PY = (ny) => py + pad + ny*(ph-2*pad);
  const dotAt = (i) => 4.8 + i*0.18;
  const queryLand = ramp(lt, 7.4, 8.2, Easing.easeOutBack);

  // similarity phase
  const simPhase = lt > 8.6;
  const formulaO = pulse(lt, 8.8, 24, 0.4);
  const topK = lt > 13.0;
  const costPhase = lt > 17.4;

  const qx = PX(QUERY.nx), qy = PY(QUERY.ny);
  const hits = DOCS.map((d,i)=>({...d,i})).filter(d=>d.hit);

  return (
    <>
      <Bg accent={COLORS.purple} />
      <Eyebrow lt={lt} a={0.4} b={24} n="13" label="Inside the vector DB" color={COLORS.purple} />

      {/* ── LEFT: embedding pipeline ───────────────────────────── */}
      <div style={{ position:'absolute', left:300, top:235, transform:'translate(-50%,-50%) scale('+qIn+')', opacity:qIn,
        background:COLORS.bgPanel, border:`1.5px solid ${COLORS.inkFaint}`, borderRadius:12, padding:'12px 18px',
        fontFamily:FONTS.sans, fontSize:20, color:COLORS.ink, whiteSpace:'nowrap' }}>
        <span style={{color:COLORS.inkDim}}>text ·</span> "refund policy?"
      </div>
      <div style={{ position:'absolute', left:300, top:280, width:0, height:40, opacity:embIn,
        borderLeft:`2px solid ${COLORS.inkFaint}`, transform:'translateX(-50%)' }} />
      <div style={{ position:'absolute', left:300, top:360, transform:'translate(-50%,-50%) scale('+embIn+')', opacity:embIn,
        background:COLORS.purple+'1f', border:`1.6px solid ${COLORS.purple}`, borderRadius:12, padding:'12px 18px',
        fontFamily:FONTS.mono, fontSize:19, color:COLORS.purple, whiteSpace:'nowrap', textAlign:'center' }}>
        embedding model
      </div>
      <div style={{ position:'absolute', left:300, top:405, width:0, height:40, opacity:vecGrow,
        borderLeft:`2px solid ${COLORS.inkFaint}`, transform:'translateX(-50%)' }} />
      {vecGrow > 0.01 && (
        <>
          <EmbedBars x={300 - 18*5.5} y={455} grow={vecGrow} />
          <div style={{ position:'absolute', left:300, top:540, transform:'translateX(-50%)', opacity:vecGrow, textAlign:'center', width:420,
            fontFamily:FONTS.mono, fontSize:17, color:COLORS.inkDim }}>
            [0.12, −0.84, 0.33, 0.07, −0.51, …]<br/><span style={{color:COLORS.purple}}>a vector · 1,536 dimensions</span>
          </div>
        </>
      )}

      {/* ── CENTER: vector-space plane ─────────────────────────── */}
      <div style={{ position:'absolute', left:px, top:py, width:pw, height:ph, opacity:setup,
        border:`1.6px solid ${COLORS.inkFaint}66`, borderRadius:14, background:'rgba(155,130,224,0.04)' }} />
      <div style={{ position:'absolute', left:px+14, top:py+10, opacity:setup, fontFamily:FONTS.mono, fontSize:15, letterSpacing:'0.1em', textTransform:'uppercase', color:COLORS.inkDim }}>
        vector space <span style={{textTransform:'none'}}>(2-D projection)</span>
      </div>

      {/* similarity lines query -> hits */}
      {simPhase && hits.map((d,j)=>{
        const o = ramp(lt, 9.0+j*0.4, 9.6+j*0.4);
        return <Wire key={'l'+j} from={[qx,qy]} to={[PX(d.nx),PY(d.ny)]} color={COLORS.green} width={2} draw={o} glow />;
      })}

      {/* doc dots */}
      {DOCS.map((d,i)=>{
        const o = ramp(lt, dotAt(i), dotAt(i)+0.5, Easing.easeOutBack);
        if (o<=0.01) return null;
        const isHit = d.hit && topK;
        const c = isHit ? COLORS.green : COLORS.blue;
        return (
          <div key={i} style={{ position:'absolute', left:PX(d.nx), top:PY(d.ny), transform:`translate(-50%,-50%) scale(${o})`, opacity:o }}>
            <div style={{ width:isHit?20:15, height:isHit?20:15, borderRadius:'50%', background:c,
              boxShadow:`0 0 ${isHit?18:8}px ${c}`, border: isHit?`2px solid #fff4`:'none' }} />
            <div style={{ position:'absolute', left:'50%', top:18, transform:'translateX(-50%)', fontFamily:FONTS.mono, fontSize:13, color:COLORS.inkDim, whiteSpace:'nowrap' }}>{d.k}</div>
          </div>
        );
      })}
      {/* query dot */}
      {queryLand > 0.01 && (
        <div style={{ position:'absolute', left:qx, top:qy, transform:`translate(-50%,-50%) scale(${queryLand})` }}>
          <div style={{ width:24, height:24, borderRadius:'50%', background:COLORS.purple, boxShadow:`0 0 22px ${COLORS.purple}`, border:'2px solid #fff' }} />
          <div style={{ position:'absolute', left:'50%', top:-26, transform:'translateX(-50%)', fontFamily:FONTS.mono, fontSize:14, color:COLORS.purple, whiteSpace:'nowrap', fontWeight:600 }}>query</div>
        </div>
      )}

      {/* ── RIGHT: cosine similarity + ranking ─────────────────── */}
      {formulaO > 0.01 && (
        <div style={{ position:'absolute', left:1250, top:300, opacity:formulaO, width:560 }}>
          <div style={{ fontFamily:FONTS.sans, fontSize:20, color:COLORS.inkDim, marginBottom:14 }}>rank by <b style={{color:COLORS.green}}>cosine similarity</b>:</div>
          <div style={{ fontFamily:FONTS.math, fontSize:40, color:COLORS.ink, marginBottom:6 }}>
            cos θ = <span style={{display:'inline-flex', flexDirection:'column', verticalAlign:'middle', textAlign:'center'}}>
              <span style={{borderBottom:`2px solid ${COLORS.inkFaint}`, padding:'0 10px'}}>A · B</span>
              <span style={{padding:'2px 10px 0'}}>‖A‖ ‖B‖</span>
            </span>
          </div>
          <div style={{ fontFamily:FONTS.math, fontStyle:'italic', fontSize:19, color:COLORS.inkDim, marginBottom:22 }}>
            1.0 = identical meaning · 0 = unrelated
          </div>
          {topK && [['refund #1','0.94'],['returns','0.91'],['refund #2','0.88'],['plans','0.42'],['careers','0.07']].map(([k,s],i)=>{
            const o = ramp(lt, 13.2+i*0.22, 13.7+i*0.22);
            const hit = i<3;
            return (
              <div key={i} style={{ display:'flex', alignItems:'center', justifyContent:'space-between', opacity:o,
                padding:'8px 14px', marginBottom:7, borderRadius:9, width:380,
                background:(hit?COLORS.green:COLORS.inkFaint)+'14', border:`1px solid ${(hit?COLORS.green:COLORS.inkFaint)}55` }}>
                <span style={{ fontFamily:FONTS.mono, fontSize:19, color:hit?COLORS.ink:COLORS.inkDim }}>{k}</span>
                <span style={{ fontFamily:FONTS.math, fontSize:24, color:hit?COLORS.green:COLORS.inkDim }}>{s}</span>
              </div>
            );
          })}
          {topK && <div style={{ marginTop:10, fontFamily:FONTS.sans, fontSize:18, color:COLORS.purple, opacity:ramp(lt,15.0,15.6) }}>top-K → your retrieved chunks ↗</div>}
        </div>
      )}

      {/* cost bridge */}
      {costPhase && (
        <div style={{ position:'absolute', left:px+pw/2, top:py+ph+30, transform:'translate(-50%,0)', opacity:pulse(lt,17.6,24,0.4), textAlign:'center', width:640 }}>
          <div style={{ fontFamily:FONTS.math, fontSize:24, color:COLORS.ink }}>
            embed: <span style={{color:COLORS.green}}>~$0.02 / 1M tok</span> — cheap
          </div>
          <div style={{ fontFamily:FONTS.sans, fontSize:19, color:COLORS.inkDim, marginTop:6 }}>
            the cost isn't the search — it's <b style={{color:COLORS.coral}}>feeding the chunks back in</b>
          </div>
        </div>
      )}

      <Caption lt={lt} a={1.2} b={4.6} color={COLORS.purple}>
        An <b>embedding model</b> turns text into a vector — a list of numbers capturing its meaning.
      </Caption>
      <Caption lt={lt} a={4.8} b={8.4}>
        Every document is a <b>point</b> in that space; similar meanings land <b style={{color:COLORS.blue}}>close together</b>.
      </Caption>
      <Caption lt={lt} a={8.8} b={12.8}>
        Embed the query too, then measure the <b style={{color:COLORS.green}}>angle</b> to every doc — that's cosine similarity.
      </Caption>
      <Caption lt={lt} a={13.2} b={17.2} color={COLORS.green}>
        The <b>top-K nearest</b> become the chunks you retrieve — semantic search, not keywords.
      </Caption>
      <Caption lt={lt} a={17.6} b={24}>
        Embedding is cheap; the real bill comes from injecting those chunks — <b style={{color:COLORS.coral}}>next</b>.
      </Caption>
    </>
  );
}

Object.assign(window, { SceneVectorDB, EmbedBars });
