'use client';

import { useEffect, useRef, useCallback } from 'react';
import { useReducedMotion } from '@/hooks/use-reduced-motion';

interface InkDataMorphProps {
  className?: string;
}

interface TrailPoint {
  x: number;
  y: number;
}

interface Particle {
  x: number;
  y: number;
  vx: number;
  vy: number;
  radius: number;
  initialRadius: number;
  dataRadius: number;
  opacity: number;
  toneIndex: number;
  rotation: number;
  scaleX: number;
  scaleY: number;
  isSplash: boolean;
  phase: 'spreading' | 'settling' | 'morphing' | 'complete' | 'fading';
  spreadTime: number;
  maxSpreadTime: number;
  settleTime: number;
  morphProgress: number;
  targetX: number;
  targetY: number;
  delay: number;
  age: number;
  trail: TrailPoint[];
  seed1: number;
  seed2: number;
  wobbleFactor: number;
  prevVx: number;
  prevVy: number;
  inkLayerCount: number;
}

interface ResponsiveConfig {
  particleCount: number;
  centers: { x: number; y: number }[];
  centerDistribution: number[];
  targetXRange: [number, number];
  targetYRange: [number, number];
  connectionDistance: number;
  glowScale: number;
  gradientLayers: number;
  maxTrail: number;
  splashRatio: number;
  bgOrbScale: number;
  inkLayers: number;
  inkRadiusScale: number;
  inkStringThreshold: number;
  showFeibai: boolean;
  paperTextureSize: number;
  wobbleDetail: number;
}

const TONES = [
  { hex: '#1C1C1C', rgb: '28, 28, 28', weight: 0.15 },
  { hex: '#4A4A4A', rgb: '74, 74, 74', weight: 0.25 },
  { hex: '#8C8C8C', rgb: '140, 140, 140', weight: 0.30 },
  { hex: '#C41E3A', rgb: '196, 30, 58', weight: 0.10 },
  { hex: '#E8707A', rgb: '232, 112, 122', weight: 0.20 },
] as const;

const ACCENT_TONES = [3, 4];

function pickToneIndex(isSplash: boolean): number {
  if (isSplash) {
    return Math.random() < 0.4 ? 0 : 1;
  }
  const r = Math.random();
  let cumulative = 0;
  for (let i = 0; i < TONES.length; i++) {
    const tone = TONES[i];
    if (!tone) {
      continue;
    }
    cumulative += tone.weight;
    if (r < cumulative) {
      return i;
    }
  }
  return 2;
}

function getResponsiveConfig(W: number): ResponsiveConfig {
  if (W < 768) {
    return {
      particleCount: 70,
      centers: [{ x: 0.65, y: 0.3 }],
      centerDistribution: [1],
      targetXRange: [0.55, 0.9],
      targetYRange: [0.1, 0.5],
      connectionDistance: 60,
      glowScale: 0.5,
      gradientLayers: 2,
      maxTrail: 2,
      splashRatio: 0,
      bgOrbScale: 0.45,
      inkLayers: 3,
      inkRadiusScale: 1.2,
      inkStringThreshold: 0.35,
      showFeibai: false,
      paperTextureSize: 128,
      wobbleDetail: 16,
    };
  }
  if (W < 1024) {
    return {
      particleCount: 120,
      centers: [
        { x: 0.68, y: 0.3 },
        { x: 0.3, y: 0.6 },
      ],
      centerDistribution: [0.65, 0.35],
      targetXRange: [0.5, 0.9],
      targetYRange: [0.12, 0.6],
      connectionDistance: 80,
      glowScale: 0.75,
      gradientLayers: 2,
      maxTrail: 4,
      splashRatio: 0.05,
      bgOrbScale: 0.7,
      inkLayers: 3,
      inkRadiusScale: 1.1,
      inkStringThreshold: 0.4,
      showFeibai: true,
      paperTextureSize: 128,
      wobbleDetail: 20,
    };
  }
  return {
    particleCount: 180,
    centers: [
      { x: 0.72, y: 0.3 },
      { x: 0.25, y: 0.65 },
    ],
    centerDistribution: [0.67, 0.33],
    targetXRange: [0.5, 0.9],
    targetYRange: [0.12, 0.65],
    connectionDistance: 100,
    glowScale: 1.0,
    gradientLayers: 3,
    maxTrail: 6,
    splashRatio: 0.1,
    bgOrbScale: 1.0,
    inkLayers: 4,
    inkRadiusScale: 1.0,
    inkStringThreshold: 0.5,
    showFeibai: true,
    paperTextureSize: 256,
    wobbleDetail: 24,
  };
}

