// Hero ASCII canvas — horizontal Matrix-style streams flowing left → right.
// The wordmark is INVISIBLE in the dim ambient field; it only reveals itself
// where a stream's brightness sweeps over a mask cell — letters appear in
// flashes through the flow of letters, never explicitly painted.

window.HeroASCII = function HeroASCII({ logoText = 'THEVOYD', intensity = 1 }) {
  const canvasRef = React.useRef(null);
  const rafRef = React.useRef(0);

  React.useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext('2d', { alpha: false });

    const DPR = Math.min(window.devicePixelRatio || 1, 2);
    const CELL_W = 10;
    const CELL_H = 13;

    let W = 0, H = 0, cols = 0, rows = 0;
    let chars = null;       // Uint16Array char code per cell
    let bgBright = null;    // Float32Array dim baseline (0 = invisible)
    let maskNextAt = null;  // Float32Array per-cell next char-update time (mask cells only)
    let mask = null;        // Uint8Array (0/1) — wordmark hit-map
    let streams = [];       // {row, x, speed, tail, lastHead}
    let bursts = [];        // long code-line scrolls
    let glints = [];        // brief bright sparks
    let startTime = performance.now();
    let mouseX = 0.5, mouseY = 0.5;
    let curX = 0.5, curY = 0.5;

    // Latin + Cyrillic with a sprinkle of katakana — much more readable than full-katakana.
    const noiseChars = (
      'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +
      'abcdefghijklmnopqrstuvwxyz' +
      'АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЭЮЯ' +
      'абвгдежзийклмнопрстуфхцчшщэюя' +
      '0123456789' +
      '{}()[]<>=+-*/!@#$%&|;:,.?_~^\\' +
      'アイウエオカキクケコサシスセソタチツテト' // sparser katakana flavor
    );
    const codeSnippets = [
      'agent.plan(intent="reply_to_lead")',
      'await llm.complete(prompt, model="claude-sonnet-4-6")',
      'memory.recall(session_id, last_n=24)',
      'tools.crm.create_lead({source: "tg"})',
      'guardrails.check(message)',
      'rag.retrieve(query, k=8, rerank=true)',
      'response = await orchestrator.run(state)',
      'session.escalate(reason="low_confidence")',
      'embeddings.upsert(doc, ns="knowledge")',
      'workflow.execute(steps=[plan, act, verify])',
      'if (user.intent === "sales") route(agent.sdr)',
      'await ws.send({ type: "typing", agent_id })',
      'voice = await tts("ru-RU", text, voice="ada")',
      'queue.push(task, priority="hi")',
      'on_message: dispatch(handlers.intent_router)',
      'observability.log({event:"reply_drafted"})',
      'rl.score(state, action, reward=0.92)',
      'context.window = trim(history, max_tokens=8k)',
      'router.match(/^GET\\s+\\/api\\/agents/, h.list)',
      'db.tx(async (t) => { await t.lock("session") })',
    ];

    function randNoise() { return noiseChars.charCodeAt((Math.random() * noiseChars.length) | 0); }

    function buildMask() {
      const off = document.createElement('canvas');
      off.width = cols; off.height = rows;
      const m = off.getContext('2d');
      m.fillStyle = '#000';
      m.fillRect(0, 0, cols, rows);
      m.fillStyle = '#fff';
      m.textBaseline = 'middle';
      m.textAlign = 'left';
      const targetW = cols * 0.78;
      // Add explicit inter-letter tracking so adjacent glyphs don't run together.
      // tracking is a fraction of the em — 0.14em ≈ ~2 cells at the typical hero size.
      const tracking = 0.14;
      let fs = Math.min(rows * 0.40, cols * 0.22);
      m.font = `900 ${fs}px 'Inter Tight', 'Inter', 'Helvetica Neue', system-ui, sans-serif`;
      const measureTotal = () => {
        let w = 0;
        for (const ch of logoText) w += m.measureText(ch).width;
        return w + tracking * fs * Math.max(0, logoText.length - 1);
      };
      while (measureTotal() > targetW && fs > 4) {
        fs -= 0.5;
        m.font = `900 ${fs}px 'Inter Tight', 'Inter', 'Helvetica Neue', system-ui, sans-serif`;
      }
      let x = (cols - measureTotal()) / 2;
      const y = rows * 0.46;
      for (const ch of logoText) {
        m.fillText(ch, x, y);
        x += m.measureText(ch).width + tracking * fs;
      }

      const img = m.getImageData(0, 0, cols, rows).data;
      const u = new Uint8Array(cols * rows);
      for (let i = 0; i < cols * rows; i++) {
        u[i] = img[i * 4] > 128 ? 1 : 0;
      }
      mask = u;
    }

    function buildStreams() {
      streams = [];
      // ~85% of rows host a stream — dense enough that the wordmark gets
      // swept multiple times per row-traversal but still leaves quiet rows.
      for (let r = 0; r < rows; r++) {
        if (Math.random() < 0.85) {
          streams.push({
            row: r,
            x: -Math.random() * cols * 0.7,
            speed: 0.15 + Math.random() * 0.55,
            tail: 9 + Math.floor(Math.random() * 16),
            lastHead: -999,
            headHue: Math.random() < 0.16 ? '99,102,241' : (Math.random() < 0.1 ? '236,72,153' : '248,248,244'),
          });
        }
      }
      // occasional double-streams for variety
      const extra = (rows * 0.15) | 0;
      for (let k = 0; k < extra; k++) {
        const r = (Math.random() * rows) | 0;
        streams.push({
          row: r,
          x: -Math.random() * cols,
          speed: 0.18 + Math.random() * 0.5,
          tail: 8 + Math.floor(Math.random() * 14),
          lastHead: -999,
          headHue: Math.random() < 0.5 ? '232,232,225' : '248,248,244',
        });
      }
    }

    function setup() {
      const rect = canvas.getBoundingClientRect();
      W = Math.max(1, rect.width);
      H = Math.max(1, rect.height);
      canvas.width = W * DPR;
      canvas.height = H * DPR;
      ctx.setTransform(DPR, 0, 0, DPR, 0, 0);
      ctx.font = `500 13px 'JetBrains Mono', ui-monospace, monospace`;
      ctx.textBaseline = 'top';

      cols = Math.ceil(W / CELL_W);
      rows = Math.ceil(H / CELL_H);

      const n = cols * rows;
      chars = new Uint16Array(n);
      bgBright = new Float32Array(n);
      maskNextAt = new Float32Array(n);
      for (let i = 0; i < n; i++) {
        chars[i] = randNoise();
      }
      buildMask();
      // bg field: mask cells stay BLACK by default — they only light up when a stream sweeps through
      for (let i = 0; i < n; i++) {
        if (mask[i] === 1) {
          bgBright[i] = 0;
          maskNextAt[i] = Math.random() * 0.3;
        } else {
          bgBright[i] = Math.random() < 0.30 ? (0.05 + Math.random() * 0.08) : 0;
        }
      }
      buildStreams();
      bursts = [];
      glints = [];
      startTime = performance.now();
    }

    function spawnBurst() {
      const snippet = codeSnippets[(Math.random() * codeSnippets.length) | 0];
      bursts.push({
        text: snippet,
        x: -snippet.length * CELL_W,
        y: (1 + Math.floor(Math.random() * (rows - 2))) * CELL_H,
        speed: 0.60 + Math.random() * 1.2,
        // variety of brightness — bright bursts dominate every now and then
        alpha: 0.05 + Math.random() * Math.random() * 0.30,
        color: Math.random() < 0.78
          ? 'rgba(200,200,195,'
          : (Math.random() < 0.5 ? 'rgba(99,102,241,' : 'rgba(74,222,128,'),
      });
    }

    function spawnGlint() {
      glints.push({
        c: (Math.random() * cols) | 0,
        r: (Math.random() * rows) | 0,
        life: 1.0,
        speed: 0.012 + Math.random() * 0.02,
      });
    }

    function frame(t) {
      curX += (mouseX - curX) * 0.05;
      curY += (mouseY - curY) * 0.05;
      const px = (curX - 0.5) * 6;
      const py = (curY - 0.5) * 4;

      ctx.fillStyle = '#000';
      ctx.fillRect(0, 0, W, H);

      ctx.save();
      ctx.translate(px, py);

      // ── Pass 0: code bursts (back layer, varying brightness) ──
      if (bursts.length < 24 && Math.random() < 0.28 * intensity) spawnBurst();
      for (let i = bursts.length - 1; i >= 0; i--) {
        const b = bursts[i];
        b.x += b.speed * intensity;
        ctx.fillStyle = b.color + b.alpha + ')';
        ctx.fillText(b.text, b.x, b.y);
        if (b.x > W) bursts.splice(i, 1);
      }

      // ── Pass 1: ambient dim noise field ──
      // Slowly mutate ~0.10% of bg chars per frame so it feels alive
      const mutateCount = (chars.length * 0.0010) | 0;
      for (let k = 0; k < mutateCount; k++) {
        const i = (Math.random() * chars.length) | 0;
        if (mask[i] !== 1) chars[i] = randNoise();
      }

      ctx.fillStyle = 'rgb(180,180,175)';
      for (let r = 0; r < rows; r++) {
        for (let c = 0; c < cols; c++) {
          const i = r * cols + c;
          const b = bgBright[i];
          if (b <= 0.02) continue;
          ctx.globalAlpha = b;
          ctx.fillText(String.fromCharCode(chars[i]), c * CELL_W, r * CELL_H);
        }
      }
      ctx.globalAlpha = 1;

      // ── Pass 1.5: wordmark baseline — mask cells always lit, chars rotate fast ──
      // Left-to-right wipe on first reveal, then steady visibility.
      const elapsed = (t - startTime) / 1000;
      const wipeT = Math.min(1, Math.max(0, (elapsed - 0.35) / 1.1));
      const wipeBand = 0.30;
      ctx.fillStyle = 'rgb(246, 246, 240)';
      for (let r = 0; r < rows; r++) {
        for (let c = 0; c < cols; c++) {
          const i = r * cols + c;
          if (mask[i] !== 1) continue;
          const colWipe = Math.min(1, Math.max(0, (wipeT - (c / cols) * (1 - wipeBand)) / wipeBand));
          if (colWipe < 0.05) continue;

          // Fast per-cell char rotation: every 100–240ms
          if (elapsed >= maskNextAt[i]) {
            chars[i] = randNoise();
            maskNextAt[i] = elapsed + 0.10 + Math.random() * 0.14;
          }

          ctx.globalAlpha = Math.min(0.72, colWipe * 0.72);
          ctx.fillText(String.fromCharCode(chars[i]), c * CELL_W, r * CELL_H);
        }
      }
      ctx.globalAlpha = 1;

      // ── Pass 2: horizontal streams sweep left → right ──
      // Mask cells in the path light up ~3× brighter, revealing THEVOYD
      // dynamically through the flow.
      for (let si = 0; si < streams.length; si++) {
        const s = streams[si];
        s.x += s.speed * intensity;

        const head = Math.floor(s.x);

        // When head advances to a new cell — randomize its glyph
        if (head !== s.lastHead) {
          if (head >= 0 && head < cols) {
            chars[s.row * cols + head] = randNoise();
          }
          s.lastHead = head;
        }

        for (let k = 0; k <= s.tail; k++) {
          const col = head - k;
          if (col < 0 || col >= cols) continue;
          const i = s.row * cols + col;
          // brightness falls off with distance from head
          const fade = Math.pow(1 - k / s.tail, 1.5);
          const inMask = mask[i] === 1;
          let a = fade * (inMask ? 1.85 : 0.52);
          if (a > 0.96) a = 0.96;
          if (a < 0.03) continue;

          if (k === 0) {
            // head — slightly hot
            ctx.fillStyle = `rgba(255, 255, 255, ${Math.min(1, a * 1.05)})`;
          } else if (k < 3) {
            ctx.fillStyle = `rgba(${s.headHue}, ${a})`;
          } else {
            // dim tail in cool grey
            ctx.fillStyle = `rgba(218, 218, 210, ${a})`;
          }
          ctx.fillText(String.fromCharCode(chars[i]), col * CELL_W, s.row * CELL_H);
        }

        // Reset when fully past right edge
        if (s.x - s.tail > cols + 2) {
          s.x = -Math.random() * 30;
          s.lastHead = -999;
          s.speed = 0.15 + Math.random() * 0.55;
          s.tail = 9 + Math.floor(Math.random() * 16);
        }
      }

      // ── Pass 3: glints — brief sparkles for life ──
      if (Math.random() < 0.4 * intensity && glints.length < 10) spawnGlint();
      for (let i = glints.length - 1; i >= 0; i--) {
        const g = glints[i];
        g.life -= g.speed;
        if (g.life <= 0) { glints.splice(i, 1); continue; }
        const idx = g.r * cols + g.c;
        if (idx >= 0 && idx < chars.length && mask[idx] !== 1) {
          ctx.fillStyle = `rgba(255, 255, 255, ${g.life * 0.55})`;
          ctx.fillText(String.fromCharCode(chars[idx] || randNoise()), g.c * CELL_W, g.r * CELL_H);
        }
      }

      ctx.restore();

      rafRef.current = requestAnimationFrame(frame);
    }

    setup();
    const ro = new ResizeObserver(() => setup());
    ro.observe(canvas);

    const onMove = (e) => {
      const r = canvas.getBoundingClientRect();
      mouseX = (e.clientX - r.left) / r.width;
      mouseY = (e.clientY - r.top) / r.height;
    };
    window.addEventListener('mousemove', onMove);

    rafRef.current = requestAnimationFrame(frame);

    return () => {
      cancelAnimationFrame(rafRef.current);
      ro.disconnect();
      window.removeEventListener('mousemove', onMove);
    };
  }, [logoText, intensity]);

  return <canvas ref={canvasRef} />;
};
