/* Real opportunities found — extensible list with expandable cards + real timecourse chart */

const CASES = [
  {
    kind: "spike",
    tk: "DOT",
    featured: true,
    delta: -100.00,
    detected: "28s",
    source: "uniswap:eth · DOT / WETH",
    prev: { buy: "$1.242378", sell: "$1.222850" },
    now:  { buy: "$0.0000022388", sell: "$0.0000000998" },
    pool: "0x68d466…436da6 · 1s candles",
    when: "2026-04-13 03:55:51 UTC",
    caption: [
      "Drain block hit at 03:55:23 UTC. 28 seconds later web3map fired the alert at 03:55:51.",
      "Over the following sixteen minutes, price rose to $0.8768.",
      "+39,163,650% of the alert price and 70.6% of the pre-exploit price.",
    ],
    // Real DOT/WETH event on Uniswap v3 (dexscreener 1s candles):
    // Drain block (tx included):   03:55:23 UTC — $1.242378 -> $0.0000022388
    // web3map detection / TG:      03:55:51 UTC — t=+28s, still at floor
    // Dead-cat bounce peak:        ~04:11:23 UTC — t=+960s, $0.8768
    // Latency = detection - block_time = 28s.
    // Bounce peak % of pre-rug    = 0.8768 / 1.242378 = 70.57%.
    series: [
      // pre-rug flat
      {t:-15,px:1.2427},{t:-10,px:1.2425},{t:-5,px:1.2424},
      {t:-2,px:1.2424},{t:-1,px:1.2425},
      // drain block (03:55:23) — liquidity removed in a single tx
      {t:0,px:0.0000022388},
      // floor hover while detection window closes
      {t:10,px:0.0000022300},{t:20,px:0.0000022350},
      // web3map alert fires (03:55:51) — price still at the floor
      {t:28,px:0.0000022388},
      {t:45,px:0.0000022500},{t:60,px:0.0000025000},
      // trickle buys accumulate against near-zero liquidity
      {t:120,px:0.0000900000},{t:180,px:0.0025000000},
      {t:300,px:0.0400000000},{t:420,px:0.1300000000},
      {t:540,px:0.3000000000},{t:660,px:0.4800000000},
      {t:780,px:0.6600000000},{t:870,px:0.8100000000},
      // dead-cat bounce peak (~04:11:23 UTC, t=+960s)
      {t:960,px:0.8768},
      // second leg down
      {t:1020,px:0.5500000000},
    ],
    detectedAtS: 28,
    detectedPx: 0.0000022388,
    preSpikePx: 1.242378,
    capturedPct: 100.00,
    bouncePeakAtS: 960,
    bouncePeakAtMin: 16,
    expectedRoiLabel: "+39.2M%",
    expectedRoiSub: "70.6% of pre-exploit",
    headline: [
      "Exploit captured at the floor in 28 seconds.",
      "+39,163,650% on the alert price.",
    ],
  },
  {
    kind: "spike",
    tk: "MANTA",
    delta: -44.66,
    detected: "28s",
    source: "uniswap:eth · MANTA / WETH",
    prev: { buy: "$0.066820", sell: "$0.062661" },
    now:  { buy: "$0.034675", sell: "$0.031777" },
    pool: "0xec64a7…ef8a · 1s candles",
    when: "2026-04-13 03:14:26 UTC",
    caption: [
      "Flash-drop block hit at 03:13:58 UTC. 28 seconds later web3map fired the alert at 03:14:26.",
      "Within about a minute, price fully recovered to $0.0668.",
      "+93% of the alert price and 100% of the pre-drop price.",
    ],
    // TG payload: prev $0.066820 -> alert $0.034675 (44.66% buy drop).
    // Dexscreener confirms a full V-shape: the pool re-anchored at pre-
    // drop ~$0.067 within roughly one minute of the alert.
    series: [
      // pre-drop flat
      {t:-30, px:0.066820}, {t:-15, px:0.0665}, {t:-5, px:0.0660},
      {t:-2, px:0.0650}, {t:-1, px:0.0540},
      // flash-drop block (03:13:58 UTC)
      {t:0, px:0.034675},
      {t:10, px:0.0348}, {t:20, px:0.0349},
      // web3map alert fires (03:14:26 UTC, block + 28s)
      {t:28, px:0.034675},
      {t:35, px:0.0405}, {t:42, px:0.0500}, {t:50, px:0.0600},
      // full recovery (03:14:58 UTC, block + 60s)
      {t:60, px:0.0668},
      {t:90, px:0.0670}, {t:120, px:0.0666}, {t:180, px:0.0669},
    ],
    detectedAtS: 28,
    detectedPx: 0.034675,
    preSpikePx: 0.066820,
    capturedPct: 44.66,
    bouncePeakAtS: 60,
    bouncePeakAtMin: 1,
    expectedRoiLabel: "+93%",
    expectedRoiSub: "100% of pre-drop",
    headline: [
      "Flash drop captured in 28 seconds.",
      "+93% on the alert price within one minute.",
    ],
  },
  {
    kind: "new",
    tk: "rETH",
    delta: 8.85,
    detected: "1s",
    source: "uniswap:eth  →  katana:ronin",
    prev: { buy: "$2,474.74", sell: "$2,693.70" },
    pool: "0xae787…6393  →  0x29c46…f62e",
    when: "2026-04-19 16:23:45 UTC",
  },
  {
    kind: "new",
    tk: "REQ",
    delta: 10.70,
    detected: "1s",
    source: "balancer:eth  →  bithumb",
    prev: { buy: "$0.070805", sell: "$0.078378" },
    pool: "0x8f8221…938a  (REQ)",
    when: "2026-04-18 15:11:37 UTC",
  },
];

