"use client";

import { CSSProperties, useEffect, useRef, useState } from "react";
import * as THREE from "three";
import { useDashboardSelection } from "@/hooks/useDashboardStore";
import { usePrefersReducedMotion } from "@/hooks/usePrefersReducedMotion";
import { getWeatherAuraProfile } from "@/lib/weather-aura";
import styles from "./Dashboard.module.css";

function hexToRgba(hex: string, alpha: number) {
  const sanitized = hex.replace("#", "");
  const normalized =
    sanitized.length === 3
      ? sanitized
          .split("")
          .map((char) => `${char}${char}`)
          .join("")
      : sanitized.padEnd(6, "0");
  const numeric = Number.parseInt(normalized, 16);
  const r = (numeric >> 16) & 255;
  const g = (numeric >> 8) & 255;
  const b = numeric & 255;
  return `rgba(${r}, ${g}, ${b}, ${alpha})`;
}

export function WeatherAuraLayer() {
  const { cities, selectedDetail } = useDashboardSelection();
  const prefersReducedMotion = usePrefersReducedMotion();
  const [isDesktop, setIsDesktop] = useState(false);
  const containerRef = useRef<HTMLDivElement | null>(null);

  const aura = getWeatherAuraProfile(selectedDetail, cities);

  useEffect(() => {
    if (typeof window === "undefined" || !window.matchMedia) {
      return;
    }

    const mediaQuery = window.matchMedia("(min-width: 1024px)");
    const apply = () => {
      setIsDesktop(mediaQuery.matches);
    };

    apply();
    mediaQuery.addEventListener("change", apply);
    return () => {
      mediaQuery.removeEventListener("change", apply);
    };
  }, []);

  useEffect(() => {
    const host = containerRef.current;
    if (!host || !isDesktop || prefersReducedMotion) {
      return;
    }

    const renderer = new THREE.WebGLRenderer({
      alpha: true,
      antialias: true,
      powerPreference: "low-power",
    });
    renderer.setClearColor(0x000000, 0);
    renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 1.5));

    const scene = new THREE.Scene();
    const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0.1, 10);
    camera.position.z = 2;

    const clock = new THREE.Clock();
    const cleanupMaterials = new Set<THREE.Material>();
    const particleGroups: Array<{
      kind: "flow" | "rain" | "snow" | "fog" | "cloud";
      geometry: THREE.BufferGeometry;
      positions: Float32Array;
      baseY: Float32Array;
      drift: Float32Array;
      phase: Float32Array;
      material: THREE.Material;
      mesh: THREE.Object3D;
    }> = [];
    const effectLights: THREE.Light[] = [];
    const flashOverlay = new THREE.Mesh(
      new THREE.PlaneGeometry(2.2, 2.2),
      new THREE.MeshBasicMaterial({
        color: new THREE.Color("#e0f2fe"),
        transparent: true,
        opacity: 0,
        blending: THREE.AdditiveBlending,
      }),
    );
    flashOverlay.position.z = -0.3;
    scene.add(flashOverlay);
    cleanupMaterials.add(flashOverlay.material as THREE.Material);

    function createParticleField(
      count: number,
      pointSize: number,
      opacity: number,
      depthShift: number,
    ) {
      const geometry = new THREE.BufferGeometry();
      const positions = new Float32Array(count * 3);
      const colors = new Float32Array(count * 3);
      const baseY = new Float32Array(count);
      const drift = new Float32Array(count);
      const phase = new Float32Array(count);
      const primaryColor = new THREE.Color(aura.primary);
      const secondaryColor = new THREE.Color(aura.secondary);
      const tertiaryColor = new THREE.Color(aura.tertiary);

      for (let index = 0; index < count; index += 1) {
        const offset = index * 3;
        const x = Math.random() * 2.6 - 1.3;
        const y = Math.random() * 1.8 - 0.9;
        const z = (Math.random() * 0.8 - 0.4) + depthShift;
        positions[offset] = x;
        positions[offset + 1] = y;
        positions[offset + 2] = z;
        baseY[index] = y;
        drift[index] = (0.00045 + Math.random() * 0.0012) * aura.drift;
        phase[index] = Math.random() * Math.PI * 2;

        const mixedColor = primaryColor
          .clone()
          .lerp(secondaryColor, Math.random() * 0.65)
          .lerp(tertiaryColor, Math.random() * 0.4);
        colors[offset] = mixedColor.r;
        colors[offset + 1] = mixedColor.g;
        colors[offset + 2] = mixedColor.b;
      }

      geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
      geometry.setAttribute("color", new THREE.BufferAttribute(colors, 3));

      const material = new THREE.PointsMaterial({
        size: pointSize,
        transparent: true,
        opacity,
        vertexColors: true,
        depthWrite: false,
        blending: THREE.AdditiveBlending,
        sizeAttenuation: true,
      });

      const points = new THREE.Points(geometry, material);
      scene.add(points);
      cleanupMaterials.add(material);
      particleGroups.push({
        geometry,
        positions,
        baseY,
        drift,
        phase,
        kind: "flow",
        material,
        mesh: points,
      });
    }

    function createRainField(count: number) {
      const geometry = new THREE.BufferGeometry();
      const positions = new Float32Array(count * 3);
      const baseY = new Float32Array(count);
      const drift = new Float32Array(count);
      const phase = new Float32Array(count);

      for (let index = 0; index < count; index += 1) {
        const offset = index * 3;
        positions[offset] = Math.random() * 2.8 - 1.4;
        positions[offset + 1] = Math.random() * 2.2 - 1.1;
        positions[offset + 2] = Math.random() * 0.4 - 0.2;
        baseY[index] = positions[offset + 1];
        drift[index] = (0.018 + Math.random() * 0.016) * aura.effectIntensity;
        phase[index] = 0.004 + Math.random() * 0.004;
      }

      geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
      const material = new THREE.PointsMaterial({
        size: 0.018,
        color: new THREE.Color("#7dd3fc"),
        transparent: true,
        opacity: 0.72,
        depthWrite: false,
        blending: THREE.AdditiveBlending,
      });
      const points = new THREE.Points(geometry, material);
      scene.add(points);
      cleanupMaterials.add(material);
      particleGroups.push({
        geometry,
        positions,
        baseY,
        drift,
        phase,
        kind: "rain",
        material,
        mesh: points,
      });
    }

    function createSnowField(count: number) {
      const geometry = new THREE.BufferGeometry();
      const positions = new Float32Array(count * 3);
      const baseY = new Float32Array(count);
      const drift = new Float32Array(count);
      const phase = new Float32Array(count);

      for (let index = 0; index < count; index += 1) {
        const offset = index * 3;
        positions[offset] = Math.random() * 2.8 - 1.4;
        positions[offset + 1] = Math.random() * 2.1 - 1.05;
        positions[offset + 2] = Math.random() * 0.35 - 0.18;
        baseY[index] = positions[offset + 1];
        drift[index] = (0.0045 + Math.random() * 0.0045) * aura.effectIntensity;
        phase[index] = Math.random() * Math.PI * 2;
      }

      geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
      const material = new THREE.PointsMaterial({
        size: 0.024,
        color: new THREE.Color("#f8fafc"),
        transparent: true,
        opacity: 0.85,
        depthWrite: false,
        blending: THREE.AdditiveBlending,
      });
      const points = new THREE.Points(geometry, material);
      scene.add(points);
      cleanupMaterials.add(material);
      particleGroups.push({
        geometry,
        positions,
        baseY,
        drift,
        phase,
        kind: "snow",
        material,
        mesh: points,
      });
    }

    function createFogField(count: number) {
      const geometry = new THREE.BufferGeometry();
      const positions = new Float32Array(count * 3);
      const baseY = new Float32Array(count);
      const drift = new Float32Array(count);
      const phase = new Float32Array(count);

      for (let index = 0; index < count; index += 1) {
        const offset = index * 3;
        positions[offset] = Math.random() * 2.6 - 1.3;
        positions[offset + 1] = Math.random() * 0.9 - 0.45;
        positions[offset + 2] = Math.random() * 0.45 - 0.2;
        baseY[index] = positions[offset + 1];
        drift[index] = (0.0014 + Math.random() * 0.001) * aura.effectIntensity;
        phase[index] = Math.random() * Math.PI * 2;
      }

      geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
      const material = new THREE.PointsMaterial({
        size: 0.12,
        color: new THREE.Color("#cbd5e1"),
        transparent: true,
        opacity: 0.18,
        depthWrite: false,
        blending: THREE.AdditiveBlending,
      });
      const points = new THREE.Points(geometry, material);
      scene.add(points);
      cleanupMaterials.add(material);
      particleGroups.push({
        geometry,
        positions,
        baseY,
        drift,
        phase,
        kind: "fog",
        material,
        mesh: points,
      });
    }

    function createCloudField(count: number) {
      const geometry = new THREE.BufferGeometry();
      const positions = new Float32Array(count * 3);
      const baseY = new Float32Array(count);
      const drift = new Float32Array(count);
      const phase = new Float32Array(count);

      for (let index = 0; index < count; index += 1) {
        const offset = index * 3;
        positions[offset] = Math.random() * 2.7 - 1.35;
        positions[offset + 1] = Math.random() * 0.75 + 0.1;
        positions[offset + 2] = Math.random() * 0.3 - 0.15;
        baseY[index] = positions[offset + 1];
        drift[index] = (0.0012 + Math.random() * 0.0009) * aura.effectIntensity;
        phase[index] = Math.random() * Math.PI * 2;
      }

      geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
      const material = new THREE.PointsMaterial({
        size: 0.1,
        color: new THREE.Color("#dbeafe"),
        transparent: true,
        opacity: 0.13,
        depthWrite: false,
        blending: THREE.AdditiveBlending,
      });
      const points = new THREE.Points(geometry, material);
      scene.add(points);
      cleanupMaterials.add(material);
      particleGroups.push({
        geometry,
        positions,
        baseY,
        drift,
        phase,
        kind: "cloud",
        material,
        mesh: points,
      });
    }

    createParticleField(90, 0.018, aura.particleOpacity * 0.9, -0.1);
    createParticleField(60, 0.026, aura.particleOpacity * 0.65, 0.08);

    if (aura.effect === "rain" || aura.effect === "storm") {
      createRainField(aura.effect === "storm" ? 240 : 170);
    } else if (aura.effect === "snow") {
      createSnowField(150);
    } else if (aura.effect === "fog") {
      createFogField(90);
    } else if (aura.effect === "cloud" || aura.effect === "wind") {
      createCloudField(aura.effect === "wind" ? 80 : 54);
    }

    if (aura.effect === "storm") {
      const flashLight = new THREE.PointLight(0xdbeafe, 0, 6, 2);
      flashLight.position.set(0.2, 0.9, 1.1);
      scene.add(flashLight);
      effectLights.push(flashLight);
    }

    const resize = () => {
      const width = host.clientWidth || window.innerWidth;
      const height = host.clientHeight || window.innerHeight;
      renderer.setSize(width, height, false);
    };

    resize();
    host.appendChild(renderer.domElement);

    let frameId = 0;
    let lastFrameAt = 0;

    const renderFrame = (timestamp: number) => {
      frameId = window.requestAnimationFrame(renderFrame);
      if (timestamp - lastFrameAt < 40) {
        return;
      }
      lastFrameAt = timestamp;

      const elapsed = clock.getElapsedTime();
      for (const field of particleGroups) {
        for (let index = 0; index < field.baseY.length; index += 1) {
          const offset = index * 3;
          if (field.kind === "flow") {
            let nextX = field.positions[offset] + field.drift[index];
            if (nextX > 1.35) {
              nextX = -1.35;
              field.baseY[index] = Math.random() * 1.8 - 0.9;
            }
            field.positions[offset] = nextX;
            field.positions[offset + 1] =
              field.baseY[index] +
              Math.sin(elapsed * 0.45 + field.phase[index] + nextX * 2.4) *
                0.06 *
                aura.intensity;
          } else if (field.kind === "rain") {
            let nextY = field.positions[offset + 1] - field.drift[index];
            let nextX = field.positions[offset] + field.phase[index] * aura.drift;
            if (nextY < -1.12 || nextX > 1.45) {
              nextY = 1.15 + Math.random() * 0.25;
              nextX = Math.random() * 2.9 - 1.45;
            }
            field.positions[offset] = nextX;
            field.positions[offset + 1] = nextY;
          } else if (field.kind === "snow") {
            let nextY = field.positions[offset + 1] - field.drift[index];
            let nextX =
              field.positions[offset] +
              Math.sin(elapsed * 0.9 + field.phase[index]) * 0.0024 * aura.effectIntensity;
            if (nextY < -1.1) {
              nextY = 1.12 + Math.random() * 0.2;
              nextX = Math.random() * 2.8 - 1.4;
            }
            field.positions[offset] = nextX;
            field.positions[offset + 1] = nextY;
          } else if (field.kind === "fog" || field.kind === "cloud") {
            let nextX = field.positions[offset] + field.drift[index];
            if (nextX > 1.38) {
              nextX = -1.38;
              field.baseY[index] =
                field.kind === "cloud"
                  ? Math.random() * 0.75 + 0.1
                  : Math.random() * 0.9 - 0.45;
            }
            field.positions[offset] = nextX;
            field.positions[offset + 1] =
              field.baseY[index] +
              Math.sin(elapsed * 0.3 + field.phase[index]) *
                (field.kind === "cloud" ? 0.03 : 0.05);
          }
        }

        field.geometry.attributes.position.needsUpdate = true;
      }

      if (effectLights.length > 0) {
        const flashPulse = Math.max(0, Math.sin(elapsed * 2.1) - 0.78) * 20;
        for (const light of effectLights) {
          if (light instanceof THREE.PointLight) {
            light.intensity = flashPulse;
          }
        }
        (flashOverlay.material as THREE.MeshBasicMaterial).opacity = Math.min(
          0.18,
          flashPulse / 120,
        );
      } else {
        (flashOverlay.material as THREE.MeshBasicMaterial).opacity = 0;
      }

      renderer.render(scene, camera);
    };

    frameId = window.requestAnimationFrame(renderFrame);
    window.addEventListener("resize", resize);

    return () => {
      window.cancelAnimationFrame(frameId);
      window.removeEventListener("resize", resize);
      for (const child of [...scene.children]) {
        scene.remove(child);
      }
      for (const field of particleGroups) {
        field.geometry.dispose();
      }
      cleanupMaterials.forEach((material) => material.dispose());
      renderer.dispose();
      if (renderer.domElement.parentNode === host) {
        host.removeChild(renderer.domElement);
      }
    };
  }, [
    aura.drift,
    aura.intensity,
    aura.particleOpacity,
    aura.primary,
    aura.secondary,
    aura.tertiary,
    aura.effect,
    aura.effectIntensity,
    isDesktop,
    prefersReducedMotion,
  ]);

  if (!isDesktop) {
    return null;
  }

  const overlayStyle = {
    backgroundImage: [
      `radial-gradient(circle at 18% 22%, ${hexToRgba(aura.primary, 0.18 * aura.intensity)}, transparent 32%)`,
      `radial-gradient(circle at 78% 20%, ${hexToRgba(aura.secondary, 0.14 * aura.intensity)}, transparent 34%)`,
      `radial-gradient(circle at 52% 78%, ${hexToRgba(aura.tertiary, 0.12 * aura.intensity)}, transparent 38%)`,
      aura.effect === "rain" || aura.effect === "storm"
        ? `linear-gradient(180deg, ${hexToRgba("#6FB7FF", 0.06 * aura.effectIntensity)}, transparent 45%)`
        : aura.effect === "snow"
          ? `linear-gradient(180deg, ${hexToRgba("#e2e8f0", 0.06 * aura.effectIntensity)}, transparent 45%)`
          : aura.effect === "fog"
            ? `radial-gradient(circle at 50% 56%, ${hexToRgba("#cbd5e1", 0.08 * aura.effectIntensity)}, transparent 60%)`
            : aura.effect === "cloud"
              ? `linear-gradient(180deg, ${hexToRgba("#dbeafe", 0.04 * aura.effectIntensity)}, transparent 40%)`
              : "none",
    ].join(", "),
  } as CSSProperties;

  return (
    <div
      ref={containerRef}
      aria-hidden="true"
      className={styles.weatherAura}
      data-reduced-motion={prefersReducedMotion ? "true" : "false"}
      style={overlayStyle}
    >
      <div className={styles.weatherAuraScrim} />
    </div>
  );
}
