"use client";

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

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

const VERT = `
attribute vec2 a_pos;
void main() {
  gl_Position = vec4(a_pos, 0.0, 1.0);
}
`;

// 虹彩光环：一圈被 fbm 噪声轻微扰动、沿圆周流动三色虹彩的发光细环，
// 环内漂浮两团缓慢游走的柔光“流体”。静态时缓慢呼吸，
// 激活后由 u_volume 驱动半径、亮度与扰动幅度。
const FRAG = `
precision highp float;

uniform vec2 u_resolution;
uniform float u_time;
uniform float u_volume; // 0~1，已平滑
uniform float u_active; // 0 静态 / 1 激活
uniform float u_theme;  // 0 暗色 / 1 亮色
uniform vec3 u_c0;      // sky
uniform vec3 u_c1;      // lavender
uniform vec3 u_c2;      // rose

float hash(vec2 p) {
  p = fract(p * vec2(123.34, 456.21));
  p += dot(p, p + 45.32);
  return fract(p.x * p.y);
}

float noise(vec2 p) {
  vec2 i = floor(p);
  vec2 f = fract(p);
  vec2 u = f * f * (3.0 - 2.0 * f);
  return mix(
    mix(hash(i + vec2(0.0, 0.0)), hash(i + vec2(1.0, 0.0)), u.x),
    mix(hash(i + vec2(0.0, 1.0)), hash(i + vec2(1.0, 1.0)), u.x),
    u.y
  );
}

float fbm(vec2 p) {
  float v = 0.0;
  float a = 0.5;
  mat2 m = mat2(1.6, 1.2, -1.2, 1.6);
  for (int i = 0; i < 4; i++) {
    v += a * noise(p);
    p = m * p;
    a *= 0.5;
  }
  return v;
}

// 三色沿相位做环形虹彩插值（cos 加权，首尾无缝）
vec3 iridescent(float a) {
  float w0 = 0.5 + 0.5 * cos(a);
  float w1 = 0.5 + 0.5 * cos(a - 2.0943951);
  float w2 = 0.5 + 0.5 * cos(a - 4.1887902);
  return (u_c0 * w0 + u_c1 * w1 + u_c2 * w2) / (w0 + w1 + w2);
}

// 静态抖动，消除平滑渐变上的色带
float dither(vec2 p) {
  return (hash(p) - 0.5) / 255.0;
}

void main() {
  vec2 uv = (gl_FragCoord.xy - 0.5 * u_resolution) / min(u_resolution.x, u_resolution.y);
  float r = length(uv);
  float ang = atan(uv.y, uv.x);

  float vol = clamp(u_volume, 0.0, 1.0) * u_active;
  float breathe = 0.5 + 0.5 * sin(u_time * 0.7);

  // 基础半径：静态缓慢呼吸，说话时随音量扩张
  float radius = 0.30 + 0.02 * breathe + 0.085 * vol;

  // 双层噪声扰动出有机的环形轮廓
  vec2 dir = vec2(cos(ang), sin(ang));
  float n1 = fbm(dir * 1.7 + u_time * 0.14);
  float n2 = fbm(dir * 3.1 - u_time * 0.09 + 4.7);
  float rr = radius
    + (n1 - 0.5) * (0.028 + 0.07 * vol)
    + (n2 - 0.5) * (0.012 + 0.035 * vol);
  float d = r - rr;

  // 细亮的环芯 + 宽柔的辉光晕
  float coreW = 0.008 + 0.005 * vol + 0.002 * breathe;
  float core = exp(-d * d / (2.0 * coreW * coreW));
  float bloomW = 0.055 + 0.055 * vol;
  float bloom = exp(-d * d / (2.0 * bloomW * bloomW)) * 0.55;

  // 环内流体：两团缓慢游走的柔光
  vec2 p1 = 0.13 * vec2(cos(u_time * 0.41), sin(u_time * 0.33));
  vec2 p2 = 0.16 * vec2(cos(-u_time * 0.26 + 2.3), sin(u_time * 0.36 + 1.2));
  vec2 q1 = uv - p1;
  vec2 q2 = uv - p2;
  float b1 = exp(-dot(q1, q1) / 0.022);
  float b2 = exp(-dot(q2, q2) / 0.030);
  float inside = smoothstep(rr + 0.01, rr - 0.07, r);
  float neb = (0.8 * b1 + 0.65 * b2) * inside
    * (0.22 + 0.10 * breathe + 0.55 * vol);

  // 虹彩相位：沿圆周流动并被噪声轻微扭曲
  float flow = ang + u_time * 0.25 + (n1 - 0.5) * 2.2;
  vec3 ringCol = iridescent(flow);
  vec3 nebCol = mix(
    iridescent(u_time * 0.17 + 1.3),
    iridescent(-u_time * 0.11 + 3.9),
    0.5 + 0.5 * sin(u_time * 0.21)
  );

  float ringI = core * (0.9 + 0.85 * vol + 0.1 * breathe)
    + bloom * (0.35 + 0.6 * vol + 0.08 * breathe);
  vec3 hdr = ringCol * ringI + nebCol * neb;

  // 暗色：高光优雅泛白；亮色：保持饱和色，不向白过曝
  vec3 darkMap = vec3(1.0) - exp(-hdr * 1.55);
  vec3 lightMap = clamp(ringCol * min(ringI, 1.0) + nebCol * neb, 0.0, 1.0);
  vec3 mapped = mix(darkMap, lightMap, u_theme);
  mapped += dither(gl_FragCoord.xy);

  float alpha = clamp((ringI + neb) * mix(1.15, 1.35, u_theme), 0.0, 1.0);
  gl_FragColor = vec4(mapped, alpha);
}
`;

