"use client";

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

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

/**
 * 水平频谱：一排沿中线上下对称伸展的圆头光柱，左低频、右高频。
 *
 * 整体结构是经典的「音乐播放器频谱」，但做了三处柔化处理，
 * 让它与波形 / 光环 / 星云共享同一套视觉语言：
 *
 * 1. 柱体上下镜像，且两端经窗函数收束——轮廓是一枚梭形，而不是一块矩形；
 * 2. 颜色沿横轴铺 sky → lavender → rose 渐变，与波形模式的着色完全一致；
 * 3. 静态时不归零，而是保持低幅呼吸 + 一道缓慢游走的涟漪，表示「在聆听」。
 *
 * 数据通路：AnalyserNode.getByteFrequencyData() 拿到 0~255 的频谱幅值，
 * 经「频段映射 → 增益曲线 → 快攻慢放平滑」三步，变成每根柱的高度。
 */
export function SpectrumVisualizer({
  active = false,
  analyser = null,
  stream = null,
  size = 220,
  barCount = 24,
  onError,
  className,
}: SpectrumVisualizerProps) {
  const canvasRef = React.useRef<HTMLCanvasElement>(null);
  // 每根柱平滑后的当前高度（0~1），跨帧保留才能做缓动
  const smoothRef = React.useRef<Float32Array>(new Float32Array(barCount));
  const analyserRef = useAudioAnalyser({ active, analyser, stream, onError });

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

    // ====================================================================
    // 一、画布初始化
    // ====================================================================
    // 物理像素 = CSS 像素 × dpr，再用 scale 把坐标系换算回 CSS 像素，
    // 这样后面所有绘制都按 CSS 尺寸写，又能在高分屏上保持清晰。
    // dpr 封顶 2：3x 屏的额外清晰度肉眼难辨，填充率却翻倍不止。
    const dpr = Math.min(window.devicePixelRatio || 1, 2);
    canvas.width = size * dpr;
    canvas.height = size * dpr;
    ctx.scale(dpr, dpr);

    // barCount 变化时重建平滑数组（旧值作废，从 0 重新长出来即可）
    if (smoothRef.current.length !== barCount) {
      smoothRef.current = new Float32Array(barCount);
    }
    const smooth = smoothRef.current;

    // ====================================================================
    // 二、布局常量
    // ====================================================================
    const cy = size / 2; // 中线：柱体以它为轴上下对称
    const pad = size * 0.09; // 左右留白，与波形模式的构图对齐
    const span = size - pad * 2; // 频谱实际占据的横向宽度
    const slot = span / barCount; // 每根柱占据的横向槽位
    const barW = Math.max(2, slot * 0.5); // 柱宽：柱与空隙各占一半，排列才透气
    const maxHalf = size * 0.27; // 满音量时柱体向上（向下同）最多伸多高
    const minHalf = size * 0.012; // 静音时也保留一个小圆点，排列不会断

    // 分析器 fftSize=512 → 256 个频率 bin（见 use-audio-analyser 的默认值）
    const freq = new Uint8Array(256);
    // 只取低 70% 的 bin：48kHz 采样下高频段几乎只有噪声，
    // 人声与音乐的主能量都集中在前段
    const usableBins = Math.floor(freq.length * 0.7);

    // 两端收束的窗函数：x∈[0,1] → 中间 1、两端 0。
    // 幂次 0.6 让肩部更平缓，收束只发生在最边缘，
    // 整排柱的包络因此呈梭形——与波形模式的 taper 同源
    const taper = (x: number) => Math.sin(Math.PI * x) ** 0.6;

    // 沿横轴铺三色渐变：左 sky、中 lavender、右 rose（与波形模式一致）
    const colorAt = ({ sky, lav, rose }: Palette, p: number): RGB =>
      p < 0.5 ? mix(sky, lav, p * 2) : mix(lav, rose, (p - 0.5) * 2);

    let raf = 0;
    let t = 0; // 累计时间（秒），驱动呼吸与涟漪

    // ====================================================================
    // 三、帧循环
    // ====================================================================
    const draw = () => {
      t += 0.016;

      // 每帧读主题：用户切换明暗模式时无需重建组件即可换色
      const dark = isDarkTheme();
      const palette = adaptPalette(readPalette(canvas), dark);

      const node = analyserRef.current;
      if (node) node.getByteFrequencyData(freq);

      const breathe = 0.5 + 0.5 * Math.sin(t * 1.1); // 0~1 的慢呼吸

      // ---- 1. 计算每根柱的目标高度并平滑 --------------------------------
      for (let i = 0; i < barCount; i++) {
        const p = i / (barCount - 1); // 本柱在横轴上的位置 0~1

        let target: number;
        if (node) {
          // 频段映射：p^1.7 是一条凹曲线，把更多柱分给低频段。
          // 听觉对频率近似对数感知，线性均分会让左边几根柱挤下
          // 全部人声、右边大片柱常年趴零
          const bin = Math.floor(Math.pow(p, 1.7) * usableBins);
          // 增益曲线：幅值^1.25 轻微压低小信号，
          // 环境底噪不再让整排柱毛毛躁躁，说话时的起伏反而更分明
          target = Math.pow(freq[bin] / 255, 1.25);
        } else {
          // 静态呼吸态 = 基础高度 + 整体慢呼吸 + 一道向右游走的涟漪
          // （p*7 - t*1.6：相位随位置递增、随时间回退，视觉上波峰右移）
          target =
            0.05 +
            0.04 * breathe +
            0.045 * (0.5 + 0.5 * Math.sin(p * 7 - t * 1.6));
        }

        // 快攻慢放：上升快（0.4）让节拍打得动，下降慢（0.12）让回落
        // 像余韵而不是抽搐。这是音频表 ballistics 的经典做法
        const k = target > smooth[i] ? 0.4 : 0.12;
        smooth[i] += (target - smooth[i]) * k;
      }

      ctx.clearRect(0, 0, size, size);

      // ---- 2. 频谱柱 ----------------------------------------------------
      ctx.lineCap = "round"; // 圆头：柱体首尾都是半圆，最小高度时就是一个圆点
      ctx.lineWidth = barW;
      for (let i = 0; i < barCount; i++) {
        const p = i / (barCount - 1);
        const v = smooth[i];
        const x = pad + (i + 0.5) * slot;
        // 高度 = (保底圆点 + 音量伸展) × 两端收束
        const half = (minHalf + v * maxHalf) * taper(p);

        const color = colorAt(palette, p);
        // 透明度随高度走：安静的柱隐入背景，活跃的柱跳到前景
        ctx.strokeStyle = rgba(color, (dark ? 0.4 : 0.5) + v * 0.45);
        // 柔光也随高度走，响的柱晕染更大
        ctx.shadowColor = rgba(color, 0.6);
        ctx.shadowBlur = 4 + v * 14;
        ctx.beginPath();
        ctx.moveTo(x, cy - half);
        ctx.lineTo(x, cy + half);
        ctx.stroke();
      }
      ctx.shadowBlur = 0;

      raf = requestAnimationFrame(draw);
    };

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

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