import { useEffect, useRef } from 'react';

/* Matrix code-rain canvas — faithful port of /opt/boolab MatrixRain.jsx.
   Amber head (#fbbf24) → orange (#f97316) → deep-rust (#7a3d14) trail, COL_WIDTH
   14, throttled to ~24fps, dpr capped at 2, visibilitychange-paused, and
   composited with destination-out fade so the canvas stays transparent and
   overlays the UI without dimming it. Mounted by ThemeFx only when the
   bc-anim-on gate is on (toggle ON *and* not prefers-reduced-motion). */

const CHARSET = (() => {
  let s = '';
  for (let c = 0x41; c <= 0x5a; c++) s += String.fromCharCode(c); // A–Z
  for (let c = 0x30; c <= 0x39; c++) s += String.fromCharCode(c); // 0–9
  for (let c = 0xff66; c <= 0xff9d; c++) s += String.fromCharCode(c); // half-width katakana
  return s;
})();

const COL_WIDTH = 14;
const TARGET_FPS = 24;
const FRAME_BUDGET = 1000 / TARGET_FPS;
const HEAD_COLOR = '#fbbf24'; // amber
const SECONDARY_COLOR = '#f97316'; // orange
const DEEP_COLOR = '#7a3d14'; // deep rust

function randChar(): string {
  return CHARSET.charAt((Math.random() * CHARSET.length) | 0);
}

interface Column {
  y: number;
  step: number;
}

interface Props {
  enabled?: boolean;
  density?: number;
  speed?: number;
  opacity?: number;
}

export function MatrixRain({
  enabled = true,
  density = 0.35,
  speed = 0.7,
  opacity = 0.6,
}: Props) {
  const canvasRef = useRef<HTMLCanvasElement>(null);

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

    let width = 0;
    let height = 0;
    let columns = 0;
    let cols: Column[] = [];
    let rafId: number | null = null;
    let lastTime = performance.now();
    let resizeTimer: number | null = null;

    const setupCanvas = () => {
      const dpr = Math.min(window.devicePixelRatio || 1, 2);
      // Measure the element's own CSS box (width:100% + fixed inset:0) rather
      // than window.innerWidth — innerWidth includes the scrollbar gutter and
      // would over-size the bitmap on scrollbar-present layouts.
      width = canvas.clientWidth || window.innerWidth;
      height = canvas.clientHeight || window.innerHeight;
      canvas.width = Math.floor(width * dpr);
      canvas.height = Math.floor(height * dpr);
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
      ctx.font = `${COL_WIDTH}px monospace`;
      ctx.textBaseline = 'top';

      columns = Math.max(1, Math.floor((width / COL_WIDTH) * density));
      cols = new Array(columns);
      const rowsApprox = Math.ceil(height / COL_WIDTH) + 2;
      for (let i = 0; i < columns; i++) {
        cols[i] = {
          y: -Math.floor(Math.random() * rowsApprox),
          step: 0.5 + Math.random() * 0.8,
        };
      }
      // Keep the canvas transparent so it overlays the UI without dimming it.
      ctx.clearRect(0, 0, width, height);
    };

    const draw = () => {
      // Fade older chars toward transparent (destination-out) instead of
      // filling with a solid trail colour — keeps the canvas alpha-composited.
      ctx.globalCompositeOperation = 'destination-out';
      ctx.fillStyle = 'rgba(0, 0, 0, 0.08)';
      ctx.fillRect(0, 0, width, height);
      ctx.globalCompositeOperation = 'source-over';

      const effSpeed = Math.max(0.1, speed);
      const colPitch = columns > 0 ? width / columns : COL_WIDTH;
      for (let i = 0; i < columns; i++) {
        const c = cols[i];
        if (!c) continue;
        const x = Math.floor(i * colPitch);
        const yPx = Math.floor(c.y) * COL_WIDTH;

        if (c.y >= 0) {
          ctx.fillStyle = HEAD_COLOR;
          ctx.fillText(randChar(), x, yPx);
          if (c.y >= 1) {
            ctx.fillStyle = SECONDARY_COLOR;
            ctx.fillText(randChar(), x, yPx - COL_WIDTH);
          }
          if (c.y >= 2) {
            ctx.fillStyle = DEEP_COLOR;
            ctx.fillText(randChar(), x, yPx - COL_WIDTH * 2);
          }
        }

        c.y += c.step * effSpeed;

        const passedBottom = c.y * COL_WIDTH > height;
        if (passedBottom && (Math.random() < 0.025 || c.y * COL_WIDTH > height + COL_WIDTH * 20)) {
          c.y = -1 - Math.floor(Math.random() * 8);
          c.step = 0.5 + Math.random() * 0.8;
        }
      }
    };

    const loop = (now: number) => {
      rafId = window.requestAnimationFrame(loop);
      if (document.hidden) return;
      const elapsed = now - lastTime;
      if (elapsed < FRAME_BUDGET) return;
      lastTime = now - (elapsed % FRAME_BUDGET);
      draw();
    };

    const start = () => {
      if (rafId != null) return;
      lastTime = performance.now();
      rafId = window.requestAnimationFrame(loop);
    };
    const stop = () => {
      if (rafId != null) {
        window.cancelAnimationFrame(rafId);
        rafId = null;
      }
    };
    const onVisibility = () => {
      if (document.hidden) stop();
      else start();
    };
    const onResize = () => {
      if (resizeTimer) window.clearTimeout(resizeTimer);
      resizeTimer = window.setTimeout(setupCanvas, 150);
    };

    setupCanvas();
    if (!document.hidden) start();
    document.addEventListener('visibilitychange', onVisibility);
    window.addEventListener('resize', onResize);

    return () => {
      stop();
      if (resizeTimer) window.clearTimeout(resizeTimer);
      document.removeEventListener('visibilitychange', onVisibility);
      window.removeEventListener('resize', onResize);
    };
  }, [enabled, density, speed]);

  if (!enabled) return null;

  return (
    <canvas
      ref={canvasRef}
      aria-hidden="true"
      style={{
        position: 'fixed',
        inset: 0,
        width: '100%',
        height: '100%',
        zIndex: 0,
        pointerEvents: 'none',
        display: 'block',
        opacity,
      }}
    />
  );
}