function easeInOutCubic(t: number): number {
  return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
}

function easeOutCubic(t: number): number {
  return 1 - Math.pow(1 - t, 3);
}

function lerp(a: number, b: number, t: number): number {
  return a + (b - a) * t;
}

function updateParticle(p: Particle, maxTrail: number): void {
  p.age++;
  if (p.age < p.delay) {
    return;
  }

  if (p.phase === 'spreading') {
    if (maxTrail > 0) {
      p.trail.push({ x: p.x, y: p.y });
      if (p.trail.length > maxTrail) {
        p.trail.shift();
      }
    }

    p.x += p.vx;
    p.y += p.vy;
    p.vx *= 0.98;
    p.vy *= 0.98;
    p.spreadTime++;

    const spreadRatio = p.spreadTime / p.maxSpreadTime;
    if (spreadRatio < 0.3) {
      p.radius = p.initialRadius * (1 + spreadRatio * 2);
    } else {
      p.radius = p.initialRadius * lerp(1.6, 1, easeOutCubic((spreadRatio - 0.3) / 0.7));
    }

    if (p.spreadTime >= p.maxSpreadTime) {
      if (p.isSplash) {
        p.phase = 'fading';
      } else {
        p.phase = 'settling';
      }
    }
  } else if (p.phase === 'settling') {
    p.settleTime++;
    p.opacity = Math.max(0.2, p.opacity - 0.002);
    p.x += (Math.random() - 0.5) * 0.15;
    p.y += (Math.random() - 0.5) * 0.15;
    if (p.settleTime > 60) {
      p.phase = 'morphing';
    }
  } else if (p.phase === 'morphing') {
    p.morphProgress = Math.min(1, p.morphProgress + 0.006);
    const t = easeInOutCubic(p.morphProgress);
    p.x = lerp(p.x, p.targetX, t * 0.025);
    p.y = lerp(p.y, p.targetY, t * 0.025);
    p.radius = lerp(p.radius, p.dataRadius, t * 0.02);
    p.opacity = lerp(p.opacity, 0.35, t * 0.008);
    if (p.morphProgress >= 1) {
      p.phase = 'complete';
    }
  } else if (p.phase === 'complete') {
    p.opacity = lerp(p.opacity, 0.35, 0.01);
  } else if (p.phase === 'fading') {
    p.opacity -= 0.01;
    p.radius *= 0.98;
    if (p.opacity <= 0) {
      p.opacity = 0;
      p.phase = 'complete';
    }
  }
}

function drawTrail(
  ctx: CanvasRenderingContext2D,
  p: Particle,
  rgb: string,
): void {
  if (p.trail.length < 2) {
    return;
  }

  ctx.save();
  ctx.lineCap = 'round';
  for (let i = 1; i < p.trail.length; i++) {
    const prev = p.trail[i - 1];
    const curr = p.trail[i];
    if (!prev || !curr) {
      continue;
    }
    const trailAlpha = (i / p.trail.length) * p.opacity * 0.3;
    const trailWidth = (i / p.trail.length) * p.radius * 0.8;
    ctx.beginPath();
    ctx.moveTo(prev.x, prev.y);
    ctx.lineTo(curr.x, curr.y);
    ctx.strokeStyle = `rgba(${rgb}, ${trailAlpha})`;
    ctx.lineWidth = Math.max(0.3, trailWidth);
    ctx.stroke();
  }
  ctx.restore();
}

