// custom-beats.jsx — Momentic visual/interactive beats, split into .beat-lead
// (text) and .beat-aside (feature) zones for responsive landscape on desktop.

const { useState: useCB, useMemo: useCBM } = React;

/* ---------------------------------------------------------------- VisualBeat */
function VisualBeat({ beat }) {
  const Comp = typeof beat.component === "string" ? window[beat.component] : beat.component;
  return (
    <div className="beat beat-visual">
      <div className="beat-lead">
        <div className="beat-eyebrow">{beat.eyebrow}</div>
        <h2 className="beat-title beat-visual-title">{beat.title}</h2>
        {beat.body ? <p className="beat-body beat-visual-body">{beat.body}</p> : null}
        {beat.source ? <div className="beat-source">{beat.source}</div> : null}
      </div>
      <div className="beat-aside">
        <div className="visual-stage">{Comp ? <Comp /> : null}</div>
      </div>
    </div>
  );
}

/* ----------------------------------------------------------------- FanOutBeat */
const FOL_PRESETS = [
  "best running shoes for flat feet",
  "how to fix slow page speed",
  "asana vs monday for small teams",
];
function fanOut(q) {
  const base = (q || "").trim().toLowerCase().replace(/\?+$/, "") || FOL_PRESETS[0];
  return [
    { ax: "look", t: base + " review 2026" },
    { ax: "popular", t: "most recommended " + base },
    { ax: "proof", t: base + " tested results" },
    { ax: "fresh", t: "latest " + base },
    { ax: "alts", t: base + " vs alternatives" },
    { ax: "reviews", t: base + " user reviews" },
    { ax: "proof", t: "expert picks " + base },
    { ax: "look", t: "top rated " + base },
    { ax: "alts", t: "is " + base + " worth it" },
    { ax: "popular", t: base + " comparison" },
    { ax: "reviews", t: base + " pros and cons" },
    { ax: "fresh", t: base + " this year" },
  ];
}
function FanOutBeat({ beat }) {
  const [q, setQ] = useCB(FOL_PRESETS[0]);
  const variants = useCBM(() => fanOut(q), [q]);
  const candidates = variants.length * 40;
  return (
    <div className="beat beat-fanout">
      <div className="beat-lead">
        <div className="beat-eyebrow">{beat.eyebrow}</div>
        <h2 className="beat-title" style={{ fontSize: 28 }}>{beat.title}</h2>
        <div className="fan-readout">
          <div><span className="v">1</span><span className="l">you asked</span></div>
          <div><span className="v">{variants.length}</span><span className="l">rewrites ran</span></div>
          <div><span className="v brand">~{candidates}</span><span className="l">candidates</span></div>
          <div><span className="v">~5</span><span className="l">get cited</span></div>
        </div>
      </div>
      <div className="beat-aside">
        <div className="fan-presets">
          {FOL_PRESETS.map((p) => (
            <button key={p} className={"explore-chip " + (q === p ? "active" : "")} onClick={() => setQ(p)}>{p.split(" ").slice(0, 3).join(" ")}…</button>
          ))}
        </div>
        <input className="fan-input" value={q} onChange={(e) => setQ(e.target.value)} placeholder="Type any query…" />
        <div className="fan-pills">
          {variants.map((v, i) => (<span key={i} className="fan-pill"><b>{v.ax}</b> {v.t}</span>))}
        </div>
      </div>
    </div>
  );
}

