"use client";

import * as React from "react";
import { cn } from "@/lib/utils";
import { useAudioAnalyser } from "@/hooks/use-audio-analyser";
import {
  adaptPalette,
  cyclicColor,
  isDarkTheme,
  readPalette,
  rgba,
} from "@/lib/visualizer-palette";

export type NebulaVisualizerProps = {
  /** 是否激活：true 时采集麦克风并随音频律动，false 时显示静态呼吸态 */
  active?: boolean;
  /** 外部分析器；提供后组件不再自行申请麦克风 */
  analyser?: AnalyserNode | null;
  /** 外部音频流；提供后用它构建分析器，而不调用 getUserMedia */
  stream?: MediaStream | null;
  /** 画布直径（px） */
  size?: number;
  /** 粒子数量 */
  particleCount?: number;
  /** 申请麦克风失败时回调 */
  onError?: (error: unknown) => void;
  className?: string;
};

type Particle = {
  /** 当前角度（rad） */
  ang: number;
  /** 角速度（rad/s，带方向） */
  vel: number;
  /** 基础轨道半径（占画布尺寸比例） */
  baseR: number;
  /** 呼吸相位偏移 */
  phase: number;
  /** 基础粒径（px @220 画布） */
  sz: number;
  /** 在调色板上的取色位置 */
  hue: number;
  /** 平滑后的所在频段能量 */
  v: number;
};

/**
 * 星云：一群沿环形轨道缓慢漂移的发光粒子，带运动拖尾。
 * 静态时如星环般缓慢呼吸流转；激活后粒子按所在方位
 * 对应的频段能量加速、外扩、增亮。
 */
export function NebulaVisualizer({
  active = false,
  analyser = null,
  stream = null,
  size = 220,
  particleCount = 140,
  onError,
  className,
}: NebulaVisualizerProps) {
  const canvasRef = React.useRef<HTMLCanvasElement>(null);
  const particlesRef = React.useRef<Particle[]>([]);
  const analyserRef = useAudioAnalyser({ active, analyser, stream, onError });

  React.useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext("2d");
    if (!ctx) return;

    const dpr = Math.min(window.devicePixelRatio || 1, 2);
    canvas.width = size * dpr;
    canvas.height = size * dpr;
    ctx.scale(dpr, dpr);

    const TAU = Math.PI * 2;
    if (particlesRef.current.length !== particleCount) {
      particlesRef.current = Array.from({ length: particleCount }, () => ({
        ang: Math.random() * TAU,
        vel: (0.08 + Math.random() * 0.22) * (Math.random() < 0.5 ? -1 : 1),
        baseR: 0.27 + Math.random() * 0.15,
        phase: Math.random() * TAU,
        sz: 0.7 + Math.random() * 1.5,
        hue: Math.random(),
        v: 0,
      }));
    }
    const particles = particlesRef.current;

    const cx = size / 2;
    const cy = size / 2;
    const scale = size / 220;
    const freq = new Uint8Array(256);
    const dt = 0.016;

    let raf = 0;
    let t = 0;
    let energy = 0;

    const draw = () => {
      t += dt;
      const dark = isDarkTheme();
      const palette = adaptPalette(readPalette(canvas), dark);
      const { sky, lav } = palette;

      const node = analyserRef.current;
      let level = 0;
      if (node) {
        node.getByteFrequencyData(freq);
        const bins = Math.floor(freq.length * 0.6);
        let sum = 0;
        for (let i = 0; i < bins; i++) sum += freq[i];
        level = sum / bins / 255;
      }
      energy += (level - energy) * (level > energy ? 0.3 : 0.08);

      const breathe = 0.5 + 0.5 * Math.sin(t * 0.8);

      // 用 destination-out 让上一帧整体淡出，留下运动拖尾
      ctx.globalCompositeOperation = "destination-out";
      ctx.fillStyle = "rgba(0, 0, 0, 0.16)";
      ctx.fillRect(0, 0, size, size);
      ctx.globalCompositeOperation = "source-over";

      // 中心柔光逐帧低强度补画，与淡出达到稳态平衡
      const glowR = size * (0.16 + 0.02 * breathe) * (1 + energy * 0.6);
      const glow = ctx.createRadialGradient(cx, cy, 0, cx, cy, glowR * 2);
      glow.addColorStop(0, rgba(sky, 0.045 + energy * 0.09));
      glow.addColorStop(0.6, rgba(lav, 0.02 + energy * 0.04));
      glow.addColorStop(1, rgba(lav, 0));
      ctx.fillStyle = glow;
      ctx.fillRect(0, 0, size, size);

      for (const p of particles) {
        p.ang += p.vel * dt * (1 + energy * 2.2);

        // 粒子方位映射到频段（左右镜像，低频在顶部）
        const a01 = (((p.ang + Math.PI / 2) % TAU) + TAU) % TAU / TAU;
        const m = a01 < 0.5 ? a01 * 2 : (1 - a01) * 2;
        let target = 0;
        if (node) {
          const bin = Math.floor(Math.pow(m, 1.5) * freq.length * 0.6);
          target = Math.pow(freq[bin] / 255, 1.3);
        }
        p.v += (target - p.v) * (target > p.v ? 0.3 : 0.1);

        const wobble =
          0.016 * Math.sin(t * 0.9 + p.phase) + 0.014 * (breathe - 0.5);
        const rad = (p.baseR + wobble + p.v * 0.1) * size;
        const x = cx + Math.cos(p.ang) * rad;
        const y = cy + Math.sin(p.ang) * rad;

        const color = cyclicColor(palette, p.hue + t * 0.02);
        const lum =
          0.3 + 0.2 * (0.5 + 0.5 * Math.sin(t * 1.3 + p.phase)) + 0.55 * p.v;
        ctx.fillStyle = rgba(color, Math.min(1, lum + (dark ? 0 : 0.12)));
        ctx.shadowColor = rgba(color, 0.7);
        ctx.shadowBlur = 3 + p.v * 12;
        ctx.beginPath();
        ctx.arc(x, y, p.sz * scale * (1 + p.v * 1.4), 0, TAU);
        ctx.fill();
      }
      ctx.shadowBlur = 0;

      raf = requestAnimationFrame(draw);
    };

    raf = requestAnimationFrame(draw);
    return () => cancelAnimationFrame(raf);
  }, [size, particleCount, analyserRef]);

  return (
    <canvas
      ref={canvasRef}
      role="img"
      aria-label="麦克风音频可视化（星云）"
      style={{ width: size, height: size }}
      className={cn("select-none", className)}
    />
  );
}
