/* global React, POOLS, PROTOCOL_STATS, HISTORY,
   fmt, I, ModeBadge, Mono, Hash, Stat, Spark, LineChart, gen, useNetwork,
   useOnTxSuccess */

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

// Visual mode -> token-icon background colors
const MODE_COLORS = {
  direct:    "var(--purple)",
  bonding:   "var(--orange)",
  graduated: "var(--green)",
};

// =============================================================
// LANDING — LAUNKR marketing page (replaces the prior dashboard
// landing). Three.js background, ticker, custom cursor, count-up
// stats. Everything mounted/cleaned up inside this component;
// unmount restores plain page state for other routes.
// =============================================================

// Number prettifier for the hero count-up. We allow STX values up
// to ~1B; anything bigger than 1000 collapses to "12.4K" / "1.2M"
// to keep the stat block compact. PROTOCOL_STATS.totalVolume is
// 19500 today, so the typical render is "19.5K" + STX suffix.
function fmtStatNumber(n) {
  if (n >= 1_000_000) return (n / 1_000_000).toFixed(1).replace(/\.0$/, "") + "M";
  if (n >= 1_000)     return (n / 1_000).toFixed(1).replace(/\.0$/, "")     + "K";
  return String(Math.floor(n));
}

function Landing({ go }) {
  const net = useNetwork();
  // --- Static-derived stats (mock-aligned with the rest of the app).
  // PM's hardcoded "$12M / 847 / 34%" replaced by:
  //   tvl    = PROTOCOL_STATS.totalVolume (STX)
  //   tokens = POOLS.length
  //   apy    = 5 (direct-mode fee schedule, hardcoded to match protocol)
  const stats = useMemo(() => ({
    tvl:    PROTOCOL_STATS.totalVolume,
    tokens: POOLS.length,
    apy:    5,
  }), []);

  // Ticker rows from POOLS, duplicated x2 to feed the seamless loop.
  const tickerRows = useMemo(() => {
    const row = POOLS.map(p => ({
      symbol: p.symbol,
      change: p.change24,
      mode:   p.mode,
    }));
    return [...row, ...row];
  }, []);

  // --- Capability flags (reads only once; matches at mount).
  const reducedMotion = useMemo(
    () => typeof window !== "undefined"
      && window.matchMedia
      && window.matchMedia("(prefers-reduced-motion: reduce)").matches,
    []
  );
  const isTouch = useMemo(
    () => typeof window !== "undefined"
      && (("ontouchstart" in window) || (window.matchMedia && window.matchMedia("(pointer: coarse)").matches)),
    []
  );
  const isMobile = useMemo(
    () => typeof window !== "undefined" && window.innerWidth < 768,
    []
  );

  // --- Refs.
  const canvasRef = useRef(null);
  const tvlRef    = useRef(null);
  const tokRef    = useRef(null);
  const apyRef    = useRef(null);
  const sectionsRef = useRef(null);

  // --- Navbar scroll state.
  const [navScrolled, setNavScrolled] = useState(false);
  useEffect(() => {
    const onScroll = () => setNavScrolled(window.scrollY > 40);
    window.addEventListener("scroll", onScroll, { passive: true });
    onScroll();
    return () => window.removeEventListener("scroll", onScroll);
  }, []);

  // --- Custom cursor (mount/unmount, skip on touch + reduced motion).
  useEffect(() => {
    if (reducedMotion || isTouch) return;

    document.body.classList.add("landing-active");

    const dot  = document.createElement("div");
    const ring = document.createElement("div");
    dot.id  = "cursor";
    ring.id = "cursor-ring";
    document.body.appendChild(dot);
    document.body.appendChild(ring);

    let mx = 0, my = 0, rx = 0, ry = 0;
    const onMove = (e) => {
      mx = e.clientX; my = e.clientY;
      dot.style.left = mx + "px";
      dot.style.top  = my + "px";
    };
    const tick = () => {
      rx += (mx - rx) * 0.12;
      ry += (my - ry) * 0.12;
      ring.style.left = rx + "px";
      ring.style.top  = ry + "px";
    };
    document.addEventListener("mousemove", onMove);
    const interval = setInterval(tick, 16);

    // Hover-grow on interactive elements.
    const targets = document.querySelectorAll(
      ".launkr-landing a, .launkr-landing button, " +
      ".launkr-landing .use-card, .launkr-landing .step, .launkr-landing .feature-card"
    );
    const onEnter = () => {
      dot.style.width  = "20px"; dot.style.height = "20px";
      ring.style.width = "60px"; ring.style.height = "60px";
    };
    const onLeave = () => {
      dot.style.width  = "12px"; dot.style.height = "12px";
      ring.style.width = "40px"; ring.style.height = "40px";
    };
    targets.forEach(el => {
      el.addEventListener("mouseenter", onEnter);
      el.addEventListener("mouseleave", onLeave);
    });

    return () => {
      document.body.classList.remove("landing-active");
      document.removeEventListener("mousemove", onMove);
      clearInterval(interval);
      targets.forEach(el => {
        el.removeEventListener("mouseenter", onEnter);
        el.removeEventListener("mouseleave", onLeave);
      });
      dot.remove();
      ring.remove();
    };
  }, [reducedMotion, isTouch]);

  // --- Three.js background.
  useEffect(() => {
    if (reducedMotion) return;
    if (typeof window === "undefined" || !window.THREE) {
      // CDN script may not have loaded yet; bail silently. The canvas just
      // stays blank — the page is still readable on the dark background.
      return;
    }

    const THREE = window.THREE;
    const canvas = canvasRef.current;
    if (!canvas) return;

    let renderer, scene, camera, particles, mat, geo, hexGroup, bubbleGroup;
    const floatingSprites = [];
    const disposables = [];
    let rafId = 0;

    try {
      renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true });
      renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
      renderer.setSize(window.innerWidth, window.innerHeight);

      scene  = new THREE.Scene();
      camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 200);
      camera.position.set(0, 0, 50);

      // Particle field — reduced for mobile to keep frame rates sane.
      const COUNT = isMobile ? 600 : 1800;
      const positions = new Float32Array(COUNT * 3);
      const sizes     = new Float32Array(COUNT);
      for (let i = 0; i < COUNT; i++) {
        const theta = Math.random() * Math.PI * 2;
        const phi   = Math.acos(2 * Math.random() - 1);
        const r     = 20 + Math.random() * 55;
        positions[i * 3]     = r * Math.sin(phi) * Math.cos(theta);
        positions[i * 3 + 1] = r * Math.sin(phi) * Math.sin(theta);
        positions[i * 3 + 2] = r * Math.cos(phi) - 10;
        sizes[i] = 0.5 + Math.random() * 1.5;
      }

      geo = new THREE.BufferGeometry();
      geo.setAttribute("position", new THREE.BufferAttribute(positions, 3));
      geo.setAttribute("size",     new THREE.BufferAttribute(sizes, 1));
      disposables.push(geo);

      mat = new THREE.ShaderMaterial({
        transparent: true,
        depthWrite: false,
        uniforms: {
          uTime:   { value: 0 },
          uMouse:  { value: new THREE.Vector2(0, 0) },
          uColor:  { value: new THREE.Color(0xFF5C00) },
          uColor2: { value: new THREE.Color(0xF0EBE1) },
        },
        vertexShader: `
          attribute float size;
          uniform float uTime;
          uniform vec2 uMouse;
          varying float vAlpha;
          varying float vDist;
          void main() {
            vec3 pos = position;
            vec2 mouseWorld = uMouse * 30.0;
            float d = length(pos.xy - mouseWorld);
            float repel = smoothstep(0.0, 12.0, d);
            pos.xy += normalize(pos.xy - mouseWorld) * (1.0 - repel) * 3.5;
            pos.x += sin(uTime * 0.3 + pos.y * 0.08) * 0.4;
            pos.y += cos(uTime * 0.25 + pos.x * 0.06) * 0.35;
            vDist = d;
            vAlpha = smoothstep(0.0, 8.0, d) * 0.9;
            vec4 mvPos = modelViewMatrix * vec4(pos, 1.0);
            gl_Position = projectionMatrix * mvPos;
            gl_PointSize = size * (60.0 / -mvPos.z);
          }
        `,
        fragmentShader: `
          uniform vec3 uColor;
          uniform vec3 uColor2;
          varying float vAlpha;
          varying float vDist;
          void main() {
            vec2 uv = gl_PointCoord - 0.5;
            float d = length(uv);
            if (d > 0.5) discard;
            float circle = 1.0 - smoothstep(0.3, 0.5, d);
            float t = clamp(vDist / 20.0, 0.0, 1.0);
            vec3 col = mix(uColor, uColor2, t);
            gl_FragColor = vec4(col, circle * vAlpha * 0.85);
          }
        `,
      });
      disposables.push(mat);
      particles = new THREE.Points(geo, mat);
      scene.add(particles);

      // Hex grid
      hexGroup = new THREE.Group();
      scene.add(hexGroup);
      const hexShape = (cx, cy, r) => {
        const pts = [];
        for (let i = 0; i < 6; i++) {
          const a = (Math.PI / 3) * i - Math.PI / 6;
          pts.push(new THREE.Vector2(cx + r * Math.cos(a), cy + r * Math.sin(a)));
        }
        pts.push(pts[0].clone());
        return pts;
      };
      for (let row = -4; row <= 4; row++) {
        for (let col = -6; col <= 6; col++) {
          const x = col * 7.5 + (row % 2) * 3.75;
          const y = row * 6.5;
          const dist = Math.sqrt(x * x + y * y);
          if (dist > 38) continue;
          const pts = hexShape(x, y, 3.2);
          const curve = new THREE.SplineCurve(pts);
          const g = new THREE.BufferGeometry().setFromPoints(curve.getPoints(6));
          const opacity = Math.max(0.03, 0.12 - dist * 0.002);
          const m = new THREE.LineBasicMaterial({
            color: dist < 15 ? 0xFF5C00 : 0xF0EBE1,
            transparent: true,
            opacity,
          });
          disposables.push(g, m);
          hexGroup.add(new THREE.Line(g, m));
        }
      }

      // Bubble token labels
      const bubbleDefs = [
        { text: "BTC",   accent: "#FF5C00", radius: 3.8 },
        { text: "$STX",  accent: "#FF5C00", radius: 3.0 },
        { text: "$STGY", accent: "#F0EBE1", radius: 2.6 },
        { text: "$HASH", accent: "#7ECFFF", radius: 2.4 },
        { text: "$SATK", accent: "#C8F0A0", radius: 2.2 },
        { text: "$ORDL", accent: "#FFD580", radius: 2.2 },
        { text: "BTC",   accent: "#FF5C00", radius: 2.8 },
        { text: "$RUNE", accent: "#FF9966", radius: 2.0 },
      ];

      const makeTokenCanvas = (text, accent, canvasSize) => {
        const c = document.createElement("canvas");
        c.width = canvasSize; c.height = canvasSize;
        const cx = c.getContext("2d");
        const half = canvasSize / 2;
        const grad = cx.createRadialGradient(half, half, half * 0.55, half, half, half * 0.98);
        grad.addColorStop(0,   "rgba(0,0,0,0)");
        grad.addColorStop(0.7, accent + "18");
        grad.addColorStop(1,   accent + "55");
        cx.fillStyle = grad;
        cx.beginPath(); cx.arc(half, half, half, 0, Math.PI * 2); cx.fill();
        const inner = cx.createRadialGradient(half * 0.7, half * 0.6, 0, half, half, half * 0.9);
        inner.addColorStop(0,   "rgba(255,255,255,0.18)");
        inner.addColorStop(0.4, "rgba(255,255,255,0.06)");
        inner.addColorStop(1,   "rgba(0,0,0,0.22)");
        cx.fillStyle = inner;
        cx.beginPath(); cx.arc(half, half, half * 0.88, 0, Math.PI * 2); cx.fill();
        cx.strokeStyle = accent + "CC";
        cx.lineWidth = canvasSize * 0.018;
        cx.beginPath(); cx.arc(half, half, half * 0.88, 0, Math.PI * 2); cx.stroke();
        const spec = cx.createRadialGradient(half * 0.55, half * 0.42, 0, half * 0.55, half * 0.42, half * 0.32);
        spec.addColorStop(0, "rgba(255,255,255,0.55)");
        spec.addColorStop(1, "rgba(255,255,255,0)");
        cx.fillStyle = spec;
        cx.beginPath(); cx.arc(half * 0.55, half * 0.42, half * 0.32, 0, Math.PI * 2); cx.fill();
        const refl = cx.createRadialGradient(half * 1.3, half * 1.45, 0, half * 1.3, half * 1.45, half * 0.25);
        refl.addColorStop(0, "rgba(255,255,255,0.20)");
        refl.addColorStop(1, "rgba(255,255,255,0)");
        cx.fillStyle = refl;
        cx.beginPath(); cx.arc(half * 1.3, half * 1.45, half * 0.25, 0, Math.PI * 2); cx.fill();
        const isBtc = text === "BTC";
        const fontSize = isBtc ? canvasSize * 0.30 : canvasSize * (text.length > 4 ? 0.19 : 0.24);
        cx.font = `700 ${fontSize}px "Space Mono", monospace`;
        cx.textAlign = "center";
        cx.textBaseline = "middle";
        cx.shadowColor = accent;
        cx.shadowBlur = canvasSize * 0.08;
        cx.fillStyle = accent;
        cx.fillText(text, half, half + canvasSize * 0.02);
        cx.shadowBlur = 0;
        return c;
      };

      bubbleGroup = new THREE.Group();
      scene.add(bubbleGroup);

      bubbleDefs.forEach((def, idx) => {
        const cSize = 256;
        const labelCanvas = makeTokenCanvas(def.text, def.accent, cSize);
        const labelTex = new THREE.CanvasTexture(labelCanvas);
        const planeGeo = new THREE.PlaneGeometry(def.radius * 2, def.radius * 2);
        const planeMat = new THREE.MeshBasicMaterial({
          map: labelTex,
          transparent: true,
          depthWrite: false,
          side: THREE.DoubleSide,
        });
        disposables.push(planeGeo, planeMat, labelTex);
        const bubble = new THREE.Mesh(planeGeo, planeMat);
        const angle = (idx / bubbleDefs.length) * Math.PI * 2 + Math.random() * 0.6;
        const radius = 14 + Math.random() * 20;
        const bx = Math.cos(angle) * radius + (Math.random() - 0.5) * 6;
        const by = Math.sin(angle) * radius * 0.55 + (Math.random() - 0.5) * 8;
        const bz = 2 + Math.random() * 10;
        bubble.position.set(bx, by, bz);
        bubble.userData = {
          baseX: bx, baseY: by, baseZ: bz,
          phaseX: Math.random() * Math.PI * 2,
          phaseY: Math.random() * Math.PI * 2,
          phaseRot: Math.random() * Math.PI * 2,
          speedX: 0.14 + Math.random() * 0.12,
          speedY: 0.10 + Math.random() * 0.10,
          ampX: 1.0 + Math.random() * 2.0,
          ampY: 0.7 + Math.random() * 1.5,
          rotSpeed: (Math.random() - 0.5) * 0.003,
        };
        bubbleGroup.add(bubble);
        floatingSprites.push(bubble);
      });

      // Pointer + scroll state
      const mouse3D = new THREE.Vector2();
      const targetMouse = new THREE.Vector2();
      const onMouseMoveBg = (e) => {
        targetMouse.x =  (e.clientX / window.innerWidth)  * 2 - 1;
        targetMouse.y = -(e.clientY / window.innerHeight) * 2 + 1;
      };
      let scrollY = 0;
      const onScrollBg = () => { scrollY = window.scrollY; };
      document.addEventListener("mousemove", onMouseMoveBg);
      window.addEventListener("scroll", onScrollBg, { passive: true });

      const onResize = () => {
        if (!renderer) return;
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
        renderer.setSize(window.innerWidth, window.innerHeight);
      };
      window.addEventListener("resize", onResize);

      let t = 0;
      const animate = () => {
        rafId = requestAnimationFrame(animate);
        t += 0.012;
        mouse3D.x += (targetMouse.x - mouse3D.x) * 0.05;
        mouse3D.y += (targetMouse.y - mouse3D.y) * 0.05;
        mat.uniforms.uTime.value = t;
        mat.uniforms.uMouse.value.set(mouse3D.x, mouse3D.y);
        particles.rotation.y = t * 0.04 + mouse3D.x * 0.2;
        particles.rotation.x = mouse3D.y * 0.15;
        hexGroup.rotation.z = t * 0.015;
        hexGroup.rotation.x = mouse3D.y * 0.08;
        hexGroup.rotation.y = mouse3D.x * 0.08;
        floatingSprites.forEach(bubble => {
          const u = bubble.userData;
          bubble.position.x = u.baseX + Math.sin(t * u.speedX + u.phaseX) * u.ampX + mouse3D.x * 3.5;
          bubble.position.y = u.baseY + Math.cos(t * u.speedY + u.phaseY) * u.ampY + mouse3D.y * 2.5;
          bubble.position.z = u.baseZ + Math.sin(t * 0.2 + u.phaseRot) * 1.5 + mouse3D.x * 1.8;
          bubble.quaternion.copy(camera.quaternion);
          bubble.rotateZ(t * u.rotSpeed);
          bubble.material.opacity = 0.55 + Math.sin(t * 0.5 + u.phaseX) * 0.2;
        });
        camera.position.y = -scrollY * 0.01;
        renderer.render(scene, camera);
      };
      animate();

      return () => {
        cancelAnimationFrame(rafId);
        document.removeEventListener("mousemove", onMouseMoveBg);
        window.removeEventListener("scroll", onScrollBg);
        window.removeEventListener("resize", onResize);
        disposables.forEach(d => { try { d.dispose && d.dispose(); } catch (_) {} });
        try { renderer.dispose(); } catch (_) {}
      };
    } catch (err) {
      // If anything in the WebGL init blows up (no GL context, shader compile
      // fail), bail without crashing the React tree.
      console.warn("LAUNKR landing: Three.js init failed —", err && err.message);
      try { cancelAnimationFrame(rafId); } catch (_) {}
      try { renderer && renderer.dispose && renderer.dispose(); } catch (_) {}
      return;
    }
  }, [reducedMotion, isMobile]);

  // --- Count-up stats (skipped under reduced motion).
  useEffect(() => {
    const set = (el, txt) => { if (el) el.textContent = txt; };
    if (reducedMotion) {
      set(tvlRef.current, fmtStatNumber(stats.tvl));
      set(tokRef.current, String(stats.tokens));
      set(apyRef.current, String(stats.apy));
      return;
    }
    const animateTo = (el, target, duration, suffixFmt) => {
      if (!el) return null;
      const start = performance.now();
      let id = 0;
      const step = (now) => {
        const p = Math.min((now - start) / duration, 1);
        const eased = p * p;
        const val = eased * target;
        el.textContent = suffixFmt ? suffixFmt(val) : String(Math.floor(val));
        if (p < 1) id = requestAnimationFrame(step);
        else el.textContent = suffixFmt ? suffixFmt(target) : String(target);
      };
      id = requestAnimationFrame(step);
      return () => cancelAnimationFrame(id);
    };
    const cancellers = [];
    const t = setTimeout(() => {
      cancellers.push(animateTo(tvlRef.current, stats.tvl,    1800, fmtStatNumber));
      cancellers.push(animateTo(tokRef.current, stats.tokens, 1600));
      cancellers.push(animateTo(apyRef.current, stats.apy,    2000));
    }, 400);
    return () => {
      clearTimeout(t);
      cancellers.forEach(c => c && c());
    };
  }, [reducedMotion, stats]);

  // --- IntersectionObserver-driven scroll reveal.
  useEffect(() => {
    if (!sectionsRef.current) return;
    const root = sectionsRef.current;
    const els = root.querySelectorAll(".reveal");
    if (!els.length) return;
    if (reducedMotion) {
      els.forEach(el => el.classList.add("visible"));
      return;
    }
    const obs = new IntersectionObserver((entries) => {
      entries.forEach((e, i) => {
        if (e.isIntersecting) {
          e.target.style.transitionDelay = (i * 0.08) + "s";
          e.target.classList.add("visible");
          obs.unobserve(e.target);
        }
      });
    }, { threshold: 0.12 });
    els.forEach(el => obs.observe(el));
    return () => obs.disconnect();
  }, [reducedMotion]);

  // --- Smooth-scroll to an in-page anchor (replaces the global :target jump).
  const scrollTo = (id) => (e) => {
    e && e.preventDefault && e.preventDefault();
    const target = document.getElementById(id);
    if (!target) return;
    target.scrollIntoView({ behavior: reducedMotion ? "auto" : "smooth", block: "start" });
  };

  return (
    <div className="launkr-landing">
      <canvas ref={canvasRef} id="bg-canvas"/>

      {/* NAV (landing-only; global Topbar is hidden on this route) */}
      <nav className={"launkr-nav " + (navScrolled ? "scrolled" : "")}>
        <div className="logo">LAUNKR<span>.</span></div>
        <ul>
          <li><a href="#how"        onClick={scrollTo("how")}>How It Works</a></li>
          <li><a href="#features"   onClick={scrollTo("features")}>Features</a></li>
          <li><a href="#use-cases"  onClick={scrollTo("use-cases")}>Use Cases</a></li>
          <li><a onClick={() => go("pools")} style={{ cursor: "pointer" }}>Pools</a></li>
          <li><a className="nav-cta" onClick={() => go("create")}>Launch Token</a></li>
        </ul>
      </nav>

      {/* TICKER — POOLS sourced */}
      <div className="ticker-wrap">
        <div className="ticker-track">
          {tickerRows.map((row, i) => (
            <span className="ticker-item" key={i}>
              <span className="ticker-dot"/> {row.symbol}
              <span
                className="ticker-price"
                style={{ color: row.change >= 0 ? "var(--green)" : "var(--red)" }}
              >
                {row.change >= 0 ? "+" : ""}{row.change.toFixed(1)}%
              </span>
              &nbsp;·&nbsp; {row.mode}
            </span>
          ))}
        </div>
      </div>

      {/* HERO */}
      <section className="hero">
        <div className="protected-badge">Stacks {net.label} - Bitcoin-Secured</div>
        <div className="hero-eyebrow">Protected Token Launch Protocol</div>
        <h1>
          Your token. Your LP.<br/>
          <em>Every swap pays you.</em>
        </h1>
        <p className="hero-sub">
          LAUNKR creates protected tokens that trade exclusively within their own liquidity pool —
          collecting fees for agents, DeFi protocols, and any use case you can imagine. Built on Stacks.
          Secured by Bitcoin.
        </p>
        <div className="hero-actions">
          <button className="btn-primary" onClick={() => go("create")}>Launch Your Token →</button>
          <a href="#how" className="btn-ghost" onClick={scrollTo("how")}>How it works</a>
        </div>
        <div className="hero-stats">
          <div>
            <div className="stat-num">
              <span ref={tvlRef}>0</span>{" "}
              <span className="unit">STX</span>
            </div>
            <div className="stat-label">Total Value Locked</div>
          </div>
          <div>
            <div className="stat-num"><span ref={tokRef}>0</span></div>
            <div className="stat-label">Tokens Launched</div>
          </div>
          <div>
            <div className="stat-num"><span ref={apyRef}>0</span><span className="unit">%</span></div>
            <div className="stat-label">Avg Fee APY</div>
          </div>
        </div>
      </section>

      <div ref={sectionsRef}>
        {/* HOW IT WORKS */}
        <div className="divider"/>
        <section className="how" id="how">
          <p className="section-tag reveal">Protocol</p>
          <h2 className="section-title reveal">How <em>LAUNKR</em> works</h2>
          <div className="steps reveal">
            <div className="step">
              <span className="step-num">01</span>
              <div className="step-icon">
                <svg width="20" height="20" viewBox="0 0 20 20" fill="none">
                  <rect x="3" y="3" width="14" height="14" rx="1.5" stroke="var(--orange)" strokeWidth="1.4"/>
                  <path d="M7 7h6M7 10h6M7 13h4" stroke="var(--orange)" strokeWidth="1.4" strokeLinecap="round"/>
                </svg>
              </div>
              <h3>Configure your token</h3>
              <p>Define supply, fee rate, and distribution rules. Your token is protected from day one — no external DEX listing possible.</p>
            </div>
            <div className="step">
              <span className="step-num">02</span>
              <div className="step-icon">
                <svg width="20" height="20" viewBox="0 0 20 20" fill="none">
                  <circle cx="10" cy="10" r="7" stroke="var(--orange)" strokeWidth="1.4"/>
                  <path d="M10 6v4l3 2" stroke="var(--orange)" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round"/>
                </svg>
              </div>
              <h3>Deploy protected LP</h3>
              <p>A dedicated liquidity pool is deployed on-chain with locked trading routes. Every swap routes through your LP and generates fees.</p>
            </div>
            <div className="step">
              <span className="step-num">03</span>
              <div className="step-icon">
                <svg width="20" height="20" viewBox="0 0 20 20" fill="none">
                  <path d="M10 3v14M6 7l4-4 4 4M6 13l4 4 4-4" stroke="var(--orange)" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round"/>
                </svg>
              </div>
              <h3>Fees flow to you</h3>
              <p>Trade fees accumulate in the launkr and can be claimed by agents, protocols, DAOs, or any on-chain entity you designate.</p>
            </div>
          </div>
        </section>

        {/* FEATURES */}
        <div className="divider"/>
        <section className="features" id="features">
          <p className="section-tag reveal">Features</p>
          <h2 className="section-title reveal">Engineered for <em>control</em></h2>
          <div className="features-grid reveal">
            <div className="feature-card highlight">
              <div className="feature-tag">Core</div>
              <h3>Protected liquidity pool</h3>
              <p>Tokens launched via LAUNKR can only be traded within their own LP. No external routing, no price manipulation from outside markets. Full price discovery inside your ecosystem.</p>
              <div className="feature-visual"/>
            </div>
            <div className="feature-card">
              <div className="feature-tag">Revenue</div>
              <h3>Programmable fee collection</h3>
              <p>Set custom fee tiers and direct yield to any on-chain address. Agents, DAOs, multi-sigs, or smart contracts — fees flow where you program them.</p>
              <div className="feature-visual"/>
            </div>
            <div className="feature-card">
              <div className="feature-tag">Infrastructure</div>
              <h3>Bitcoin-anchored security</h3>
              <p>Built on Stacks, every transaction inherits Bitcoin finality. Your LP and token contract are secured by the most battle-tested blockchain in history.</p>
              <div className="feature-visual"/>
            </div>
            <div className="feature-card">
              <div className="feature-tag">Composable</div>
              <h3>DeFi & Agent native</h3>
              <p>Full SIP-010 compatibility. Any AI agent or DeFi protocol can interact, trade, and collect fees. Designed for autonomous on-chain systems from the ground up.</p>
              <div className="feature-visual"/>
            </div>
          </div>
        </section>

        {/* USE CASES */}
        <div className="divider"/>
        <section className="use-cases" id="use-cases">
          <p className="section-tag reveal">Use Cases</p>
          <h2 className="section-title reveal">Built for <em>builders</em></h2>
          <div className="use-grid reveal">
            <div className="use-card">
              <div className="use-icon">
                <svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
                  <rect x="8" y="3" width="12" height="14" rx="2" stroke="var(--orange)" strokeWidth="1.5"/>
                  <circle cx="11" cy="9" r="1.5" fill="var(--orange)"/>
                  <circle cx="17" cy="9" r="1.5" fill="var(--orange)"/>
                  <path d="M11 13h6" stroke="var(--orange)" strokeWidth="1.5" strokeLinecap="round"/>
                  <path d="M10 17v4M18 17v4" stroke="var(--orange)" strokeWidth="1.5" strokeLinecap="round"/>
                  <path d="M7 21h14" stroke="var(--orange)" strokeWidth="1.5" strokeLinecap="round"/>
                </svg>
              </div>
              <h4>AI Agents</h4>
              <p>Deploy tokens that fund autonomous agents. Every trade fills the agent's operational wallet automatically.</p>
            </div>
            <div className="use-card">
              <div className="use-icon">
                <svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
                  <path d="M14 4v6M14 18v6M4 14h6M18 14h6" stroke="var(--orange)" strokeWidth="1.5" strokeLinecap="round"/>
                  <circle cx="14" cy="14" r="4" stroke="var(--orange)" strokeWidth="1.5"/>
                  <circle cx="14" cy="14" r="1.5" fill="var(--orange)"/>
                </svg>
              </div>
              <h4>DeFi Protocols</h4>
              <p>Create protocol-native tokens with built-in revenue streams, no third-party DEX dependency.</p>
            </div>
            <div className="use-card">
              <div className="use-icon">
                <svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
                  <path d="M4 22h20M7 22v-8M14 22V10M21 22v-5" stroke="var(--orange)" strokeWidth="1.5" strokeLinecap="round"/>
                  <path d="M4 10l10-6 10 6" stroke="var(--orange)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
                </svg>
              </div>
              <h4>DAOs & Governance</h4>
              <p>Launch governance tokens where trading fees directly fund the treasury — aligned incentives by design.</p>
            </div>
            <div className="use-card">
              <div className="use-icon">
                <svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
                  <rect x="3" y="6" width="22" height="16" rx="2" stroke="var(--orange)" strokeWidth="1.5"/>
                  <path d="M3 11h22" stroke="var(--orange)" strokeWidth="1.5"/>
                  <circle cx="10" cy="16" r="2" stroke="var(--orange)" strokeWidth="1.5"/>
                  <path d="M15 15h6M15 17h4" stroke="var(--orange)" strokeWidth="1.5" strokeLinecap="round"/>
                </svg>
              </div>
              <h4>Gaming & NFTs</h4>
              <p>In-game economies with protected tokens that only circulate within your game's ecosystem.</p>
            </div>
          </div>
        </section>

        {/* CTA */}
        <div className="divider"/>
        <section className="cta-section">
          <div className="cta-bg"/>
          <h2>Ready to launch<br/>your <em>protected</em> token?</h2>
          <p>Go live on Stacks in under 5 minutes.</p>
          <div className="cta-actions">
            <button className="btn-primary" onClick={() => go("create")}>Launch Now →</button>
            {/* TODO(PM): destino real de docs */}
            <a href="#" className="btn-ghost" onClick={(e) => e.preventDefault()}>Read the docs</a>
          </div>
        </section>
      </div>

      {/* FOOTER */}
      <footer className="launkr-footer">
        <p>© 2026 LAUNKR PROTOCOL · BUILT ON STACKS</p>
        <div className="footer-links">
          <a href="#" onClick={(e) => e.preventDefault()}>Twitter</a>
          <a href="#" onClick={(e) => e.preventDefault()}>Discord</a>
          <a href="#" onClick={(e) => e.preventDefault()}>GitHub</a>
          {/* TODO(PM): destino real de docs */}
          <a href="#" onClick={(e) => e.preventDefault()}>Docs</a>
        </div>
      </footer>
    </div>
  );
}

