"use client";

import { useTheme } from "next-themes";
import type React from "react";
import { useCallback, useEffect, useRef } from "react";

const PALETTES = {
  dark: {
    particles: [
      "rgba(255,255,255,0.70)",
      "rgba(255,255,255,0.45)",
      "rgba(180,180,180,0.50)",
      "rgba(200,200,200,0.35)",
      "rgba(255,255,255,0.22)",
    ],
    grainOpacity: 0.05,
  },
  light: {
    particles: [
      "rgba(0,0,0,0.55)",
      "rgba(0,0,0,0.35)",
      "rgba(60,60,60,0.40)",
      "rgba(80,80,80,0.25)",
      "rgba(0,0,0,0.18)",
    ],
    grainOpacity: 0.03,
  },
} as const;

interface Particle {
  angle: number;
  radius: number;
  speed: number;
  size: number;
  colorIndex: number;
  wobbleAmp: number;
  wobbleSpeed: number;
  wobblePhase: number;
}

interface CanvasSize {
  dpr: number;
  height: number;
  width: number;
}

interface AnimatedBackgroundContainerProps {
  children: React.ReactNode;
  className?: string;
  particleCount?: number;
  orbitRadius?: number;
  followSpeed?: number;
  mobileSpeed?: number;
}

const DEFAULT_PARTICLE_COLORS: readonly string[] = PALETTES.dark.particles;
const PARTICLE_COLOR_COUNT = DEFAULT_PARTICLE_COLORS.length;
const EDGE_FADE_DISTANCE = 80;
const MAX_DEVICE_PIXEL_RATIO = 2;
const MOBILE_TARGET_DISTANCE = 30;

const rand = (min: number, max: number) => Math.random() * (max - min) + min;

const isMobileDevice = () => {
  if (typeof window === "undefined") {
    return false;
  }

  return window.matchMedia("(pointer: coarse)").matches || window.innerWidth < 768;
};

const createParticle = (orbitRadius: number): Particle => {
  const minRadius = Math.max(10, orbitRadius * 0.12);

  return {
    angle: rand(0, Math.PI * 2),
    radius: rand(minRadius, orbitRadius),
    speed: rand(0.002, 0.003) * (Math.random() > 0.5 ? 1 : -1),
    size: rand(1.2, 4),
    colorIndex: Math.floor(rand(0, PARTICLE_COLOR_COUNT)),
    wobbleAmp: rand(orbitRadius * 0.025, orbitRadius * 0.12),
    wobbleSpeed: rand(0.008, 0.035),
    wobblePhase: rand(0, Math.PI * 2),
  };
};

