import { useEffect, useRef } from 'react';
import { useWebSocket } from '../../hooks/useWebSocket';
import { useForge } from '../../context/ForgeContext';
import { useMatrixRain } from '../../context/MatrixRainContext';
import {
  FORGE_RAIN_STRINGS,
  pickMysticWord,
  wordColumnSpan,
} from '../../help/matrixRainEffects';

const KATAKANA =
  'アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲン';
const HEX = '0123456789ABCDEFabcdef';
const SYMBOLS = '!@#$%^&*<>/?|\\~';
const ALPHABET = KATAKANA + HEX + SYMBOLS;

const FONT_SIZE = 10;

interface Column {
  y: number;
  speed: number;
  liveSrc: string;
  livePos: number;
}

interface WordDrop {
  text: string;
  colStart: number;
  y: number;
  speed: number;
}

export default function MatrixRain() {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const wrapRef = useRef<HTMLDivElement>(null);
  const { agents, recentShares, commandResults } = useWebSocket();
  const { forging, stage } = useForge();
  const { crucibleFocus } = useMatrixRain();

  const forgingRef = useRef(false);
  const crucibleRef = useRef(false);
  const stageRef = useRef('');
  forgingRef.current = forging;
  crucibleRef.current = crucibleFocus;
  stageRef.current = stage;

  const livePoolRef = useRef<string[]>([]);
  useEffect(() => {
    const pool: string[] = [];
    for (const a of agents) {
      pool.push(a.id.replace(/-/g, ''));
      if (a.hashrate_15s > 0) pool.push(`${a.hashrate_15s.toFixed(0)}H`);
      if (a.ip) pool.push(a.ip.replace(/\./g, ''));
    }
    for (const s of recentShares.slice(0, 8)) {
      if (s.hash) pool.push(s.hash.replace(/[^a-fA-F0-9]/g, '').slice(0, 24));
    }
    for (const r of (commandResults ?? []).slice(-5)) {
      if (r.action) pool.push(r.action.toUpperCase().padEnd(8, '_'));
    }
    const intense = forgingRef.current || crucibleRef.current;
    if (intense) {
      pool.push(...FORGE_RAIN_STRINGS);
      if (forgingRef.current && stageRef.current) {
        pool.push(stageRef.current.replace(/[^A-Z0-9]/gi, '').toUpperCase().slice(0, 16));
      }
      if (crucibleRef.current) {
        pool.push('CRUCIBLE', 'TARGET', 'EXECUTE', 'REMOTE');
      }
    }
    livePoolRef.current = pool.length > 0 ? pool : ['AETHERFORGE', 'MINING', '00E5FF'];
  }, [agents, recentShares, commandResults, forging, crucibleFocus]);

  const eventLogRef = useRef<{ text: string; alpha: number }[]>([]);
  const prevShareLen = useRef(0);
  const prevAgentLen = useRef(0);
  const prevCmdSeq = useRef(0);

  useEffect(() => {
    const newEvents: string[] = [];
    if (recentShares.length > prevShareLen.current) {
      const s = recentShares[0];
      newEvents.push(`SHARE ${s.accepted ? 'OK' : 'REJECT'} ${s.agent_id?.slice(0, 6) ?? '??'}`);
    }
    prevShareLen.current = recentShares.length;
    if (agents.length > prevAgentLen.current) {
      const a = agents[agents.length - 1];
      newEvents.push(`AGENT ONLINE ${a.name?.slice(0, 8) ?? '??'}`);
    }
    prevAgentLen.current = agents.length;

    const results = commandResults ?? [];
    if (results.length > 0) {
      const latest = results[results.length - 1];
      if (latest._seq > prevCmdSeq.current) {
        prevCmdSeq.current = latest._seq;
        const tag = latest.success ? 'CMD OK' : 'CMD FAIL';
        newEvents.push(`${tag} ${latest.action?.toUpperCase() ?? '?'}`);
      }
    }

    for (const ev of newEvents) {
      eventLogRef.current.push({ text: ev, alpha: 1 });
      if (eventLogRef.current.length > 6) eventLogRef.current.shift();
    }
  }, [recentShares, agents, commandResults]);

  const intenseMode = forging || crucibleFocus;
  const wrapClass = intenseMode
    ? `matrix-rain-wrap matrix-rain-wrap--intense${crucibleFocus && !forging ? ' matrix-rain-wrap--crucible' : ''}${forging ? ' matrix-rain-wrap--forge' : ''}`
    : 'matrix-rain-wrap';

  useEffect(() => {
    const canvas = canvasRef.current;
    const wrap = wrapRef.current;
    if (!canvas || !wrap) return;
    const ctx = canvas.getContext('2d');
    if (!ctx) return;

    const resize = () => {
      const r = wrap.getBoundingClientRect();
      canvas.width = Math.floor(r.width);
      canvas.height = Math.floor(r.height);
    };
    resize();
    const ro = new ResizeObserver(resize);
    ro.observe(wrap);

    let cols: Column[] = [];
    const wordDropsRef: WordDrop[] = [];

    const resetCols = () => {
      const numCols = Math.max(1, Math.floor(canvas.width / FONT_SIZE));
      cols = Array.from({ length: numCols }, () => ({
        y: Math.random() * -(canvas.height * 2),
        speed: 0.3 + Math.random() * 0.55,
        liveSrc: '',
        livePos: 0,
      }));
    };
    resetCols();

    const spawnWordDrop = () => {
      if (cols.length < 4) return;
      const text = pickMysticWord();
      const span = wordColumnSpan(text);
      const colStart = Math.floor(Math.random() * Math.max(1, cols.length - span));
      wordDropsRef.push({
        text,
        colStart,
        y: -span - 2,
        speed: 0.35 + Math.random() * 0.25,
      });
    };

    const injectInterval = setInterval(() => {
      const pool = livePoolRef.current;
      if (pool.length === 0 || cols.length === 0) return;
      const colIdx = Math.floor(Math.random() * cols.length);
      const src = pool[Math.floor(Math.random() * pool.length)];
      cols[colIdx].liveSrc = src;
      cols[colIdx].livePos = 0;
    }, 180);

    const wordInterval = setInterval(() => {
      if (!forgingRef.current) spawnWordDrop();
    }, 7000 + Math.random() * 5000);

    spawnWordDrop();

    let raf: number;
    let lastTime = 0;

    const drawWordDrop = (wd: WordDrop, speedMult: number, intense: boolean) => {
      const H = canvas.height;
      let lastCharIdx = wd.text.length - 1;
      while (lastCharIdx >= 0 && wd.text[lastCharIdx] === ' ') lastCharIdx--;

      let colIdx = 0;
      for (let i = 0; i < wd.text.length; i++) {
        const ch = wd.text[i];
        if (ch === ' ') {
          colIdx++;
          continue;
        }
        const row = wd.y - (wd.text.length - 1 - i);
        const py = row * FONT_SIZE;
        if (py < -FONT_SIZE || py > H + FONT_SIZE) {
          colIdx++;
          continue;
        }
        const x = (wd.colStart + colIdx) * FONT_SIZE;
        const isHead = i === lastCharIdx;

        if (intense) {
          ctx.fillStyle = isHead ? 'rgba(255,235,120,1)' : 'rgba(255,140,0,0.85)';
        } else {
          ctx.fillStyle = isHead ? 'rgba(220,120,255,1)' : 'rgba(140,0,200,0.55)';
        }
        ctx.fillText(ch, x, py);
        colIdx++;
      }
      wd.y += wd.speed * speedMult;
    };

    const draw = (ts: number) => {
      raf = requestAnimationFrame(draw);
      const intense = forgingRef.current || crucibleRef.current;
      const targetFps = intense ? 50 : 24;
      const msPerFrame = 1000 / targetFps;
      if (ts - lastTime < msPerFrame) return;
      lastTime = ts;

      const W = canvas.width;
      const H = canvas.height;

      ctx.fillStyle = intense ? 'rgba(0,0,0,0.10)' : 'rgba(0,0,0,0.18)';
      ctx.fillRect(0, 0, W, H);

      ctx.font = `${FONT_SIZE}px 'Courier New', monospace`;

      const speedMult = intense ? 3.2 : 1.0;

      for (let i = 0; i < cols.length; i++) {
        const col = cols[i];
        const x = i * FONT_SIZE;
        const y = col.y;

        let ch: string;
        if (col.liveSrc && col.livePos < col.liveSrc.length) {
          ch = col.liveSrc[col.livePos];
          col.livePos++;
        } else {
          ch = ALPHABET[Math.floor(Math.random() * ALPHABET.length)];
        }

        if (intense) {
          ctx.fillStyle = 'rgba(255,220,80,0.98)';
          ctx.fillText(ch, x, y * FONT_SIZE);
          if (y > 1) {
            ctx.fillStyle = '#ff8c00';
            ctx.fillText(
              ALPHABET[Math.floor(Math.random() * ALPHABET.length)],
              x,
              (y - 1) * FONT_SIZE
            );
          }
          if (Math.random() < 0.14) {
            const dimY = Math.floor(Math.random() * Math.max(1, y - 2));
            ctx.fillStyle = 'rgba(255,120,0,0.4)';
            ctx.fillText(
              ALPHABET[Math.floor(Math.random() * ALPHABET.length)],
              x,
              dimY * FONT_SIZE
            );
          }
        } else {
          ctx.fillStyle = 'rgba(255,255,255,0.95)';
          ctx.fillText(ch, x, y * FONT_SIZE);
          if (y > 1) {
            ctx.fillStyle = '#00ff41';
            ctx.fillText(
              ALPHABET[Math.floor(Math.random() * ALPHABET.length)],
              x,
              (y - 1) * FONT_SIZE
            );
          }
          if (Math.random() < 0.04) {
            const dimY = Math.floor(Math.random() * Math.max(1, y - 2));
            ctx.fillStyle = 'rgba(0,180,60,0.22)';
            ctx.fillText(
              ALPHABET[Math.floor(Math.random() * ALPHABET.length)],
              x,
              dimY * FONT_SIZE
            );
          }
        }

        col.y += col.speed * speedMult;
        if (col.y * FONT_SIZE > H && Math.random() > 0.96) {
          col.y = Math.random() * -20;
          col.speed = 0.3 + Math.random() * 0.55;
          col.liveSrc = '';
          col.livePos = 0;
        }
      }

      for (let w = wordDropsRef.length - 1; w >= 0; w--) {
        drawWordDrop(wordDropsRef[w], speedMult, intense);
        if (wordDropsRef[w].y * FONT_SIZE > H + 40) {
          wordDropsRef.splice(w, 1);
        }
      }
      if (wordDropsRef.length < 3 && Math.random() < 0.02) {
        spawnWordDrop();
      }

      const logs = eventLogRef.current;
      const lineH = FONT_SIZE + 2;
      ctx.font = `${FONT_SIZE - 1}px 'Courier New', monospace`;
      for (let j = 0; j < logs.length; j++) {
        const entry = logs[logs.length - 1 - j];
        const oy = H - 6 - j * lineH;
        if (oy < 0) break;
        const fail = entry.text.includes('FAIL') || entry.text.includes('REJECT');
        const logColor = intense
          ? `rgba(255,160,0,${(entry.alpha * 0.75).toFixed(2)})`
          : fail
            ? `rgba(255,80,80,${(entry.alpha * 0.65).toFixed(2)})`
            : `rgba(0,255,65,${(entry.alpha * 0.55).toFixed(2)})`;
        ctx.fillStyle = logColor;
        ctx.fillText(`> ${entry.text}`, 4, oy);
        entry.alpha = Math.max(0, entry.alpha - 0.003);
      }
    };

    raf = requestAnimationFrame(draw);

    return () => {
      cancelAnimationFrame(raf);
      clearInterval(injectInterval);
      clearInterval(wordInterval);
      ro.disconnect();
    };
  }, []);

  return (
    <div ref={wrapRef} className={wrapClass} aria-hidden="true">
      <canvas ref={canvasRef} className="matrix-rain-canvas" />
      <div className="matrix-rain-scanlines" />
      <div className="matrix-rain-vignette-top" />
      <div className="matrix-rain-vignette-btm" />
    </div>
  );
}