function drawInkDot(
  ctx: CanvasRenderingContext2D,
  x: number,
  y: number,
  r: number,
  rotation: number,
  scaleX: number,
  scaleY: number,
  rgb: string,
  opacity: number,
): void {
  ctx.save();
  ctx.translate(x, y);
  ctx.rotate(rotation);
  ctx.beginPath();
  ctx.ellipse(0, 0, Math.max(0.5, r * scaleX), Math.max(0.5, r * scaleY), 0, 0, Math.PI * 2);
  ctx.fillStyle = `rgba(${rgb}, ${opacity})`;
  ctx.fill();
  ctx.restore();
}

function drawParticle(
  ctx: CanvasRenderingContext2D,
  p: Particle,
  time: number,
  config: ResponsiveConfig,
): void {
  if (p.age < p.delay || p.opacity <= 0) {
    return;
  }

  const tone = TONES[p.toneIndex];
  if (!tone) {
    return;
  }
  const rgb = tone.rgb;
  const r = Math.max(0.5, p.radius);
  const gs = config.glowScale;

  if (p.phase === 'spreading') {
    drawTrail(ctx, p, rgb);

    if (config.gradientLayers >= 3) {
      const grad3 = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, r * 5 * gs);
      grad3.addColorStop(0, `rgba(${rgb}, ${p.opacity * 0.06})`);
      grad3.addColorStop(0.5, `rgba(${rgb}, ${p.opacity * 0.02})`);
      grad3.addColorStop(1, 'transparent');
      ctx.fillStyle = grad3;
      ctx.fillRect(p.x - r * 5 * gs, p.y - r * 5 * gs, r * 10 * gs, r * 10 * gs);
    }

    const grad2R = r * 2.8 * gs;
    const grad2 = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, grad2R);
    grad2.addColorStop(0, `rgba(${rgb}, ${p.opacity * 0.2})`);
    grad2.addColorStop(0.6, `rgba(${rgb}, ${p.opacity * 0.06})`);
    grad2.addColorStop(1, 'transparent');
    ctx.fillStyle = grad2;
    ctx.fillRect(p.x - grad2R, p.y - grad2R, grad2R * 2, grad2R * 2);

    const grad1 = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, r);
    grad1.addColorStop(0, `rgba(${rgb}, ${p.opacity * 0.9})`);
    grad1.addColorStop(0.7, `rgba(${rgb}, ${p.opacity * 0.4})`);
    grad1.addColorStop(1, 'transparent');
    ctx.fillStyle = grad1;
    ctx.beginPath();
    ctx.arc(p.x, p.y, r, 0, Math.PI * 2);
    ctx.fill();

    drawInkDot(ctx, p.x, p.y, r * 0.6, p.rotation, p.scaleX, p.scaleY, rgb, p.opacity * 0.7);
  } else if (p.phase === 'settling') {
    const grad2R = r * 2.2 * gs;
    const grad2 = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, grad2R);
    grad2.addColorStop(0, `rgba(${rgb}, ${p.opacity * 0.15})`);
    grad2.addColorStop(0.5, `rgba(${rgb}, ${p.opacity * 0.05})`);
    grad2.addColorStop(1, 'transparent');
    ctx.fillStyle = grad2;
    ctx.fillRect(p.x - grad2R, p.y - grad2R, grad2R * 2, grad2R * 2);

    const grad1 = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, r);
    grad1.addColorStop(0, `rgba(${rgb}, ${p.opacity * 0.85})`);
    grad1.addColorStop(0.6, `rgba(${rgb}, ${p.opacity * 0.3})`);
    grad1.addColorStop(1, 'transparent');
    ctx.fillStyle = grad1;
    ctx.beginPath();
    ctx.arc(p.x, p.y, r, 0, Math.PI * 2);
    ctx.fill();

    drawInkDot(ctx, p.x, p.y, r * 0.5, p.rotation, p.scaleX, p.scaleY, rgb, p.opacity * 0.5);
  } else if (p.phase === 'fading') {
    const grad1 = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, r * 1.5);
    grad1.addColorStop(0, `rgba(${rgb}, ${p.opacity * 0.6})`);
    grad1.addColorStop(1, 'transparent');
    ctx.fillStyle = grad1;
    ctx.beginPath();
    ctx.arc(p.x, p.y, r * 1.5, 0, Math.PI * 2);
    ctx.fill();
  } else {
    const pulse = 1 + Math.sin(time * 0.002 + p.targetX * 0.01) * 0.15;
    const glowR = r * 3 * pulse * gs;

    const grad2 = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, glowR);
    grad2.addColorStop(0, `rgba(${rgb}, ${p.opacity * 0.12})`);
    grad2.addColorStop(0.4, `rgba(${rgb}, ${p.opacity * 0.04})`);
    grad2.addColorStop(1, 'transparent');
    ctx.fillStyle = grad2;
    ctx.fillRect(p.x - glowR, p.y - glowR, glowR * 2, glowR * 2);

    drawInkDot(ctx, p.x, p.y, r, p.rotation, p.scaleX, p.scaleY, rgb, p.opacity);
  }
}

