// 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 glow = null;        // Float32Array — lingering reveal energy per mask cell
    let accentRGB = ['99,102,241', '236,72,153', '245,158,11']; // gradient: indigo·pink·amber
    let startTime = performance.now();

    // Pull the live accent gradient (--acc-1/2/3) so colored glyphs match the
    // "автоматизация и боты" gradient and follow any accent-palette change.
    function readAccentRGB() {
      const cs = getComputedStyle(document.documentElement);
      const toRGB = (hex) => {
        hex = (hex || '').trim().replace('#', '');
        if (hex.length === 3) hex = hex.split('').map((h) => h + h).join('');
        if (hex.length !== 6) return null;
        const n = parseInt(hex, 16);
        return `${(n >> 16) & 255},${(n >> 8) & 255},${n & 255}`;
      };
      const out = [1, 2, 3].map((k) => toRGB(cs.getPropertyValue('--acc-' + k))).filter(Boolean);
      if (out.length) accentRGB = out;
    }
    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); }

    // Hand-built bitmap glyphs in a regular BOLD-SANS style (normal width, even
    // 2-cell strokes — not condensed). An integer-scaled bitmap keeps letters
    // perfectly symmetric/even with zero sampling artifacts; rasterizing a real
    // font into this coarse grid is what produced the ragged/asymmetric shapes.
    const GLYPHS = {
      'T': ['########', '########', '...##...', '...##...', '...##...', '...##...', '...##...', '...##...', '...##...', '...##...', '...##...', '...##...'],
      'H': ['##....##', '##....##', '##....##', '##....##', '##....##', '########', '########', '##....##', '##....##', '##....##', '##....##', '##....##'],
      'E': ['########', '########', '##......', '##......', '##......', '######..', '######..', '##......', '##......', '##......', '########', '########'],
      'V': ['##....##', '##....##', '##....##', '.##..##.', '.##..##.', '.##..##.', '.##..##.', '..####..', '..####..', '..####..', '...##...', '...##...'],
      'O': ['.######.', '########', '##....##', '##....##', '##....##', '##....##', '##....##', '##....##', '##....##', '##....##', '########', '.######.'],
      'Y': ['##....##', '##....##', '.##..##.', '.##..##.', '..####..', '..####..', '...##...', '...##...', '...##...', '...##...', '...##...', '...##...'],
      'D': ['######..', '#######.', '##...##.', '##....##', '##....##', '##....##', '##....##', '##....##', '##....##', '##...##.', '#######.', '######..'],
    };
    const GW = 8, GH = 12;

    function buildMask() {
      const text = (logoText || '').toUpperCase();
      const chars = [...text];
      const n = chars.length;
      const baseGap = 1; // base columns between letters
      const baseW = n * GW + (n - 1) * baseGap;
      // Integer scale so letters stay perfectly crisp (no fractional sampling).
      const S = Math.max(1, Math.floor(Math.min((cols * 0.82) / baseW, (rows * 0.42) / GH)));
      const gap = baseGap * S;
      const glyphW = GW * S, glyphH = GH * S;
      const totalW = n * glyphW + (n - 1) * gap;
      let gx = Math.round((cols - totalW) / 2);
      const y0 = Math.round(rows * 0.44 - glyphH / 2);

      const u = new Uint8Array(cols * rows);
      for (const ch of chars) {
        const g = GLYPHS[ch]; // unknown chars (e.g. space) just advance the cursor
        if (g) {
          for (let ry = 0; ry < GH; ry++) {
            const rowStr = g[ry];
            for (let rx = 0; rx < GW; rx++) {
              if (rowStr[rx] !== '#') continue;
              for (let sy = 0; sy < S; sy++) {
                for (let sx = 0; sx < S; sx++) {
                  const cc = gx + rx * S + sx;
                  const rr = y0 + ry * S + sy;
                  if (cc >= 0 && cc < cols && rr >= 0 && rr < rows) u[rr * cols + cc] = 1;
                }
              }
            }
          }
        }
        gx += glyphW + gap;
      }
      mask = u;
    }

    // ~65% of streams glow in an accent-gradient color (the whole tail is tinted),
    // the rest stay white/grey. Returns { hue, accent }.
    function pickHue() {
      if (Math.random() < 0.65) {
        return { hue: accentRGB[(Math.random() * accentRGB.length) | 0], accent: true };
      }
      return { hue: (Math.random() < 0.5 ? '232,232,225' : '248,248,244'), accent: false };
    }

    function buildStreams() {
      streams = [];
      // EVERY row hosts at least one stream — guarantees no row is ever black,
      // so the whole wordmark (incl. the bottom rows of the glyphs) gets swept.
      for (let r = 0; r < rows; r++) {
        const p = pickHue();
        streams.push({
          row: r,
          x: -Math.random() * cols * 0.7,
          speed: 0.30 + Math.random() * 1.10,
          tail: 9 + Math.floor(Math.random() * 16),
          lastHead: -999,
          headHue: p.hue,
          accent: p.accent,
        });
      }
      // extra double-streams for density & variety (~35% more)
      const extra = (rows * 0.35) | 0;
      for (let k = 0; k < extra; k++) {
        const r = (Math.random() * rows) | 0;
        const p = pickHue();
        streams.push({
          row: r,
          x: -Math.random() * cols,
          speed: 0.36 + Math.random() * 1.00,
          tail: 8 + Math.floor(Math.random() * 14),
          lastHead: -999,
          headHue: p.hue,
          accent: p.accent,
        });
      }
    }

    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);
      glow = 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;
        }
      }
      readAccentRGB();
      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 reveal — lingering afterimage of passing glyphs ──
      // Nothing is drawn as a standalone word. Mask cells only carry "glow"
      // deposited by streams sweeping through (Pass 2). Here that glow decays
      // slowly, so THE VOYD lingers where glyphs just flew past and fades out
      // until the next stream re-lights it. No always-on wordmark.
      ctx.fillStyle = 'rgb(248, 248, 242)';
      for (let r = 0; r < rows; r++) {
        for (let c = 0; c < cols; c++) {
          const i = r * cols + c;
          if (mask[i] !== 1) continue;
          let g = glow[i];
          if (g <= 0.012) { glow[i] = 0; continue; }
          g *= 0.9925;                      // slow decay → afterimage lingers ~5s
          glow[i] = g;
          ctx.globalAlpha = Math.min(0.9, g);
          ctx.fillText(String.fromCharCode(chars[i]), c * CELL_W, r * CELL_H);
        }
      }
      ctx.globalAlpha = 1;

      // ── Pass 2: horizontal streams sweep left → right ──
      // Where a stream crosses a mask cell it deposits glow[] (charged below),
      // which Pass 1.5 then lets linger — that is what reveals the wordmark.
      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;
          // Charge the wordmark afterimage as the glyph passes through it.
          if (inMask) glow[i] = Math.max(glow[i], fade * 0.95);
          let a = fade * (inMask ? 0.95 : 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 (s.accent) {
            // colored stream — the entire tail is tinted in its accent hue
            ctx.fillStyle = `rgba(${s.headHue}, ${a})`;
          } 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.30 + Math.random() * 1.10;
          s.tail = 9 + Math.floor(Math.random() * 16);
          const p = pickHue();
          s.headHue = p.hue;
          s.accent = p.accent;
        }
      }

      // ── 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} />;
};