// =============================================================
// POOL LIST
// =============================================================
// Mock POOLS principals are baked into data.jsx with a hardcoded testnet
// deployer prefix ("ST2ABWV7...KJJYWE."). We do NOT mutate the source data;
// instead, swap the prefix in at render time so the displayed string follows
// the active network. The U+2026 char in the regex matches the truncation
// ellipsis embedded in the mock literals; accepts both ST and SP shapes.
function rebuildPrincipal(mockPrincipal, deployer) {
  if (!mockPrincipal) return mockPrincipal;
  return mockPrincipal.replace(/^S[TP][0-9A-Z…]+\./, `${deployer}.`);
}

function PoolList({ go }) {
  const net = useNetwork();
  const [filters, setFilters] = useState({ direct: true, bonding: true, graduated: true });
  const [showPaused, setShowPaused] = useState(false);
  const [sort, setSort] = useState("tvl");
  const [lookup, setLookup] = useState("");

  // Live pool enumeration. livePools === null means loading; an empty
  // array means the singleton has no pool-created events on this network
  // yet (fall back to mock). liveErr surfaces a visible failure pill but
  // still renders mock so the demo stays usable.
  const [livePools, setLivePools] = useState(null);
  const [liveErr,   setLiveErr]   = useState(null);

  // Extracted so the same fetch path is used by net.id change AND by the
  // tx-success bus. Returns a cancel function the useEffect cleanup uses;
  // bus-driven calls ignore the return.
  const refetch = useCallback(() => {
    let cancelled = false;
    setLivePools(null);
    setLiveErr(null);
    const api = window.HiroApi;
    if (!api || typeof api.listPoolsViaEvents !== "function") {
      setLivePools([]);
      return () => { cancelled = true; };
    }
    api.listPoolsViaEvents(net).then(
      (rows) => { if (!cancelled) setLivePools(rows); },
      (err)  => { if (!cancelled) { setLiveErr(err); setLivePools([]); } }
    );
    return () => { cancelled = true; };
  }, [net]);

  useEffect(() => {
    const cleanup = refetch();
    return cleanup;
  }, [net.id, refetch]);

  // Brute-force refresh on any confirmed tx. No filtering by tx kind --
  // create-pool, swap, burn all trigger the same lightweight re-read.
  useOnTxSuccess(() => {
    refetch();
  });

  const toggleMode = (mode) => setFilters(f => ({ ...f, [mode]: !f[mode] }));
  const resetFilters = () => {
    setFilters({ direct: true, bonding: true, graduated: true });
    setShowPaused(false);
  };

  // Use live pools when we have a non-empty array; otherwise fall back to
  // the mock POOLS so the demo still looks reasonable on a singleton with
  // zero pool-created events.
  const sourcePools = (Array.isArray(livePools) && livePools.length > 0) ? livePools : POOLS;

  const filtered = useMemo(() => {
    const list = sourcePools.filter(p => filters[p.mode] && (showPaused || p.active));
    const sorter =
      sort === "vol24"  ? (a, b) => b.vol24 - a.vol24  :
      sort === "burn24" ? (a, b) => b.burn24 - a.burn24 :
                          (a, b) => b.tvl - a.tvl;
    return list.slice().sort(sorter);
  }, [sourcePools, filters, showPaused, sort]);

  return (
    <div className="page">
      <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between", marginBottom: 16, gap: 12, flexWrap: "wrap" }}>
        <div>
          <div className="eyebrow">Pools</div>
          <h1 className="h1" style={{ fontSize: 32 }}>All pools</h1>
        </div>
        <button className="btn btn-primary" onClick={() => go("create")}><I.plus/> Create a pool</button>
      </div>

      {livePools === null && liveErr === null && (
        <div className="card" style={{
          marginBottom: 18,
          padding: "10px 14px",
          background: "var(--surface-2)",
          borderLeft: "3px solid var(--ink-3)",
          fontSize: 13,
          color: "var(--ink-2)",
        }}>
          <span className="mono" style={{ fontSize: 11, textTransform: "uppercase", letterSpacing: "0.08em", color: "var(--ink-3)", marginRight: 8 }}>loading</span>
          Loading live pools from <Mono>{net.singletonName}</Mono>...
        </div>
      )}

      {liveErr && (
        <div className="card" style={{
          marginBottom: 18,
          padding: "10px 14px",
          background: "var(--surface-2)",
          borderLeft: "3px solid var(--red)",
          fontSize: 13,
          color: "var(--ink-2)",
        }}>
          <span className="mono" style={{ fontSize: 11, textTransform: "uppercase", letterSpacing: "0.08em", color: "var(--red)", marginRight: 8 }}>live read failed</span>
          {String(liveErr.message || liveErr).slice(0, 160)}
        </div>
      )}

      {Array.isArray(livePools) && livePools.length === 0 && !liveErr && (
        <div className="card" style={{
          marginBottom: 18,
          padding: "10px 14px",
          background: "var(--surface-2)",
          borderLeft: "3px solid var(--orange)",
          fontSize: 13,
          color: "var(--ink-2)",
        }}>
          <span className="mono" style={{ fontSize: 11, textTransform: "uppercase", letterSpacing: "0.08em", color: "var(--ink-3)", marginRight: 8 }}>mock data</span>
          Showing prototype pools - no live pools yet. Once a pool is created on
          <Mono> {net.singletonName}</Mono>, this list will switch to live reads.
        </div>
      )}

      {/* Filter bar */}
      <div className="card" style={{ marginBottom: 18, display: "flex", flexWrap: "wrap", gap: 12, alignItems: "center" }}>
        <div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
          <FilterChip label="Direct"    active={filters.direct}    onClick={() => toggleMode("direct")}    color="var(--purple)"/>
          <FilterChip label="Bonding"   active={filters.bonding}   onClick={() => toggleMode("bonding")}   color="var(--orange)"/>
          <FilterChip label="Graduated" active={filters.graduated} onClick={() => toggleMode("graduated")} color="var(--green)"/>
        </div>
        <label style={{ display: "inline-flex", alignItems: "center", gap: 6, fontSize: 13, color: "var(--ink-2)", cursor: "pointer" }}>
          <input type="checkbox" checked={showPaused} onChange={(e) => setShowPaused(e.target.checked)}/>
          Show paused
        </label>
        <div style={{ flex: 1 }}/>
        <input
          className="input mono"
          style={{ flex: "1 1 220px", minWidth: 200, fontSize: 12 }}
          placeholder="Look up a token by principal..."
          value={lookup}
          onChange={(e) => setLookup(e.target.value)}
        />
        <button className="btn" disabled={!lookup} onClick={() => { /* prototype: no-op */ }}>Look up</button>
        <select
          className="input"
          style={{ width: "auto", padding: "8px 10px", fontSize: 12 }}
          value={sort}
          onChange={(e) => setSort(e.target.value)}
        >
          <option value="tvl">Sort: TVL desc</option>
          <option value="vol24">Sort: 24h vol desc</option>
          <option value="burn24">Sort: 24h burn desc</option>
        </select>
      </div>

      {filtered.length === 0 ? (
        <div className="card" style={{ textAlign: "center", padding: 40 }}>
          <p className="muted" style={{ marginTop: 0 }}>No pools match your filters.</p>
          <button className="btn" onClick={resetFilters}>Reset filters</button>
        </div>
      ) : (
        <div className="card" style={{ padding: 0, overflow: "hidden" }}>
          <PoolListHeader/>
          {filtered.map((p, i) => <PoolRow key={p.id} pool={p} go={go} last={i === filtered.length - 1}/>)}
        </div>
      )}
    </div>
  );
}