function drawConnections(
  ctx: CanvasRenderingContext2D,
  particles: Particle[],
  connectionDistance: number,
): void {
  const morphed = particles.filter(
    p => p.phase === 'morphing' && p.morphProgress > 0.3
  );
  ctx.save();
  ctx.lineCap = 'round';
  for (let i = 0; i < morphed.length; i++) {
    const a = morphed[i];
    if (!a) {
      continue;
    }
    const toneA = TONES[a.toneIndex];
    if (!toneA) {
      continue;
    }
    for (let j = i + 1; j < morphed.length; j++) {
      const b = morphed[j];
      if (!b) {
        continue;
      }
      const toneB = TONES[b.toneIndex];
      if (!toneB) {
        continue;
      }
      const dx = a.x - b.x;
      const dy = a.y - b.y;
      const dist = Math.sqrt(dx * dx + dy * dy);
      if (dist < connectionDistance && dist > 0) {
        const progress = Math.min(a.morphProgress, b.morphProgress);
        const distRatio = 1 - dist / connectionDistance;
        const alpha = distRatio * 0.12 * progress;
        const lineWidth = 0.3 + distRatio * 0.7;

        const useAccent = ACCENT_TONES.includes(a.toneIndex) || ACCENT_TONES.includes(b.toneIndex);
        const connRgb = useAccent ? TONES[3].rgb : TONES[0].rgb;

        const mx = (a.x + b.x) / 2;
        const my = (a.y + b.y) / 2;
        const offset = dist * 0.1;
        const cpx = mx + (dy / dist) * offset;
        const cpy = my - (dx / dist) * offset;

        ctx.beginPath();
        ctx.moveTo(a.x, a.y);
        ctx.quadraticCurveTo(cpx, cpy, b.x, b.y);
        ctx.strokeStyle = `rgba(${connRgb}, ${alpha})`;
        ctx.lineWidth = lineWidth;
        ctx.stroke();
      }
    }
  }
  ctx.restore();
}