/* ---- Timecourse chart: left price panel + centred inline markers ---- */
const TimecourseChart = ({ series, detectedAtS, bouncePeakAtS }) => {
  const W = 600, H = 190, padL = 96, padR = 20, padT = 18, padB = 28;
  const xs = series.map(p => p.t);
  const ys = series.map(p => p.px);
  const xMin = Math.min(...xs), xMax = Math.max(...xs);
  const yMax = Math.max(...ys);
  const yMin = Math.min(...ys.filter(v => v > 0));
  const useLog = yMax / (yMin || 1) > 50;
  const scaleX = t => padL + ((t - xMin) / (xMax - xMin)) * (W - padL - padR);
  const scaleY = v => {
    if (useLog) {
      const lv = Math.log10(Math.max(v, yMin));
      const lmin = Math.log10(yMin), lmax = Math.log10(yMax);
      return padT + (1 - (lv - lmin) / (lmax - lmin)) * (H - padT - padB);
    }
    return padT + (1 - (v - yMin) / (yMax - yMin)) * (H - padT - padB);
  };
  const pts = series.map(p => `${scaleX(p.t).toFixed(1)},${scaleY(p.px).toFixed(1)}`);
  const linePath = "M" + pts.join(" L");
  const areaPath = linePath + ` L${scaleX(xMax)},${H - padB} L${scaleX(xMin)},${H - padB} Z`;
  const detPt = series.find(p => p.t === detectedAtS);
  const xDet = scaleX(detectedAtS);
  const yDet = scaleY(detPt?.px || ys[0]);

  const bncPt = bouncePeakAtS != null ? series.find(p => p.t === bouncePeakAtS) : null;
  const hasBounce = !!bncPt;
  const xBnc = hasBounce ? scaleX(bouncePeakAtS) : null;
  const yBnc = hasBounce ? scaleY(bncPt.px) : null;

  const fmtT = t => (t === 0 ? "T" : (t > 0 ? "+" : "") + t + "s");
  const fmtPx = v => v >= 0.01 ? "$" + v.toFixed(4) : "$" + v.toExponential(2);

  // x-axis ticks: only evenly spaced time markers. Marker annotations are
  // drawn separately so we don't collide with the dot + callout.
  const niceStep = (range) => {
    const targets = [5, 10, 15, 30, 60, 120, 300, 600];
    return targets.find(s => range / s <= 6) || 600;
  };
  const step = niceStep(xMax - xMin);
  const baseTicks = new Set();
  if (xMin <= 0 && 0 <= xMax) baseTicks.add(0);
  for (let t = Math.ceil(xMin / step) * step; t <= xMax; t += step) {
    if (t >= xMin && t <= xMax) baseTicks.add(t);
  }
  const tickTs = [...baseTicks].sort((a, b) => a - b);

  // Center the marker label on its dot by default; only flip to start/end
  // when the text would actually clip the chart gutters. labelHalf is the
  // rendered half-width of "+28s"/"+16m" at fontSize 11 monospace (~32px
  // wide), with a 2px safety margin so dots very close to the gutter
  // still read as centered.
  const labelHalf = 18;
  const pickAnchor = x =>
    x - labelHalf < padL ? "start" :
    x + labelHalf > W - padR ? "end" :
    "middle";
  const detAnchor = pickAnchor(xDet);
  const detTextX = detAnchor === "start" ? xDet + 4 : detAnchor === "end" ? xDet - 4 : xDet;
  const bncAnchor = hasBounce ? pickAnchor(xBnc) : "middle";
  const bncTextX = hasBounce
    ? (bncAnchor === "start" ? xBnc + 4 : bncAnchor === "end" ? xBnc - 4 : xBnc)
    : 0;

  const detLabelY = Math.max(padT + 10, yDet - 14);
  const bncLabelY = hasBounce ? Math.min(H - padB - 6, yBnc + 22) : 0;

  // Left-side price stack. Peak aligns to yBnc (top), detection to yDet
  // (floor). ROI is rendered in the left-side stat card, not the chart.
  const slotX = padL - 14;
  const peakLabelY = hasBounce
    ? Math.max(padT + 4, Math.min(yBnc, H - padB - 40))
    : null;
  const detStatY = Math.max(padT + 28, Math.min(yDet, H - padB - 24));

  return (
    <svg viewBox={`0 0 ${W} ${H}`} width="100%" preserveAspectRatio="none" style={{display: "block"}}>
      {/* time gridlines (muted) */}
      {tickTs.map((t, i) => (
        <line key={"g"+i} x1={scaleX(t)} y1={padT} x2={scaleX(t)} y2={H - padB}
              stroke="color-mix(in oklab, currentColor 6%, transparent)" strokeWidth="0.5"/>
      ))}
      {/* price path: fill area + stroke line */}
      <path d={areaPath} fill="var(--down)" opacity="0.08"/>
      <path d={linePath} fill="none" stroke="var(--down)" strokeWidth="1.6" strokeLinejoin="round"/>

      {/* ---- Detection marker (inline: dot + time only) ---- */}
      <g>
        <line x1={xDet} y1={detLabelY + 4} x2={xDet} y2={yDet - 6}
              stroke="var(--accent)" strokeWidth="1"/>
        <line x1={xDet} y1={yDet + 6} x2={xDet} y2={H - padB}
              stroke="var(--accent)" strokeOpacity="0.22" strokeWidth="0.7" strokeDasharray="2 3"/>
        <circle cx={xDet} cy={yDet} r="6" fill="var(--accent)" opacity="0.18"/>
        <circle cx={xDet} cy={yDet} r="3" fill="var(--accent)"/>
        <text x={detTextX} y={detLabelY} textAnchor={detAnchor} fontSize="11"
              fontFamily="var(--font-mono)" fill="var(--accent)" fontWeight="700"
              letterSpacing="0.02em">
          +{detectedAtS}s
        </text>
      </g>

      {/* ---- Bounce peak marker (inline: dot + time only) ---- */}
      {hasBounce && (
        <g>
          <line x1={xBnc} y1={yBnc + 6} x2={xBnc} y2={bncLabelY - 10}
                stroke="var(--accent)" strokeWidth="1"/>
          <line x1={xBnc} y1={padT} x2={xBnc} y2={yBnc - 6}
                stroke="var(--accent)" strokeOpacity="0.18" strokeWidth="0.7" strokeDasharray="2 3"/>
          <circle cx={xBnc} cy={yBnc} r="6" fill="var(--accent)" opacity="0.18"/>
          <circle cx={xBnc} cy={yBnc} r="3" fill="var(--accent)"/>
          <text x={bncTextX} y={bncLabelY} textAnchor={bncAnchor} fontSize="11"
                fontFamily="var(--font-mono)" fill="var(--accent)" fontWeight="700"
                letterSpacing="0.02em">
            +{Math.round(bouncePeakAtS / 60)}m
          </text>
        </g>
      )}

      {/* ---- Left price panel (PEAK + DETECTED) ---- */}
      {hasBounce && (
        <g>
          <line x1={padL - 6} y1={padT - 4} x2={padL - 6} y2={H - padB + 4}
                stroke="color-mix(in oklab, currentColor 8%, transparent)" strokeWidth="0.6"/>
          {/* PEAK */}
          <text x={slotX} y={peakLabelY - 4} textAnchor="end" fontSize="8.5"
                fontFamily="var(--font-mono)" fill="var(--fg-3)" letterSpacing="0.14em">
            PEAK
          </text>
          <text x={slotX} y={peakLabelY + 10} textAnchor="end" fontSize="12"
                fontFamily="var(--font-mono)" fill="var(--accent-ink)" fontWeight="700">
            {fmtPx(bncPt.px)}
          </text>
          {/* DETECTED */}
          <text x={slotX} y={detStatY + 4} textAnchor="end" fontSize="8.5"
                fontFamily="var(--font-mono)" fill="var(--fg-3)" letterSpacing="0.14em">
            DETECTED
          </text>
          <text x={slotX} y={detStatY + 18} textAnchor="end" fontSize="12"
                fontFamily="var(--font-mono)" fill="var(--accent)" fontWeight="700">
            {fmtPx(detPt?.px ?? ys[0])}
          </text>
        </g>
      )}
      {/* x labels */}
      {tickTs.map((t, i) => (
        <text key={"tx"+i} x={scaleX(t)} y={H - 8} fontSize="9.5" textAnchor="middle"
              fill="var(--fg-4)" fontFamily="var(--font-mono)" letterSpacing="0.04em">{fmtT(t)}</text>
      ))}
      {/* y extremes only render when the left stat panel is inactive */}
      {!hasBounce && (
        <>
          <text x={padL - 6} y={padT + 6} fontSize="9.5" textAnchor="end"
                fill="var(--fg-4)" fontFamily="var(--font-mono)">{fmtPx(yMax)}</text>
          <text x={padL - 6} y={H - padB} fontSize="9.5" textAnchor="end"
                fill="var(--fg-4)" fontFamily="var(--font-mono)">{fmtPx(yMin)}</text>
        </>
      )}
    </svg>
  );
};