/* ---------------------------------------------------------------- PassageBeat */
const PP_PARAS = [
  { t: "A good chef's knife stays balanced in your hand and holds an edge through a month of daily use before it needs a touch up.", cited: true, reason: "Lead" },
  { t: "Knife-making has a long history, from Japanese honyaki forging to the tang-through-handle construction popularized in Solingen.", cited: false, reason: "Background" },
  { t: "A chef's knife is a general-purpose blade between 6 and 10 inches long, with a curved edge for rocking and a flat heel for chopping. It handles about 80% of kitchen tasks.", cited: true, reason: "Definition" },
  { t: "When we started this guide we considered only Western knives, then reader feedback pushed us to widen scope to Japanese blades.", cited: false, reason: "Methodology" },
  { t: "In testing 14 knives across six weeks, the Misono UX10 held a usable edge through 600 cuts of butternut squash, twice the average.", cited: true, reason: "Evidence" },
  { t: "That brings us to the question of handle materials, which we will look at next.", cited: false, reason: "Transition" },
  { t: "The right knife inspires confidence and brings real joy to every meal, a small daily upgrade worth slowing down for.", cited: false, reason: "Marketing" },
];
function PassageBeat({ beat }) {
  const [picked, setPicked] = useCB({});
  const [revealed, setRevealed] = useCB(false);
  const toggle = (i) => { if (!revealed) setPicked((p) => ({ ...p, [i]: !p[i] })); };
  const score = PP_PARAS.reduce((a, p, i) => a + ((!!picked[i] === p.cited) ? 1 : 0), 0);
  return (
    <div className="beat beat-passage">
      <div className="beat-lead">
        <div className="beat-eyebrow">{beat.eyebrow}</div>
        <h2 className="beat-title" style={{ fontSize: 26 }}>{beat.title}</h2>
        <p className="beat-body beat-visual-body">A short guide on choosing a chef's knife. Tap the passages a model would cite, then reveal.</p>
      </div>
      <div className="beat-aside">
        <div className="passage-list">
          {PP_PARAS.map((p, i) => {
            let cls = "pp-para";
            if (revealed) cls += p.cited ? " cited" : " skipped";
            else if (picked[i]) cls += " picked";
            return (
              <div key={i} className={cls} role="button" onClick={() => toggle(i)}>
                {p.t}
                {revealed && <span className="pp-tag">{p.cited ? "CITED · " + p.reason : "SKIPPED · " + p.reason}</span>}
              </div>
            );
          })}
        </div>
        <div className="walker-nav">
          {revealed ? <span className="pp-score">{score}/7 matched · 3 cited, 4 skipped</span> : <span />}
          {!revealed
            ? <button className="walker-next" onClick={() => setRevealed(true)}>Reveal <span>→</span></button>
            : <button className="walker-back" onClick={() => { setRevealed(false); setPicked({}); }}>Try again</button>}
        </div>
      </div>
    </div>
  );
}

/* --------------------------------------------------------------- CentroidBeat */
const CB_ALIEN = [122, 245, 140], CB_CORAL = [255, 115, 108];
function cbLerp(a, b, t) { const c = a.map((v, i) => Math.round(v + (b[i] - v) * t)); return `rgb(${c[0]},${c[1]},${c[2]})`; }
function CentroidBeat({ beat }) {
  const [spun, setSpun] = useCB(15);
  const t = spun / 100;
  const catC = [120, 230], genC = [330, 80];
  const youX = catC[0] + (genC[0] - catC[0]) * t, youY = catC[1] + (genC[1] - catC[1]) * t;
  const distinct = Math.round(92 - t * 78);
  const dColor = cbLerp(CB_ALIEN, CB_CORAL, t);
  const catDots = [[90,238],[112,202],[140,250],[104,272],[150,222],[82,216]];
  const genDots = [[300,68],[332,96],[356,80],[316,112],[348,120],[296,52]];
  return (
    <div className="beat beat-centroid">
      <div className="beat-lead">
        <div className="beat-eyebrow">{beat.eyebrow}</div>
        <h2 className="beat-title" style={{ fontSize: 26 }}>{beat.title}</h2>
        <div className="centroid-readout">
          <div className="centroid-num" style={{ color: dColor }}>{distinct}</div>
          <div className="centroid-lbl">distinctiveness</div>
        </div>
        <div className="scenario-slider" style={{ borderTop: "none", paddingTop: 0 }}>
          <div className="scenario-meta"><span>all original</span><span className="v">{spun}% spun</span></div>
          <input type="range" min="0" max="100" value={spun} onChange={(e) => setSpun(+e.target.value)} />
        </div>
        {beat.source ? <div className="beat-source">{beat.source}</div> : null}
      </div>
      <div className="beat-aside">
        <svg className="centroid-svg" viewBox="0 0 420 300">
          <ellipse cx="120" cy="232" rx="78" ry="62" fill="rgba(122,245,140,0.12)" stroke="rgba(64,85,72,0.4)" strokeWidth="1" strokeDasharray="3 3" transform="rotate(-12 120 232)" />
          <ellipse cx="332" cy="88" rx="70" ry="52" fill="rgba(155,157,150,0.14)" stroke="rgba(155,157,150,0.5)" strokeWidth="1" strokeDasharray="3 3" transform="rotate(10 332 88)" />
          {catDots.map((d, i) => <circle key={"c" + i} cx={d[0]} cy={d[1]} r="4.5" fill="#405548" opacity="0.7" />)}
          {genDots.map((d, i) => <circle key={"g" + i} cx={d[0]} cy={d[1]} r="4.5" fill="#9B9D96" opacity="0.7" />)}
          <line x1={catC[0]} y1={catC[1]} x2={genC[0]} y2={genC[1]} stroke="#FF2417" strokeWidth="1" strokeDasharray="2 4" opacity="0.4" />
          <circle cx={youX} cy={youY} r="10" fill="#FF2417" />
          <circle cx={youX} cy={youY} r="16" fill="none" stroke="#FF2417" strokeWidth="1.4" opacity="0.4" />
          <text x={youX} y={youY - 20} textAnchor="middle" fontFamily="IBM Plex Mono" fontWeight="600" fontSize="11" fill="#FF2417">YOU</text>
          <text x="120" y="296" textAnchor="middle" fontFamily="IBM Plex Mono" fontSize="9" fill="#73746E">your category</text>
          <text x="332" y="158" textAnchor="middle" fontFamily="IBM Plex Mono" fontSize="9" fill="#73746E">generic content</text>
        </svg>
      </div>
    </div>
  );
}