function drawBgOrbs(
  ctx: CanvasRenderingContext2D,
  W: number,
  H: number,
  orbScale: number,
): void {
  const orbs = [
    { x: W * 0.72, y: H * 0.28, r: 350 * orbScale, rgb: TONES[3].rgb, alpha: 0.04 },
    { x: W * 0.28, y: H * 0.68, r: 280 * orbScale, rgb: TONES[0].rgb, alpha: 0.03 },
    { x: W * 0.5, y: H * 0.45, r: 400 * orbScale, rgb: TONES[4].rgb, alpha: 0.015 },
    { x: W * 0.85, y: H * 0.7, r: 200 * orbScale, rgb: TONES[1].rgb, alpha: 0.02 },
  ];
  for (const o of orbs) {
    const grad = ctx.createRadialGradient(o.x, o.y, 0, o.x, o.y, o.r);
    grad.addColorStop(0, `rgba(${o.rgb}, ${o.alpha})`);
    grad.addColorStop(0.6, `rgba(${o.rgb}, ${o.alpha * 0.3})`);
    grad.addColorStop(1, 'transparent');
    ctx.fillStyle = grad;
    ctx.fillRect(o.x - o.r, o.y - o.r, o.r * 2, o.r * 2);
  }
}

function createParticle(
  cx: number,
  cy: number,
  W: number,
  H: number,
  delay: number,
  config: ResponsiveConfig,
): Particle {
  const angle = Math.random() * Math.PI * 2;
  const dist = Math.random() * 8;
  const isSplash = Math.random() < config.splashRatio;
  const speed = isSplash
    ? 1.5 + Math.random() * 3
    : 0.4 + Math.random() * 1.8;

  const toneIndex = pickToneIndex(isSplash);
  const isHeavy = toneIndex === 0 || toneIndex === 3;

  const initialRadius = isSplash
    ? 0.5 + Math.random() * 1.5
    : 2 + Math.random() * 5;

  const [txMin, txMax] = config.targetXRange;
  const [tyMin, tyMax] = config.targetYRange;

  return {
    x: cx + Math.cos(angle) * dist,
    y: cy + Math.sin(angle) * dist,
    vx: Math.cos(angle) * speed,
    vy: Math.sin(angle) * speed,
    radius: initialRadius,
    initialRadius,
    dataRadius: isSplash ? 0 : 1.2 + Math.random() * 2.5,
    opacity: isSplash ? 0.6 + Math.random() * 0.3 : 0.5 + Math.random() * 0.4,
    toneIndex,
    rotation: Math.random() * Math.PI * 2,
    scaleX: isHeavy ? 0.8 + Math.random() * 0.2 : 0.5 + Math.random() * 0.4,
    scaleY: isHeavy ? 0.8 + Math.random() * 0.2 : 0.5 + Math.random() * 0.4,
    isSplash,
    phase: 'spreading',
    spreadTime: 0,
    maxSpreadTime: isSplash ? 30 + Math.random() * 40 : 70 + Math.random() * 90,
    settleTime: 0,
    morphProgress: 0,
    targetX: W * (txMin + Math.random() * (txMax - txMin)),
    targetY: H * (tyMin + Math.random() * (tyMax - tyMin)),
    delay,
    age: 0,
    trail: [],
    seed1: Math.random() * Math.PI * 2,
    seed2: Math.random() * Math.PI * 2,
    wobbleFactor: 0.3,
    prevVx: 0,
    prevVy: 0,
    inkLayerCount: config.inkLayers,
  };
}

function initParticlesArray(W: number, H: number, config: ResponsiveConfig): Particle[] {
  const particles: Particle[] = [];
  let remaining = config.particleCount;

  for (let c = 0; c < config.centers.length; c++) {
    const centerDef = config.centers[c];
    if (!centerDef) {
      continue;
    }
    const cx = W * centerDef.x;
    const cy = H * centerDef.y;
    const dist = config.centerDistribution[c] ?? 0;
    const count = c === config.centers.length - 1
      ? remaining
      : Math.floor(config.particleCount * dist);
    remaining -= count;

    const baseDelay = c * 25;
    for (let i = 0; i < count; i++) {
      particles.push(
        createParticle(cx, cy, W, H, baseDelay + Math.random() * 20, config)
      );
    }
  }

  return particles;
}