const Sparkline = ({ series, down = true }) => {
  if (!series || series.length < 2) return null;
  const W = 120, H = 34;
  const xs = series.map(p => p.t), ys = series.map(p => p.px);
  const xMin = Math.min(...xs), xMax = Math.max(...xs);
  const yMin = Math.min(...ys.filter(v => v > 0)), yMax = Math.max(...ys);
  const useLog = yMax / (yMin || 1) > 50;
  const sx = t => ((t - xMin) / (xMax - xMin)) * W;
  const sy = v => {
    if (useLog) {
      const lv = Math.log10(Math.max(v, yMin));
      return (1 - (lv - Math.log10(yMin)) / (Math.log10(yMax) - Math.log10(yMin))) * H;
    }
    return (1 - (v - yMin) / (yMax - yMin)) * H;
  };
  const path = "M" + series.map(p => `${sx(p.t).toFixed(1)},${sy(p.px).toFixed(1)}`).join(" L");
  return (
    <svg viewBox={`0 0 ${W} ${H}`} width="100%" height={H} preserveAspectRatio="none" style={{display: "block"}}>
      <path d={path} fill="none" stroke={down ? "var(--down)" : "var(--accent)"} strokeWidth="1.25"/>
    </svg>
  );
};

/* ---- Card ---- */
const OppCard = ({ c, expanded, onToggle }) => {
  const isDown = c.delta < 0;
  const pct = (c.delta > 0 ? "+" : "") + c.delta.toFixed(2) + "%";
  const kindLabel =
    c.kind === "spike" ? "PRICE SPIKE" :
    c.kind === "arb"   ? "ARBITRAGE"   :
                         "NEW ENTRY";
  const canExpand = !!c.series || !!c.pnl;

  return (
    <article className={"opp " + (c.featured ? "featured " : "") + (c.kind === "spike" ? "spike " : "") + (isDown ? "" : "up") + (expanded ? " expanded" : "")}>
      <button className="opp-clickable" onClick={() => canExpand && onToggle()} aria-expanded={expanded}>
        <div className="opp-head">
          <div>
            <div className="opp-tag"><span className="sq"/>{kindLabel}</div>
            <div className="opp-ticker" style={{marginTop: 6}}>{c.tk}</div>
          </div>
          <div style={{textAlign: "right"}}>
            <div className={"opp-delta " + (isDown ? "down" : "up")}>{pct}</div>
            <div className="detected" style={{marginTop: 4}}>detected · {c.detected}</div>
          </div>
        </div>

        {!expanded && c.series && (
          <div style={{marginTop: 4}}>
            <Sparkline series={c.series} down={isDown}/>
          </div>
        )}

        <div className="opp-body">
          <div className="row"><span>Source</span><b>{c.source}</b></div>
          {!expanded && c.capturedPct != null && (
            <div className="row">
              <span>Captured at alert</span>
              <b style={{color: isDown ? "var(--down)" : "var(--accent-ink)"}}>
                {isDown ? "−" : "+"}{c.capturedPct.toFixed(2)}%
              </b>
            </div>
          )}
        </div>

        <div className="opp-foot">
          <span>{c.pool}</span>
          <span style={{display: "flex", alignItems: "center", gap: 8}}>
            {c.when}
            {canExpand && (
              <span className="opp-chev" aria-hidden>
                <svg width="10" height="10" viewBox="0 0 10 10" fill="none" style={{transform: expanded ? "rotate(180deg)" : "none", transition: "transform 180ms ease"}}>
                  <path d="M2 4l3 3 3-3" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round" strokeLinejoin="round"/>
                </svg>
              </span>
            )}
          </span>
        </div>
      </button>

      {expanded && (
        <div className="opp-expand">
          {c.headline && (
            <p className="opp-headline">
              {Array.isArray(c.headline)
                ? c.headline.map((line, i) => (
                    <React.Fragment key={i}>
                      {i > 0 && <br/>}
                      {i > 0
                        ? <span style={{color: "var(--accent-ink)"}}>{line}</span>
                        : line}
                    </React.Fragment>
                  ))
                : c.headline}
            </p>
          )}

          {c.series && (
            <div className="opp-chart">
              <TimecourseChart series={c.series} detectedAtS={c.detectedAtS} bouncePeakAtS={c.bouncePeakAtS}/>
            </div>
          )}

          <div className="opp-keystats">
            <div>
              <span className="k">Detection latency</span>
              <span className="v">{c.detected}</span>
            </div>
            {c.preSpikePx != null && (
              <div>
                <span className="k">Last clean tick</span>
                <span className="v mono">${c.preSpikePx}</span>
              </div>
            )}
            {c.detectedPx != null && (
              <div>
                <span className="k">Price at alert</span>
                <span className="v mono" style={{color: isDown ? "var(--down)" : "var(--accent-ink)"}}>
                  ${c.detectedPx}
                </span>
              </div>
            )}
            {c.capturedPct != null && (
              <div>
                <span className="k">Move captured</span>
                <span className="v" style={{color: isDown ? "var(--down)" : "var(--accent-ink)"}}>
                  {isDown ? "−" : "+"}{c.capturedPct.toFixed(1)}%
                </span>
              </div>
            )}
            {c.expectedRoiLabel != null && (
              <div>
                <span className="k">Peak ROI{c.bouncePeakAtMin != null ? ` · +${c.bouncePeakAtMin}m` : ""}</span>
                <span className="v mono" style={{color: "var(--accent-ink)"}}>
                  {c.expectedRoiLabel}
                </span>
                {c.expectedRoiSub != null && (
                  <span className="k" style={{color: "var(--accent-ink)", letterSpacing: 0, textTransform: "none", whiteSpace: "nowrap"}}>
                    {c.expectedRoiSub}
                  </span>
                )}
              </div>
            )}
          </div>

          {c.caption && (
            <p className="opp-caption">
              {Array.isArray(c.caption)
                ? c.caption.map((line, i) => (
                    <React.Fragment key={i}>{i > 0 && <br/>}{line}</React.Fragment>
                  ))
                : c.caption}
            </p>
          )}
        </div>
      )}
    </article>
  );
};

const Opportunities = () => {
  const [openIdx, setOpenIdx] = React.useState(0); // featured open by default

  return (
    <section id="cases">
      <div className="container">
        <div className="section-head">
          <div className="eyebrow"><span className="dot"/>What the system has caught</div>
          <h2 className="section-title">
            Real opportunities, we surfaced first.
          </h2>
          <p className="lede">
            A running log of events web3map detected before they reached X.<br/>
            Click a card for details.
          </p>
        </div>

        <div className="opp-grid">
          {CASES.map((c, i) => (
            <OppCard
              key={i}
              c={c}
              expanded={openIdx === i}
              onToggle={() => setOpenIdx(openIdx === i ? -1 : i)}
            />
          ))}
        </div>
      </div>
    </section>
  );
};

Object.assign(window, { Opportunities });