const POOL_GRID_COLS = "minmax(0, 1.7fr) auto auto auto auto auto auto";

function PoolListHeader() {
  const cell = {
    fontFamily: "var(--font-mono)",
    fontSize: 10.5,
    textTransform: "uppercase",
    letterSpacing: "0.08em",
    color: "var(--ink-3)",
  };
  return (
    <div style={{
      display: "grid",
      gridTemplateColumns: POOL_GRID_COLS,
      alignItems: "center",
      padding: "10px 16px",
      borderBottom: "1px solid var(--line)",
      gap: 12,
      background: "var(--surface-2)",
    }}>
      <span style={cell}>Token</span>
      <span style={cell}>Mode</span>
      <span style={{ ...cell, textAlign: "right" }}>Price</span>
      <span style={{ ...cell, textAlign: "right" }}>24h</span>
      <span style={{ ...cell, textAlign: "right" }}>TVL</span>
      <span style={{ ...cell, textAlign: "right" }}>24h vol</span>
      <span style={{ ...cell, textAlign: "right" }}>Trend</span>
    </div>
  );
}

function FilterChip({ label, active, onClick, color }) {
  return (
    <button
      onClick={onClick}
      style={{
        padding: "5px 10px",
        borderRadius: 999,
        border: "1px solid " + (active ? color : "var(--line)"),
        background: active ? `color-mix(in srgb, ${color} 14%, transparent)` : "transparent",
        color: active ? color : "var(--ink-3)",
        fontFamily: "var(--font-mono)",
        fontSize: 11,
        fontWeight: 600,
        cursor: "pointer",
        textTransform: "uppercase",
        letterSpacing: "0.06em",
      }}
    >
      {label}
    </button>
  );
}