export default function AnimatedBackgroundContainer({
  children,
  className = "",
  particleCount = 60,
  orbitRadius = 240,
  followSpeed = 0.06,
  mobileSpeed = 1,
}: AnimatedBackgroundContainerProps) {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const containerRef = useRef<HTMLDivElement>(null);
  const frameRef = useRef(0);
  const animationFrameRef = useRef<number | null>(null);
  const isMobileRef = useRef(false);
  const isVisibleRef = useRef(true);
  const prefersReducedMotionRef = useRef(false);
  const particlesRef = useRef<Particle[]>([]);
  const mousePosRef = useRef({ x: 0, y: 0 });
  const smoothMouseRef = useRef({ x: 0, y: 0 });
  const mobileAnchorRef = useRef({ x: 0, y: 0 });
  const mobileTargetRef = useRef({ x: 0, y: 0 });
  const canvasSizeRef = useRef<CanvasSize>({ dpr: 1, height: 0, width: 0 });
  const containerRectRef = useRef<DOMRect | null>(null);
  const followSpeedRef = useRef(followSpeed);
  const mobileSpeedRef = useRef(mobileSpeed);
  const particleColorsRef = useRef<readonly string[]>(DEFAULT_PARTICLE_COLORS);

  const { resolvedTheme } = useTheme();
  const isDark = resolvedTheme === undefined || resolvedTheme === "dark";
  const palette = isDark ? PALETTES.dark : PALETTES.light;

  useEffect(() => {
    particleColorsRef.current = palette.particles;
  }, [palette]);

  useEffect(() => {
    followSpeedRef.current = followSpeed;
  }, [followSpeed]);

  useEffect(() => {
    mobileSpeedRef.current = mobileSpeed;
  }, [mobileSpeed]);

  useEffect(() => {
    particlesRef.current = Array.from({ length: particleCount }, () =>
      createParticle(orbitRadius),
    );
  }, [particleCount, orbitRadius]);

  const seedPositions = useCallback(() => {
    const { height, width } = canvasSizeRef.current;
    if (width === 0 || height === 0) {
      return;
    }

    const centerX = width / 2;
    const centerY = height / 2;

    mousePosRef.current = { x: centerX, y: centerY };
    smoothMouseRef.current = { x: centerX, y: centerY };
    mobileAnchorRef.current = { x: centerX, y: centerY };
    mobileTargetRef.current = {
      x: rand(centerX * 0.4, centerX * 1.6),
      y: rand(centerY * 0.4, centerY * 1.6),
    };
  }, []);

  const updateContainerRect = useCallback(() => {
    const container = containerRef.current;
    if (container) {
      containerRectRef.current = container.getBoundingClientRect();
    }
  }, []);

  const resizeCanvas = useCallback(() => {
    const canvas = canvasRef.current;
    const container = containerRef.current;
    if (!canvas || !container) {
      return;
    }

    const width = container.clientWidth;
    const height = container.clientHeight;
    const dpr = Math.min(window.devicePixelRatio || 1, MAX_DEVICE_PIXEL_RATIO);
    const nextWidth = Math.round(width * dpr);
    const nextHeight = Math.round(height * dpr);

    canvasSizeRef.current = { dpr, height, width };
    updateContainerRect();

    if (canvas.width !== nextWidth) {
      canvas.width = nextWidth;
    }
    if (canvas.height !== nextHeight) {
      canvas.height = nextHeight;
    }
    canvas.style.width = `${width}px`;
    canvas.style.height = `${height}px`;

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

    ctx.setTransform(1, 0, 0, 1, 0, 0);
    ctx.scale(dpr, dpr);
    seedPositions();
  }, [seedPositions, updateContainerRect]);

  useEffect(() => {
    isMobileRef.current = isMobileDevice();
    resizeCanvas();

    const handleResize = () => {
      isMobileRef.current = isMobileDevice();
      resizeCanvas();
    };

    const resizeObserver =
      "ResizeObserver" in window
        ? new ResizeObserver(() => {
            handleResize();
          })
        : null;

    if (containerRef.current && resizeObserver) {
      resizeObserver.observe(containerRef.current);
    }

    window.addEventListener("resize", handleResize);
    window.addEventListener("scroll", updateContainerRect, {
      capture: true,
      passive: true,
    });
    return () => {
      resizeObserver?.disconnect();
      window.removeEventListener("resize", handleResize);
      window.removeEventListener("scroll", updateContainerRect, { capture: true });
    };
  }, [resizeCanvas, updateContainerRect]);

  const handleMouseMove = useCallback((event: MouseEvent) => {
    const rect = containerRectRef.current;
    if (!rect || isMobileRef.current) {
      return;
    }

    mousePosRef.current = {
      x: event.clientX - rect.left,
      y: event.clientY - rect.top,
    };
  }, []);

  useEffect(() => {
    const container = containerRef.current;
    if (!container) {
      return;
    }

    container.addEventListener("mousemove", handleMouseMove, { passive: true });
    return () => container.removeEventListener("mousemove", handleMouseMove);
  }, [handleMouseMove]);

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

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

    const stopAnimation = () => {
      if (animationFrameRef.current !== null) {
        window.cancelAnimationFrame(animationFrameRef.current);
        animationFrameRef.current = null;
      }
    };

    const draw = () => {
      if (!isVisibleRef.current || prefersReducedMotionRef.current) {
        animationFrameRef.current = null;
        return;
      }

      const { height, width } = canvasSizeRef.current;
      if (width === 0 || height === 0) {
        animationFrameRef.current = window.requestAnimationFrame(draw);
        return;
      }

      frameRef.current += 1;

      if (isMobileRef.current) {
        const mobileLerp = 0.008 * mobileSpeedRef.current;
        mobileAnchorRef.current.x +=
          (mobileTargetRef.current.x - mobileAnchorRef.current.x) * mobileLerp;
        mobileAnchorRef.current.y +=
          (mobileTargetRef.current.y - mobileAnchorRef.current.y) * mobileLerp;

        const dx = mobileTargetRef.current.x - mobileAnchorRef.current.x;
        const dy = mobileTargetRef.current.y - mobileAnchorRef.current.y;
        if (Math.hypot(dx, dy) < MOBILE_TARGET_DISTANCE) {
          mobileTargetRef.current = {
            x: rand(width * 0.15, width * 0.85),
            y: rand(height * 0.15, height * 0.85),
          };
        }

        smoothMouseRef.current.x = mobileAnchorRef.current.x;
        smoothMouseRef.current.y = mobileAnchorRef.current.y;
      } else {
        const desktopLerp = followSpeedRef.current;
        smoothMouseRef.current.x +=
          (mousePosRef.current.x - smoothMouseRef.current.x) * desktopLerp;
        smoothMouseRef.current.y +=
          (mousePosRef.current.y - smoothMouseRef.current.y) * desktopLerp;
      }

      const centerX = smoothMouseRef.current.x;
      const centerY = smoothMouseRef.current.y;
      const colors = particleColorsRef.current;

      ctx.clearRect(0, 0, width, height);

      for (const particle of particlesRef.current) {
        particle.angle += particle.speed;

        const wobble =
          Math.sin(frameRef.current * particle.wobbleSpeed + particle.wobblePhase) *
          particle.wobbleAmp;
        const radius = particle.radius + wobble;
        const x = centerX + Math.cos(particle.angle) * radius;
        const y = centerY + Math.sin(particle.angle) * radius;

        const edgeFade = Math.max(
          0,
          Math.min(
            x / EDGE_FADE_DISTANCE,
            (width - x) / EDGE_FADE_DISTANCE,
            y / EDGE_FADE_DISTANCE,
            (height - y) / EDGE_FADE_DISTANCE,
            1,
          ),
        );

        if (edgeFade <= 0) {
          continue;
        }

        ctx.globalAlpha = edgeFade;
        ctx.fillStyle = colors[particle.colorIndex] ?? colors[0] ?? "#ffffff";
        ctx.beginPath();
        ctx.arc(x, y, particle.size, 0, Math.PI * 2);
        ctx.fill();
      }

      ctx.globalAlpha = 1;
      animationFrameRef.current = window.requestAnimationFrame(draw);
    };

    const startAnimation = () => {
      if (
        animationFrameRef.current === null &&
        isVisibleRef.current &&
        !prefersReducedMotionRef.current
      ) {
        animationFrameRef.current = window.requestAnimationFrame(draw);
      }
    };

    const handleVisibilityChange = () => {
      isVisibleRef.current = document.visibilityState === "visible";

      if (isVisibleRef.current) {
        startAnimation();
      } else {
        stopAnimation();
      }
    };

    const motionMedia = window.matchMedia("(prefers-reduced-motion: reduce)");
    const handleMotionChange = () => {
      prefersReducedMotionRef.current = motionMedia.matches;

      if (prefersReducedMotionRef.current) {
        stopAnimation();
        ctx.clearRect(0, 0, canvasSizeRef.current.width, canvasSizeRef.current.height);
      } else {
        startAnimation();
      }
    };

    isVisibleRef.current = document.visibilityState === "visible";
    prefersReducedMotionRef.current = motionMedia.matches;
    startAnimation();

    document.addEventListener("visibilitychange", handleVisibilityChange);
    motionMedia.addEventListener("change", handleMotionChange);

    return () => {
      document.removeEventListener("visibilitychange", handleVisibilityChange);
      motionMedia.removeEventListener("change", handleMotionChange);
      stopAnimation();
    };
  }, []);

  return (
    <div
      ref={containerRef}
      className={className}
      style={{
        position: "relative",
        minHeight: "100vh",
        width: "100%",
        overflow: "hidden",
        transition: "background-color 0.6s ease",
      }}
    >
      <canvas
        ref={canvasRef}
        aria-hidden
        style={{
          position: "absolute",
          inset: 0,
          zIndex: 0,
          pointerEvents: "none",
        }}
      />

      <div
        aria-hidden
        style={{
          position: "absolute",
          inset: 0,
          zIndex: 1,
          opacity: palette.grainOpacity,
          pointerEvents: "none",
          backgroundImage: `url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E")`,
          backgroundRepeat: "repeat",
          backgroundSize: "180px 180px",
        }}
      />

      <div style={{ position: "relative", zIndex: 2 }}>{children}</div>
    </div>
  );
}