/* ------------------------------------------------------------------ GraphBeat */
const GT_STEPS = [
  { t: "Tap a step to start.", d: "Seven steps from a query to a cited vertex. A stylized walk of the model's internal knowledge graph to the vertex that supports the answer." },
  { t: "Permutation confidence score", d: "The system computes a score for each possible interpretation of the query. The highest-scoring interpretation seeds the search." },
  { t: "Initial vertex attribution", d: "A starting vertex is chosen, the entity the query is most likely asking about. That seed becomes the base of comparison." },
  { t: "Proximity via clustering", d: "Nodes near the seed cluster into related neighborhoods, the semantically adjacent concepts the traversal may visit." },
  { t: "Vertices inventory", d: "Inside the surfaced clusters, candidate vertices are enumerated. These are the nodes compared against the seed." },
  { t: "Edge traversal and retrieval", d: "Edges out of the seed and across clusters are walked. Each candidate vertex is retrieved with whatever data the graph carries." },
  { t: "Vertex value comparison", d: "Each vertex carries a relevance value. The system compares each against the seed's base value, keeping the higher-supporting ones." },
  { t: "Navigation to desired vertex", d: "The traversal lands on the highest-value vertex supporting the answer. That vertex gets cited. A brand with strong edges to it can appear here." },
];
const GT_NODES = [
  { id: "seed", x: 110, y: 200, c: "brand", seed: true, val: "1.0" },
  { id: "b1", x: 70, y: 168, c: "brand", val: ".4" }, { id: "b2", x: 145, y: 232, c: "brand", val: ".3" }, { id: "b3", x: 84, y: 230, c: "brand", val: ".5" },
  { id: "c1", x: 170, y: 64, c: "cat", val: ".6" }, { id: "c2", x: 228, y: 90, c: "cat", val: ".7" }, { id: "c3", x: 196, y: 116, c: "cat", val: ".9", target: true }, { id: "c4", x: 152, y: 104, c: "cat", val: ".5" },
  { id: "u1", x: 270, y: 196, c: "use", val: ".4" }, { id: "u2", x: 312, y: 228, c: "use", val: ".3" }, { id: "u3", x: 258, y: 236, c: "use", val: ".4" }, { id: "u4", x: 306, y: 190, c: "use", val: ".5" },
];
const GT_EDGES = [["seed","c4"],["seed","b1"],["seed","b2"],["seed","b3"],["c4","c3"],["c3","c2"],["c3","c1"],["c3","u1"],["u1","u4"],["u1","u3"],["seed","u3"]];
const GT_NMAP = Object.fromEntries(GT_NODES.map((n) => [n.id, n]));
function GraphBeat({ beat }) {
  const [step, setStep] = useCB(0);
  const cur = GT_STEPS[step];
  const showClusters = step >= 3, showEdges = step >= 5, showVals = step >= 6, showCited = step >= 7;
  return (
    <div className="beat beat-graph">
      <div className="beat-lead">
        <div className="beat-eyebrow">{beat.eyebrow}</div>
        <h2 className="beat-title" style={{ fontSize: 24 }}>{beat.title}</h2>
        <div className="graph-dots">
          {[1,2,3,4,5,6,7].map((n) => (
            <button key={n} className={"graph-dot " + (step === n ? "on" : step > n ? "done" : "")} onClick={() => setStep(n)}>{n}</button>
          ))}
        </div>
        <div className="graph-step">
          <div className="walker-step-title" style={{ fontSize: 18 }}>{cur.t}</div>
          <p className="walker-step-body">{cur.d}</p>
        </div>
        <div className="walker-nav">
          <button className="walker-back" onClick={() => setStep(0)} disabled={step === 0}>Reset</button>
          <button className="walker-next" onClick={() => setStep((s) => Math.min(7, s + 1))} disabled={step === 7}>{step === 7 ? "Cited" : "Next"} <span>→</span></button>
        </div>
      </div>
      <div className="beat-aside">
        <svg className="graph-svg" viewBox="0 0 380 280" fontFamily="IBM Plex Mono">
          <defs>
            <filter id="wob-gtb" x="-8%" y="-8%" width="116%" height="116%">
              <feTurbulence type="fractalNoise" baseFrequency="0.02" numOctaves="2" seed="5" />
              <feDisplacementMap in="SourceGraphic" scale="1.1" />
            </filter>
          </defs>
          <g filter="url(#wob-gtb)" fill="none" stroke="#1F201B" strokeWidth="1.5" opacity={showClusters ? 0.85 : 0.12} style={{ transition: "opacity 0.4s" }}>
            <ellipse cx="100" cy="200" rx="74" ry="56" transform="rotate(-12 100 200)" />
            <ellipse cx="196" cy="92" rx="80" ry="58" transform="rotate(8 196 92)" />
            <ellipse cx="284" cy="212" rx="70" ry="52" transform="rotate(-6 284 212)" />
          </g>
          <g filter="url(#wob-gtb)" stroke="#FF2417" fill="none" strokeLinecap="round">
            {GT_EDGES.map((e, i) => {
              const a = GT_NMAP[e[0]], b = GT_NMAP[e[1]];
              const hot = showEdges && (e[0] === "seed" || e[0] === "c4" || e[0] === "c3");
              return <path key={i} d={`M ${a.x} ${a.y} Q ${(a.x + b.x) / 2 + 5} ${(a.y + b.y) / 2 - 5} ${b.x} ${b.y}`} strokeWidth={hot ? 2.2 : 1.3} opacity={showEdges ? (hot ? 0.95 : 0.4) : 0.22} style={{ transition: "opacity 0.4s" }} />;
            })}
          </g>
          {GT_NODES.map((n) => {
            const cand = step >= 4 && n.c === "cat";
            return (
              <g key={n.id}>
                <circle cx={n.x} cy={n.y} r={n.seed ? 8 : 6} fill="#FF2417" opacity={(step >= 1 && n.seed) || cand || step >= 5 ? 1 : 0.55} stroke={cand ? "#1F201B" : "none"} strokeWidth="1" />
                {showVals && <text x={n.x + 9} y={n.y - 7} fontFamily="IBM Plex Mono" fontSize="9" fill="#1F201B" opacity="0.75">{n.val}</text>}
              </g>
            );
          })}
          {step >= 1 && <circle cx={GT_NMAP.seed.x} cy={GT_NMAP.seed.y} r="15" fill="none" stroke="#1F201B" strokeWidth="1.3" strokeDasharray="3 3" />}
          {showCited && (<g><circle cx={GT_NMAP.c3.x} cy={GT_NMAP.c3.y} r="14" fill="none" stroke="#1F201B" strokeWidth="1.5" strokeDasharray="3 3" /><text x={GT_NMAP.c3.x + 19} y={GT_NMAP.c3.y + 3} fontFamily="IBM Plex Mono" fontWeight="700" fontSize="15" fill="#1F201B">← cited</text></g>)}
          <text x="70" y="244" fontSize="14" fontWeight="700" fill="#FF2417" opacity={showClusters ? 1 : 0.3}>Brand</text>
          <text x="160" y="38" fontSize="14" fontWeight="700" fill="#FF2417" opacity={showClusters ? 1 : 0.3}>Category</text>
          <text x="262" y="260" fontSize="14" fontWeight="700" fill="#FF2417" opacity={showClusters ? 1 : 0.3}>Use case</text>
        </svg>
      </div>
    </div>
  );
}

Object.assign(window, { VisualBeat, FanOutBeat, PassageBeat, CentroidBeat, GraphBeat });