function PoolRow({ pool, go, last }) {
  const net = useNetwork();
  const isBonding = pool.mode === "bonding";
  const progress = isBonding
    ? Math.min(100, Math.round((pool.bondedStxCollected / pool.graduationThreshold) * 100))
    : null;
  const sparkData = useMemo(() => gen(20, 30, 5, 0, pool.id.charCodeAt(0)), [pool.id]);
  const changeColor = pool.change24 >= 0 ? "var(--green)" : "var(--red)";
  const displayPrincipal = rebuildPrincipal(pool.principal, net.deployer);

  return (
    <div
      onClick={() => go("pool", pool.id)}
      style={{
        display: "grid",
        gridTemplateColumns: POOL_GRID_COLS,
        alignItems: "center",
        padding: "14px 16px",
        cursor: "pointer",
        borderBottom: last ? "0" : "1px solid var(--line)",
        gap: 12,
        transition: "background 0.12s",
      }}
      onMouseEnter={(e) => { e.currentTarget.style.background = "var(--surface-2)"; }}
      onMouseLeave={(e) => { e.currentTarget.style.background = "transparent"; }}
    >
      <div style={{ display: "flex", alignItems: "center", gap: 10, minWidth: 0 }}>
        <span style={{
          width: 28,
          height: 28,
          borderRadius: 6,
          background: MODE_COLORS[pool.mode] || "var(--purple)",
          opacity: pool.active ? 0.85 : 0.35,
          flexShrink: 0,
        }}/>
        <div className="token-cell" style={{ display: "flex", flexDirection: "column", gap: 1, minWidth: 0, flex: 1 }}>
          <span style={{ fontSize: 13, fontWeight: 600, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{pool.name}</span>
          <span style={{ display: "inline-flex", alignItems: "center", gap: 6 }}>
            <span className="mono dim" style={{ fontSize: 11 }}>{pool.symbol}</span>
            {pool.isLive && (
              <span style={{
                display: "inline-flex",
                alignItems: "center",
                gap: 4,
                padding: "1px 6px",
                borderRadius: 4,
                background: "color-mix(in srgb, var(--green) 14%, transparent)",
                color: "var(--green)",
                fontFamily: "var(--font-mono)",
                fontSize: 9,
                fontWeight: 700,
                letterSpacing: "0.08em",
              }}>
                <span style={{ width: 5, height: 5, borderRadius: 999, background: "var(--green)", display: "inline-block" }}/>
                LIVE
              </span>
            )}
          </span>
          <span className="mono dim" style={{ fontSize: 10, opacity: 0.7, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{pool.isLive ? pool.principal : displayPrincipal}</span>
        </div>
        {isBonding && (
          <div style={{ flex: "0 0 64px", marginLeft: 4 }}>
            <div style={{ height: 4, borderRadius: 999, background: "var(--surface-2)", overflow: "hidden" }}>
              <div style={{ width: progress + "%", height: "100%", background: "var(--orange)", transition: "width 0.4s" }}/>
            </div>
            <span className="mono dim" style={{ fontSize: 10 }}>{progress}% to grad.</span>
          </div>
        )}
      </div>
      <ModeBadge mode={pool.mode} paused={!pool.active}/>
      <span className="mono" style={{ fontSize: 12, textAlign: "right" }}>{pool.price.toExponential(2)}</span>
      <span className="mono" style={{ color: changeColor, fontSize: 12, textAlign: "right" }}>{fmt.pct(pool.change24)}</span>
      <span className="mono" style={{ fontSize: 12, textAlign: "right" }}>{fmt.stxShort(pool.tvl)} STX</span>
      <span className="mono" style={{ fontSize: 12, textAlign: "right" }}>{fmt.stxShort(pool.vol24)}</span>
      <span style={{ display: "inline-flex", justifyContent: "flex-end" }}>
        <Spark data={sparkData} color={changeColor} height={28} width={80}/>
      </span>
    </div>
  );
}

Object.assign(window, { Landing, PoolList });
