// 轻量音效：用 Web Audio 合成，无需音频素材文件。
// 经过母线压缩 + 卷积混响，质感更接近高端棋牌客户端。
// 首次用户交互时解锁 AudioContext（浏览器自动播放策略）。

type SfxName = 'deal' | 'chip' | 'click' | 'fold' | 'win' | 'lose' | 'turn' | 'jackpot';

let ctx: AudioContext | null = null;
let muted = false;

// 母线：dry/wet → compressor → destination
let master: GainNode | null = null;
let reverbSend: GainNode | null = null;

/** 生成一段指数衰减的混响脉冲（无需音频素材） */
function buildImpulse(ac: AudioContext, seconds: number, decay: number): AudioBuffer {
  const rate = ac.sampleRate;
  const len = Math.floor(rate * seconds);
  const buf = ac.createBuffer(2, len, rate);
  for (let ch = 0; ch < 2; ch++) {
    const data = buf.getChannelData(ch);
    for (let i = 0; i < len; i++) {
      data[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / len, decay);
    }
  }
  return buf;
}

function getCtx(): AudioContext | null {
  if (typeof window === 'undefined') return null;
  if (!ctx) {
    const AC =
      window.AudioContext ||
      (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;
    if (!AC) return null;
    ctx = new AC();

    // 母线压缩器：统一音量、避免叠音爆音
    const comp = ctx.createDynamicsCompressor();
    comp.threshold.value = -14;
    comp.knee.value = 24;
    comp.ratio.value = 3;
    comp.attack.value = 0.004;
    comp.release.value = 0.18;
    comp.connect(ctx.destination);

    master = ctx.createGain();
    master.gain.value = 0.9;
    master.connect(comp);

    // 卷积混响支路
    const verb = ctx.createConvolver();
    verb.buffer = buildImpulse(ctx, 1.6, 2.6);
    const wet = ctx.createGain();
    wet.gain.value = 0.85;
    verb.connect(wet).connect(comp);
    reverbSend = ctx.createGain();
    reverbSend.gain.value = 1;
    reverbSend.connect(verb);
  }
  if (ctx.state === 'suspended') void ctx.resume();
  return ctx;
}

/** 在首次用户手势时调用，解锁声音。 */
export function unlockAudio() {
  getCtx();
}

export function setMuted(m: boolean) {
  muted = m;
}

export function isMuted() {
  return muted;
}

interface VoiceOpts {
  type?: OscillatorType;
  detune?: number;
  glideTo?: number; // 频率滑音目标
  attack?: number;
  reverb?: number; // 0~1 送往混响的比例
  pan?: number; // -1~1
}

/** 单个柔和音：平滑包络 + 低通去尖锐 + 可选混响送出 */
function voice(
  ac: AudioContext,
  freq: number,
  start: number,
  dur: number,
  peak: number,
  opts: VoiceOpts = {},
) {
  const t = ac.currentTime + start;
  const { type = 'sine', detune = 0, glideTo, attack = 0.008, reverb = 0.18, pan = 0 } = opts;

  const osc = ac.createOscillator();
  osc.type = type;
  osc.frequency.setValueAtTime(freq, t);
  if (glideTo) osc.frequency.exponentialRampToValueAtTime(glideTo, t + dur);
  osc.detune.value = detune;

  const lp = ac.createBiquadFilter();
  lp.type = 'lowpass';
  lp.frequency.value = Math.min(freq * 6 + 1200, 16000);

  const gain = ac.createGain();
  gain.gain.setValueAtTime(0.0001, t);
  gain.gain.exponentialRampToValueAtTime(peak, t + attack);
  gain.gain.exponentialRampToValueAtTime(0.0001, t + dur);

  const panner = ac.createStereoPanner();
  panner.pan.value = pan;

  osc.connect(lp).connect(gain).connect(panner);
  if (master) panner.connect(master);
  if (reverb > 0 && reverbSend) {
    const send = ac.createGain();
    send.gain.value = reverb;
    panner.connect(send).connect(reverbSend);
  }
  osc.start(t);
  osc.stop(t + dur + 0.05);
}

/** 钟/玻璃质感音：叠加非整数泛音，余韵自然——用于赢牌/提示/爆奖 */
function bell(ac: AudioContext, freq: number, start: number, peak: number, dur: number, reverb = 0.3) {
  voice(ac, freq, start, dur, peak, { type: 'sine', reverb });
  voice(ac, freq * 2.01, start, dur * 0.7, peak * 0.5, { type: 'sine', reverb });
  voice(ac, freq * 3.0, start, dur * 0.45, peak * 0.25, { type: 'sine', reverb });
}

/** 扫频噪声：发牌“唰”、丢牌“甩” */
function swish(
  ac: AudioContext,
  start: number,
  dur: number,
  peak: number,
  from: number,
  to: number,
) {
  const t = ac.currentTime + start;
  const n = Math.floor(ac.sampleRate * dur);
  const buf = ac.createBuffer(1, n, ac.sampleRate);
  const data = buf.getChannelData(0);
  for (let i = 0; i < n; i++) data[i] = (Math.random() * 2 - 1) * (1 - i / n);
  const src = ac.createBufferSource();
  src.buffer = buf;

  const bp = ac.createBiquadFilter();
  bp.type = 'bandpass';
  bp.Q.value = 0.8;
  bp.frequency.setValueAtTime(from, t);
  bp.frequency.exponentialRampToValueAtTime(to, t + dur);

  const gain = ac.createGain();
  gain.gain.setValueAtTime(0.0001, t);
  gain.gain.exponentialRampToValueAtTime(peak, t + 0.01);
  gain.gain.exponentialRampToValueAtTime(0.0001, t + dur);

  src.connect(bp).connect(gain);
  if (master) gain.connect(master);
  if (reverbSend) {
    const send = ac.createGain();
    send.gain.value = 0.12;
    gain.connect(send).connect(reverbSend);
  }
  src.start(t);
}

/** 金属脆响：筹码落桌的高频泛音 */
function clink(ac: AudioContext, freq: number, start: number, peak: number, pan = 0) {
  const t = ac.currentTime + start;
  const osc = ac.createOscillator();
  osc.type = 'triangle';
  osc.frequency.value = freq;
  const hp = ac.createBiquadFilter();
  hp.type = 'highpass';
  hp.frequency.value = 900;
  const gain = ac.createGain();
  gain.gain.setValueAtTime(0.0001, t);
  gain.gain.exponentialRampToValueAtTime(peak, t + 0.004);
  gain.gain.exponentialRampToValueAtTime(0.0001, t + 0.13);
  const panner = ac.createStereoPanner();
  panner.pan.value = pan;
  osc.connect(hp).connect(gain).connect(panner);
  if (master) panner.connect(master);
  if (reverbSend) {
    const send = ac.createGain();
    send.gain.value = 0.22;
    panner.connect(send).connect(reverbSend);
  }
  osc.start(t);
  osc.stop(t + 0.18);
}

export function play(name: SfxName) {
  if (muted) return;
  const ac = getCtx();
  if (!ac) return;
  switch (name) {
    case 'deal':
      // 卡牌滑出的“唰”+ 落桌的软低音
      swish(ac, 0, 0.13, 0.14, 5200, 1400);
      voice(ac, 180, 0.05, 0.12, 0.12, { type: 'sine', reverb: 0.1 });
      break;
    case 'chip':
      // 筹码叠落：三声错落金属脆响，左右微展开
      clink(ac, 2400, 0, 0.16, -0.15);
      clink(ac, 3100, 0.05, 0.13, 0.1);
      clink(ac, 2750, 0.1, 0.11, -0.05);
      voice(ac, 320, 0.02, 0.08, 0.06, { type: 'sine', reverb: 0.08 });
      break;
    case 'click':
      // UI 轻点：柔和木质 tick
      voice(ac, 660, 0, 0.05, 0.1, { type: 'triangle', reverb: 0.06 });
      voice(ac, 1320, 0.005, 0.04, 0.05, { type: 'sine', reverb: 0.06 });
      break;
    case 'turn':
      // 轮到你：温润双音铃提示
      bell(ac, 880, 0, 0.18, 0.5, 0.35);
      bell(ac, 1320, 0.12, 0.18, 0.55, 0.35);
      break;
    case 'fold':
      // 丢牌：向下甩牌的低扫声
      swish(ac, 0, 0.22, 0.12, 1600, 300);
      voice(ac, 160, 0.04, 0.18, 0.08, { type: 'sine', glideTo: 90, reverb: 0.12 });
      break;
    case 'win':
      // 赢牌：明亮大三和弦琶音 + 余韵
      bell(ac, 523.25, 0, 0.2, 0.55);
      bell(ac, 659.25, 0.1, 0.2, 0.6);
      bell(ac, 783.99, 0.2, 0.22, 0.7);
      bell(ac, 1046.5, 0.32, 0.24, 0.9);
      break;
    case 'jackpot':
      // 爆奖：华丽上行琶音 + 高音闪光 + 细碎金光
      bell(ac, 523.25, 0, 0.2, 0.5);
      bell(ac, 659.25, 0.09, 0.2, 0.5);
      bell(ac, 783.99, 0.18, 0.22, 0.55);
      bell(ac, 1046.5, 0.27, 0.22, 0.6);
      bell(ac, 1318.5, 0.37, 0.24, 0.7);
      bell(ac, 1567.98, 0.48, 0.26, 1.1, 0.45);
      swish(ac, 0.48, 0.5, 0.05, 9000, 5000);
      clink(ac, 3600, 0.6, 0.08, 0.2);
      clink(ac, 4200, 0.72, 0.07, -0.2);
      break;
    case 'lose':
      // 输牌：柔和下行小调，不刺耳
      voice(ac, 392, 0, 0.22, 0.12, { type: 'sine', reverb: 0.25 });
      voice(ac, 311.13, 0.14, 0.34, 0.12, { type: 'sine', glideTo: 246.94, reverb: 0.25 });
      break;
  }
}
