// app.jsx — chrome around the block-treemap canvas viz
// panels: header bar · left (stats + filters + threshold) · right (network, leaderboard, selected)
// Tweaks: colorMode, labels, pendingCount

const { useState, useEffect, useRef, useMemo } = React;

const TYPES = ["TRANSFER", "SWAP", "NFT", "ARB", "SANDWICH", "MEV", "CONTRACT"];

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "palette": "blazed",
  "colorMode": "type",
  "showLabels": true,
  "pendingCount": 4,
  "txCap": 2000
}/*EDITMODE-END*/;

function fmtGw(g) { return g.toFixed(g < 10 ? 2 : 1); }
function fmtEth(v) {
  if (v >= 100) return v.toFixed(0);
  if (v >= 10) return v.toFixed(1);
  if (v >= 1) return v.toFixed(2);
  if (v >= 0.001) return v.toFixed(4);
  return v.toExponential(1);
}
function fmtAgo(ms) {
  const s = ms / 1000;
  if (s < 1) return Math.round(s * 1000) + "ms";
  if (s < 60) return s.toFixed(1) + "s";
  return Math.floor(s / 60) + "m" + Math.floor(s % 60) + "s";
}
function fmtNum(n) {
  if (n >= 1e6) return (n / 1e6).toFixed(2) + "M";
  if (n >= 1e3) return (n / 1e3).toFixed(1) + "k";
  return String(Math.round(n));
}

function Stat({ label, value, unit, accent, sub }) {
  return (
    <div className="stat">
      <div className="stat-label">{label}</div>
      <div className="stat-value" style={accent ? { color: accent } : null}>
        {value}
        {unit ? <span className="stat-unit"> {unit}</span> : null}
      </div>
      {sub != null ? <div className="stat-sub">{sub}</div> : null}
    </div>
  );
}

function TypeChip({ type, color, active, count, onClick }) {
  return (
    <button
      className={"chip" + (active ? " on" : "")}
      onClick={onClick}
      style={{ "--c": color }}
    >
      <span className="chip-dot" />
      <span className="chip-label">{type}</span>
      <span className="chip-count">{fmtNum(count)}</span>
    </button>
  );
}

function MiniBar({ value, max, color }) {
  const pct = Math.max(0, Math.min(1, value / max));
  return (
    <div className="minibar">
      <div className="minibar-fill" style={{ width: pct * 100 + "%", background: color }} />
    </div>
  );
}

// Map a gas price to a 0..1 inclusion-odds fraction using the same percentiles
// as the text label, so the bar fill matches LOW/MED/HIGH/VERY HIGH instead of
// plotting raw gwei against the absolute slider scale.
function inclusionOdds(gp, p10, p50, p90) {
  const seg = (x, x0, x1, y0, y1) =>
    y0 + (y1 - y0) * Math.max(0, Math.min(1, (x - x0) / ((x1 - x0) || 1)));
  if (!p90) return 0; // empty mempool: no signal
  let o;
  if (gp >= p90) o = seg(gp, p90, p90 * 1.5, 0.9, 1); // VERY HIGH -> 90-100%
  else if (gp >= p50) o = seg(gp, p50, p90, 0.6, 0.9); // HIGH      -> 60-90%
  else if (gp >= p10) o = seg(gp, p10, p50, 0.3, 0.6); // MED       -> 30-60%
  else o = seg(gp, 0, p10, 0.06, 0.3); // LOW       -> 6-30%
  return Math.max(0.04, Math.min(1, o));
}