export function InkDataMorph({
  className = '',
}: InkDataMorphProps) {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const particlesRef = useRef<Particle[]>([]);
  const animationRef = useRef<number | undefined>(undefined);
  const startTimeRef = useRef<number>(0);
  const configRef = useRef<ResponsiveConfig | null>(null);
  const shouldReduceMotion = useReducedMotion();

  const drawStaticFinal = useCallback(() => {
    const canvas = canvasRef.current;
    if (!canvas) {
      return;
    }
    const ctx = canvas.getContext('2d');
    if (!ctx) {
      return;
    }
    const config = configRef.current;
    if (!config) {
      return;
    }

    const dpr = canvas.width / (canvas.clientWidth || 1);
    const W = canvas.width / dpr;
    const H = canvas.height / dpr;

    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    ctx.clearRect(0, 0, W, H);
    ctx.fillStyle = '#FAFAF5';
    ctx.fillRect(0, 0, W, H);

    drawBgOrbs(ctx, W, H, config.bgOrbScale);

    const finalParticles = particlesRef.current
      .filter(p => !p.isSplash)
      .map(p => ({
        ...p,
        x: p.targetX,
        y: p.targetY,
        radius: p.dataRadius,
        opacity: 0.35,
        phase: 'complete' as const,
        trail: [],
      }));

    for (const p of finalParticles) {
      drawParticle(ctx, p, 0, config);
    }

    drawConnections(ctx, finalParticles, config.connectionDistance);
  }, []);

  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) {
      return;
    }

    const parent = canvas.parentElement;
    if (!parent) {
      return;
    }

    const initCanvas = () => {
      if (animationRef.current !== undefined) {
        cancelAnimationFrame(animationRef.current);
        animationRef.current = undefined;
      }

      const W = parent.clientWidth;
      const H = parent.clientHeight;
      const dpr = Math.min(window.devicePixelRatio || 1, 2);
      canvas.width = W * dpr;
      canvas.height = H * dpr;

      const config = getResponsiveConfig(W);
      configRef.current = config;
      particlesRef.current = initParticlesArray(W, H, config);

      if (shouldReduceMotion) {
        drawStaticFinal();
        return;
      }

      startTimeRef.current = performance.now();

      const animate = () => {
        const ctx = canvas.getContext('2d');
        if (!ctx) {
          return;
        }

        const time = performance.now() - startTimeRef.current;

        ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
        ctx.clearRect(0, 0, W, H);
        ctx.fillStyle = '#FAFAF5';
        ctx.fillRect(0, 0, W, H);

        drawBgOrbs(ctx, W, H, config.bgOrbScale);

        const particles = particlesRef.current;
        let allComplete = true;

        for (const p of particles) {
          updateParticle(p, config.maxTrail);
          drawParticle(ctx, p, time, config);
          if (p.phase !== 'complete') {
            allComplete = false;
          }
        }

        drawConnections(ctx, particles, config.connectionDistance);

        if (allComplete) {
          animationRef.current = undefined;
          return;
        }

        animationRef.current = requestAnimationFrame(animate);
      };

      animationRef.current = requestAnimationFrame(animate);
    };

    initCanvas();

    let resizeTimer: ReturnType<typeof setTimeout> | undefined;
    const handleResize = () => {
      if (resizeTimer !== undefined) {
        clearTimeout(resizeTimer);
      }
      resizeTimer = setTimeout(initCanvas, 300);
    };

    window.addEventListener('resize', handleResize);

    return () => {
      window.removeEventListener('resize', handleResize);
      if (resizeTimer !== undefined) {
        clearTimeout(resizeTimer);
      }
      if (animationRef.current !== undefined) {
        cancelAnimationFrame(animationRef.current);
      }
    };
  }, [shouldReduceMotion, drawStaticFinal]);

  return (
    <canvas
      ref={canvasRef}
      className={`absolute inset-0 w-full h-full pointer-events-none ${className}`}
      aria-hidden="true"
    />
  );
}