function compile(gl: WebGLRenderingContext, type: number, src: string) {
  const sh = gl.createShader(type);
  if (!sh) return null;
  gl.shaderSource(sh, src);
  gl.compileShader(sh);
  if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) {
    gl.deleteShader(sh);
    return null;
  }
  return sh;
}

const norm = ({ r, g, b }: RGB): [number, number, number] => [
  r / 255,
  g / 255,
  b / 255,
];

export function AuraVisualizer({
  active = false,
  analyser = null,
  stream = null,
  size = 220,
  onError,
  className,
}: AuraVisualizerProps) {
  const canvasRef = React.useRef<HTMLCanvasElement>(null);
  const volumeRef = React.useRef(0);
  const activeRef = React.useRef(active);
  const analyserRef = useAudioAnalyser({ active, analyser, stream, onError });

  React.useEffect(() => {
    activeRef.current = active;
  }, [active]);

  React.useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const gl = canvas.getContext("webgl", {
      alpha: true,
      premultipliedAlpha: false,
      antialias: true,
    });
    if (!gl) return;

    const vs = compile(gl, gl.VERTEX_SHADER, VERT);
    const fs = compile(gl, gl.FRAGMENT_SHADER, FRAG);
    const prog = gl.createProgram();
    if (!vs || !fs || !prog) return;
    gl.attachShader(prog, vs);
    gl.attachShader(prog, fs);
    gl.linkProgram(prog);
    if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) return;
    gl.useProgram(prog);

    const buf = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, buf);
    gl.bufferData(
      gl.ARRAY_BUFFER,
      new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]),
      gl.STATIC_DRAW,
    );
    const aPos = gl.getAttribLocation(prog, "a_pos");
    gl.enableVertexAttribArray(aPos);
    gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0);

    const uRes = gl.getUniformLocation(prog, "u_resolution");
    const uTime = gl.getUniformLocation(prog, "u_time");
    const uVol = gl.getUniformLocation(prog, "u_volume");
    const uActive = gl.getUniformLocation(prog, "u_active");
    const uTheme = gl.getUniformLocation(prog, "u_theme");
    const uC0 = gl.getUniformLocation(prog, "u_c0");
    const uC1 = gl.getUniformLocation(prog, "u_c1");
    const uC2 = gl.getUniformLocation(prog, "u_c2");

    const dpr = Math.min(window.devicePixelRatio || 1, 2);
    const px = Math.round(size * dpr);
    canvas.width = px;
    canvas.height = px;
    gl.viewport(0, 0, px, px);
    gl.uniform2f(uRes, px, px);

    gl.enable(gl.BLEND);
    gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);

    const freq = new Uint8Array(256);
    let raf = 0;
    const start = performance.now();

    const draw = () => {
      const t = (performance.now() - start) / 1000;

      // 由频谱算出单一音量标量（低中频，人声主能量），快攻慢放地平滑
      const node = analyserRef.current;
      let target = 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];
        target = sum / bins / 255;
      }
      const k = target > volumeRef.current ? 0.3 : 0.08;
      volumeRef.current += (target - volumeRef.current) * k;

      const dark = isDarkTheme();
      const { sky, lav, rose } = adaptPalette(readPalette(canvas), dark);
      gl.uniform1f(uTime, t);
      gl.uniform1f(uVol, volumeRef.current);
      gl.uniform1f(uActive, activeRef.current ? 1 : 0);
      gl.uniform1f(uTheme, dark ? 0 : 1);
      gl.uniform3fv(uC0, norm(sky));
      gl.uniform3fv(uC1, norm(lav));
      gl.uniform3fv(uC2, norm(rose));

      gl.clearColor(0, 0, 0, 0);
      gl.clear(gl.COLOR_BUFFER_BIT);
      gl.drawArrays(gl.TRIANGLES, 0, 6);

      raf = requestAnimationFrame(draw);
    };

    raf = requestAnimationFrame(draw);
    return () => {
      cancelAnimationFrame(raf);
      gl.deleteProgram(prog);
      gl.deleteShader(vs);
      gl.deleteShader(fs);
      gl.deleteBuffer(buf);
    };
  }, [size, analyserRef]);

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