function BlockCard({ b, palette, isLatest, age }) {
  const util = Math.round(b.utilization * 100);
  const colors = Object.values(palette.types);
  return (
    <div className={"block" + (isLatest ? " latest" : "")} style={{ "--c": palette.text }}>
      <div className="block-head">
        <span className="block-num">#{b.number.toLocaleString()}</span>
        <span className="block-age">{fmtAgo(age)} ago</span>
      </div>
      <div className="block-mini">
        {Array.from({ length: 60 }).map((_, i) => {
          const c = colors[(b.number * 7 + i * 31) % colors.length];
          const on = ((b.number + i * 13) % 100) / 100 < b.utilization;
          return <span key={i} className="pix" style={{ background: on ? c : "rgba(255,255,255,0.05)" }} />;
        })}
      </div>
      <div className="block-stats">
        <span>{b.txCount} tx</span>
        <span>{util}%</span>
        <span>{fmtGw(b.baseFee)} gw</span>
      </div>
      <div className="block-builder">{b.builder}</div>
    </div>
  );
}

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);

  const simRef = useRef(null);
  const vizRef = useRef(null);
  const canvasRef = useRef(null);
  const stageRef = useRef(null);

  const [, setTick] = useState(0);
  const [filter, setFilter] = useState({ TRANSFER: true, SWAP: true, NFT: true, ARB: true, SANDWICH: true, MEV: true, CONTRACT: true });
  const [threshold, setThreshold] = useState(0);
  const [selected, setSelected] = useState(null);
  const [hover, setHover] = useState(null);

  // init sim+viz once
  useEffect(() => {
    const sim = new MempoolFeed(TWEAK_DEFAULTS.txCap);
    const viz = new MempoolViz(canvasRef.current, sim);
    simRef.current = sim;
    vizRef.current = viz;

    const ro = new ResizeObserver(() => {
      const r = stageRef.current.getBoundingClientRect();
      viz.resize(r.width, r.height);
    });
    ro.observe(stageRef.current);

    const id = setInterval(() => setTick((x) => x + 1), 200);
    return () => { clearInterval(id); ro.disconnect(); viz.running = false; };
  }, []);

  useEffect(() => {
    const v = vizRef.current; if (!v) return;
    v.setPalette(t.palette);
    v.colorMode = t.colorMode;
    v.showLabels = t.showLabels;
    if (v.pendingCount !== t.pendingCount) {
      v.pendingCount = t.pendingCount;
      v._layoutFrames && v._layoutFrames();
    }
  }, [t.palette, t.colorMode, t.showLabels, t.pendingCount]);

  useEffect(() => { if (vizRef.current) vizRef.current.filter = filter; }, [filter]);
  useEffect(() => { if (vizRef.current) vizRef.current.gasThreshold = threshold; }, [threshold]);

  const sim = simRef.current;
  const palette = MempoolPalettes[t.palette];

  // derived metrics
  const tps = sim ? sim.getTps() : 0;
  const pending = sim ? sim.txs.length : 0;
  const avgGas = sim ? sim.getAvgGas() : 0;
  const baseFee = sim ? sim.baseFee : 0;
  const blocks = sim ? sim.blocks : [];
  const blockNum = sim ? sim.blockNum : 0;
  const nextIn = sim ? sim.nextBlockIn() : 0;
  const [p10, p50, p90] = sim ? sim.getGasPctiles() : [0, 0, 0];
  // Pool cap is advertised by the backend in the snapshot; fall back to the
  // local default until it arrives.
  const cap = sim ? sim.maxTxs : TWEAK_DEFAULTS.txCap;

  // type counts
  const typeCounts = useMemo(() => {
    const c = { TRANSFER: 0, SWAP: 0, NFT: 0, ARB: 0, SANDWICH: 0, MEV: 0, CONTRACT: 0 };
    if (sim) for (const x of sim.txs) c[x.type]++;
    return c;
  }, [sim, pending]);

  const topTxs = useMemo(() => {
    if (!sim) return [];
    return sim.txs.slice().sort((a, b) => b.gasPrice - a.gasPrice).slice(0, 8);
  }, [sim, pending]);

  // canvas events
  const onMove = (e) => {
    const r = canvasRef.current.getBoundingClientRect();
    vizRef.current.handleMouse(e.clientX - r.left, e.clientY - r.top);
    setHover(vizRef.current.hoverTx);
  };
  const onLeave = () => { vizRef.current.handleMouse(-9999, -9999); setHover(null); };
  const onClick = (e) => {
    const r = canvasRef.current.getBoundingClientRect();
    const tx = vizRef.current.handleClick(e.clientX - r.left, e.clientY - r.top);
    setSelected(tx);
  };
  const selectFromList = (tx) => { setSelected(tx); if (vizRef.current) vizRef.current.setSelected(tx); };

  // network status: disconnected feed wins; else derive from base fee + p90
  const disconnected = sim && sim.connected === false;
  const status = disconnected ? "DISCONNECTED" : baseFee > 60 ? "CONGESTED" : baseFee > 35 ? "BUSY" : baseFee > 18 ? "NORMAL" : "QUIET";
  const statusColor = disconnected ? palette.types.MEV : status === "CONGESTED" ? palette.types.MEV : status === "BUSY" ? palette.types.CONTRACT : status === "QUIET" ? palette.types.TRANSFER : palette.types.SWAP;

  // gas slider max (informed by p90)
  const sliderMax = Math.max(60, Math.ceil(p90 * 1.2));

  return (
    <div className="app" style={{
      "--bg": palette.bg,
      "--accent": palette.text,
      "--axis": palette.axis,
      "--grid": palette.grid,
    }}>
      {/* HEADER */}
      <header className="hd">
        <div className="hd-left">
          <div className="hd-logo">
            <span className="hd-glyph">◈</span>
            <span className="hd-title">MEMPOOL<span className="hd-title-dim">.LIVE</span></span>
          </div>
          <span className="hd-sep" />
          <div className="hd-net">
            <span className="dot live" style={{ background: statusColor, boxShadow: `0 0 8px ${statusColor}` }} />
            <span>ETHEREUM MAINNET</span>
            <span className="hd-status" style={{ color: statusColor }}>· {status}</span>
          </div>
        </div>
        <div className="hd-right">
          <div className="hd-metric"><span className="m-l">BLOCK</span><span className="m-v">{blockNum.toLocaleString()}</span></div>
          <div className="hd-metric"><span className="m-l">NEXT</span><span className="m-v">{nextIn.toFixed(1)}s</span></div>
          <div className="hd-metric"><span className="m-l">BASE</span><span className="m-v" style={{ color: palette.text }}>{fmtGw(baseFee)} gw</span></div>
          <div className="hd-metric"><span className="m-l">TPS</span><span className="m-v">{tps}</span></div>
          <div className="hd-clock">{new Date().toUTCString().slice(17, 25)} UTC</div>
        </div>
      </header>

      {/* MAIN GRID */}
      <div className="grid">
        {/* LEFT PANEL */}
        <aside className="pane left">
          <Section title="LIVE STATS" badge={<Pulse color={palette.text} />}>
            <div className="stat-row">
              <Stat label="PENDING" value={pending.toLocaleString()} sub={`cap ${cap.toLocaleString()}`} />
              <Stat label="THROUGHPUT" value={tps} unit="tx/s" accent={palette.text} />
            </div>
            <div className="stat-row">
              <Stat label="AVG GAS" value={fmtGw(avgGas)} unit="gw" />
              <Stat label="BASE FEE" value={fmtGw(baseFee)} unit="gw" accent={palette.text} />
            </div>
            <div className="capbar">
              <div className="capbar-row">
                <span>POOL CAPACITY</span>
                <span>{Math.round((pending / cap) * 100)}%</span>
              </div>
              <MiniBar value={pending} max={cap} color={palette.text} />
            </div>
          </Section>

          <Section title="GAS PERCENTILES">
            <div className="pcts">
              <PctRow label="LOW   p10" v={p10} max={sliderMax} color={palette.types.TRANSFER} />
              <PctRow label="MED   p50" v={p50} max={sliderMax} color={palette.types.SWAP} />
              <PctRow label="HIGH  p90" v={p90} max={sliderMax} color={palette.types.MEV} />
            </div>
          </Section>

          <Section title="TX TYPES · FILTER">
            <div className="chips">
              {TYPES.map((tp) => (
                <TypeChip
                  key={tp}
                  type={tp}
                  color={palette.types[tp]}
                  active={filter[tp]}
                  count={typeCounts[tp]}
                  onClick={() => setFilter({ ...filter, [tp]: !filter[tp] })}
                />
              ))}
            </div>
          </Section>

          <Section title={`GAS THRESHOLD · ${threshold > 0 ? fmtGw(threshold) + " gw" : "OFF"}`}>
            <div className="slider-wrap">
              <input
                type="range" min={0} max={sliderMax} step={0.5}
                value={Math.min(threshold, sliderMax)}
                onChange={(e) => setThreshold(parseFloat(e.target.value))}
                className="slider"
                style={{ "--c": palette.text }}
              />
              <div className="slider-ticks">
                <span>0</span><span>p50</span><span>p90</span><span>{sliderMax}</span>
              </div>
              <div className="slider-hint">Txs below this gas price fade to ghosts</div>
            </div>
          </Section>

          <Section title="POOL ECONOMY">
            <div className="econ">
              <div className="econ-row"><span>TOTAL FEES PENDING</span><span className="econ-v">{sim ? sim.txs.reduce((s, x) => s + x.fee, 0).toFixed(3) : "0"} <em>ETH</em></span></div>
              <div className="econ-row"><span>TOP GAS BID</span><span className="econ-v" style={{ color: palette.types.MEV }}>{topTxs[0] ? fmtGw(topTxs[0].gasPrice) : "0"} <em>gw</em></span></div>
              <div className="econ-row"><span>MEV ACTIVITY</span><span className="econ-v" style={{ color: palette.types.MEV }}>{typeCounts.MEV + typeCounts.ARB + typeCounts.SANDWICH}</span></div>
              <div className="econ-row"><span>ARB · SANDWICH</span><span className="econ-v"><span style={{ color: palette.types.ARB }}>{typeCounts.ARB}</span> · <span style={{ color: palette.types.SANDWICH }}>{typeCounts.SANDWICH}</span></span></div>
              <div className="econ-row"><span>SEEN SESSION</span><span className="econ-v">{sim ? fmtNum(sim.totalSeen) : "0"}</span></div>
              <div className="econ-row"><span>INCLUDED</span><span className="econ-v" style={{ color: palette.text }}>{sim ? fmtNum(sim.totalIncluded) : "0"}</span></div>
            </div>
          </Section>
        </aside>

        {/* CENTER STAGE */}
        <main className="stage" ref={stageRef}>
          <canvas
            ref={canvasRef}
            className="viz"
            onMouseMove={onMove}
            onMouseLeave={onLeave}
            onClick={onClick}
          />
          <div className="stage-overlay">
            <div className="stage-toggles">
              <div className="toggle-group">
                <span className="toggle-label">COLOR</span>
                <button className={"tg" + (t.colorMode === "type" ? " on" : "")} onClick={() => setTweak("colorMode", "type")}>TYPE</button>
                <button className={"tg" + (t.colorMode === "gas" ? " on" : "")} onClick={() => setTweak("colorMode", "gas")}>GAS</button>
              </div>
              <div className="toggle-group">
                <span className="toggle-label">LABELS</span>
                <button className={"tg" + (t.showLabels ? " on" : "")} onClick={() => setTweak("showLabels", !t.showLabels)}>{t.showLabels ? "ON" : "OFF"}</button>
              </div>
            </div>
            <div className="stage-tag">
              {selected ? (
                <span>SELECTED <span className="tag-v">{selected.hash}</span></span>
              ) : hover ? (
                <span>HOVER <span className="tag-v">{hover.hash}</span></span>
              ) : (
                <span className="tag-hint">hover a tx for detail · click to pin</span>
              )}
            </div>
          </div>
        </main>

        {/* RIGHT PANEL */}
        <aside className="pane right">
          <Section title="NETWORK STATUS">
            <div className="net">
              <div className="net-status" style={{ borderColor: statusColor }}>
                <span className="dot" style={{ background: statusColor, boxShadow: `0 0 10px ${statusColor}` }} />
                <span style={{ color: statusColor }}>{status}</span>
              </div>
              <div className="net-rows">
                <div><span>SAFE</span><span style={{ color: palette.types.TRANSFER }}>{fmtGw(p10)} gw</span><span className="muted">~30s</span></div>
                <div><span>STD</span><span style={{ color: palette.types.SWAP }}>{fmtGw(p50)} gw</span><span className="muted">~12s</span></div>
                <div><span>FAST</span><span style={{ color: palette.types.MEV }}>{fmtGw(p90)} gw</span><span className="muted">&lt;12s</span></div>
              </div>
            </div>
          </Section>

          <Section title="SELECTED TX">
            {selected ? (
              <div className="detail">
                <div className="d-head">
                  <span className="d-type" style={{ background: palette.types[selected.type] + "22", color: palette.types[selected.type], borderColor: palette.types[selected.type] }}>{selected.type}</span>
                  <span className="d-hash">{selected.hash}</span>
                </div>
                <div className="d-row"><span>VALUE</span><span className="d-v">{fmtEth(selected.value)} ETH</span></div>
                <div className="d-row"><span>GAS PRICE</span><span className="d-v" style={{ color: palette.text }}>{fmtGw(selected.gasPrice)} gwei</span></div>
                <div className="d-row"><span>GAS LIMIT</span><span className="d-v">{(selected.gasLimit / 1000).toFixed(0)}k</span></div>
                <div className="d-row"><span>FEE (MAX)</span><span className="d-v">{selected.fee.toFixed(5)} ETH</span></div>
                <div className="d-row"><span>NONCE</span><span className="d-v">{selected.nonce}</span></div>
                <div className="d-row"><span>FROM</span><span className="d-v">{selected.from}</span></div>
                <div className="d-row"><span>TO</span><span className="d-v">{selected.to}</span></div>
                <div className="d-row"><span>AGE</span><span className="d-v">{fmtAgo(performance.now() - selected.createdAt)}</span></div>
                {!selected.includedAt && (
                  <div className="d-bar">
                    <div className="d-bar-label">
                      <span>INCLUSION ODDS</span>
                      <span>{selected.gasPrice >= p90 ? "VERY HIGH" : selected.gasPrice >= p50 ? "HIGH" : selected.gasPrice >= p10 ? "MED" : "LOW"}</span>
                    </div>
                    <MiniBar value={inclusionOdds(selected.gasPrice, p10, p50, p90)} max={1} color={palette.types[selected.type]} />
                  </div>
                )}
              </div>
            ) : (
              <div className="detail-empty">
                <div className="crosshair">+</div>
                <div>Click a tx in the pool to pin it.</div>
              </div>
            )}
          </Section>

          <Section title="TOP GAS BIDS" badge={<span className="bdg">LIVE</span>}>
            <div className="leaderboard">
              {topTxs.map((tx, i) => (
                <button
                  key={tx.id}
                  className={"lb-row" + (selected && selected.id === tx.id ? " on" : "")}
                  onClick={() => selectFromList(tx)}
                >
                  <span className="lb-rank">{String(i + 1).padStart(2, "0")}</span>
                  <span className="lb-dot" style={{ background: palette.types[tx.type], boxShadow: `0 0 6px ${palette.types[tx.type]}` }} />
                  <span className="lb-hash">{tx.hash}</span>
                  <span className="lb-gas" style={{ color: palette.text }}>{fmtGw(tx.gasPrice)}</span>
                  <span className="lb-val">{fmtEth(tx.value)}</span>
                </button>
              ))}
              {topTxs.length === 0 ? <div className="lb-empty">waiting for txs…</div> : null}
            </div>
          </Section>
        </aside>
      </div>

      {/* TWEAKS */}
      <TweaksPanel title="Tweaks">
        <TweakSection label="Visualization" />
        <TweakRadio label="Color by" value={t.colorMode}
          options={["type", "gas"]}
          onChange={(v) => setTweak("colorMode", v)} />
        <TweakSlider label="Pending blocks" value={t.pendingCount} min={2} max={6} step={1}
          onChange={(v) => setTweak("pendingCount", v)} />
        <TweakToggle label="Inline labels" value={t.showLabels}
          onChange={(v) => setTweak("showLabels", v)} />
      </TweaksPanel>
    </div>
  );
}

function Section({ title, badge, children }) {
  return (
    <section className="sect">
      <div className="sect-head">
        <span className="sect-title">{title}</span>
        {badge}
      </div>
      <div className="sect-body">{children}</div>
    </section>
  );
}

function Pulse({ color }) {
  return <span className="pulse" style={{ background: color, boxShadow: `0 0 8px ${color}` }} />;
}

function PctRow({ label, v, max, color }) {
  return (
    <div className="pct-row">
      <span className="pct-label">{label}</span>
      <MiniBar value={v} max={max} color={color} />
      <span className="pct-val" style={{ color }}>{v.toFixed(1)} gw</span>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
