/** 効果音。猫の鳴き声も工場の機械音も、全てその場で合成する。 */
import { ensureAudio, getBuses, noise, tone } from './engine';

export type SfxName =
  | 'meow'      // ニャー
  | 'meow_soft' // 小さくニャ
  | 'hiss'      // シャーッ！
  | 'purr'      // ゴロゴロ
  | 'panic'     // パニックボイス
  | 'punch'     // ネコパンチ
  | 'clank'     // 加工完了の打撃音
  | 'zap'       // 機械の故障
  | 'cash'      // 出荷
  | 'thud'      // 落下・破損
  | 'siren'     // 事故サイレン
  | 'alarm'     // 重大事故
  | 'paper'     // 書類をめくる
  | 'bell'      // 休憩開始の合図
  | 'beep'      // フォークリフトのバックブザー
  | 'horn'      // トラックの発車
  | 'chime'     // 昇格・達成
  | 'click';    // UI

/** 音が団子にならないよう、種類ごとに最短間隔を設ける (ms) */
const COOLDOWN: Partial<Record<SfxName, number>> = {
  meow: 900,
  meow_soft: 700,
  hiss: 800,
  purr: 2500,
  panic: 1500,
  punch: 400,
  clank: 130,
  zap: 500,
  cash: 90,
  thud: 300,
  paper: 150,
  bell: 3000,
  beep: 1400,
  horn: 2500,
  siren: 2500,
  alarm: 4000,
};

const lastPlayed = new Map<SfxName, number>();

export function playSfx(name: SfxName, detune = 0): void {
  const b = ensureAudio();
  if (!b) return;

  const cd = COOLDOWN[name];
  if (cd !== undefined) {
    const now = performance.now();
    const prev = lastPlayed.get(name) ?? -Infinity;
    if (now - prev < cd) return;
    lastPlayed.set(name, now);
  }

  const p = Math.pow(2, detune / 12);
  switch (name) {
    case 'meow':
      // 「ニャー」: 立ち上がりで上がって、語尾で下がる
      tone({ type: 'sawtooth', freq: 420 * p, toFreq: 700 * p, duration: 0.1, gain: 0.13, lowpass: 2200 });
      tone({ type: 'sawtooth', freq: 700 * p, toFreq: 380 * p, at: 0.09, duration: 0.34, gain: 0.15,
        lowpass: 1900, vibrato: { rate: 11, depth: 16 } });
      break;

    case 'meow_soft':
      tone({ type: 'triangle', freq: 520 * p, toFreq: 420 * p, duration: 0.18, gain: 0.08,
        lowpass: 1600, vibrato: { rate: 9, depth: 10 } });
      break;

    case 'hiss':
      // 「シャーッ！」: 高域ノイズを一気に開いて閉じる
      noise({ filter: 'bandpass', freq: 3200, toFreq: 5200, q: 0.8, duration: 0.42, gain: 0.20, attack: 0.02 });
      noise({ filter: 'highpass', freq: 2400, duration: 0.30, gain: 0.10, at: 0.04 });
      break;

    case 'purr':
      // 「ゴロゴロ」: 低域ノイズを25Hzで振幅変調する
      purr();
      break;

    case 'panic':
      // 悲鳴じみた早口のニャー2連
      tone({ type: 'sawtooth', freq: 620 * p, toFreq: 1150 * p, duration: 0.13, gain: 0.16, lowpass: 3000,
        vibrato: { rate: 17, depth: 40 } });
      tone({ type: 'sawtooth', freq: 980 * p, toFreq: 640 * p, at: 0.15, duration: 0.26, gain: 0.15,
        lowpass: 2600, vibrato: { rate: 15, depth: 34 } });
      break;

    case 'punch':
      noise({ filter: 'bandpass', freq: 900, toFreq: 260, q: 1.4, duration: 0.11, gain: 0.24 });
      tone({ type: 'sine', freq: 180, toFreq: 70, duration: 0.12, gain: 0.16 });
      break;

    case 'clank':
      // 工作機械の打撃。金属的な倍音を短く。
      tone({ type: 'square', freq: 1180 * p, toFreq: 880 * p, duration: 0.055, gain: 0.05, lowpass: 4200 });
      noise({ filter: 'bandpass', freq: 2600, q: 3, duration: 0.05, gain: 0.05 });
      tone({ type: 'sine', freq: 150, toFreq: 90, duration: 0.09, gain: 0.06 });
      break;

    case 'zap':
      noise({ filter: 'bandpass', freq: 4200, toFreq: 700, q: 2, duration: 0.28, gain: 0.20 });
      tone({ type: 'sawtooth', freq: 260, toFreq: 60, duration: 0.3, gain: 0.10, lowpass: 1400 });
      break;

    case 'cash':
      tone({ type: 'triangle', freq: 1320, duration: 0.09, gain: 0.10 });
      tone({ type: 'triangle', freq: 1980, at: 0.05, duration: 0.16, gain: 0.08 });
      break;

    case 'thud':
      noise({ filter: 'lowpass', freq: 420, toFreq: 120, duration: 0.24, gain: 0.24 });
      tone({ type: 'sine', freq: 120, toFreq: 46, duration: 0.26, gain: 0.16 });
      break;

    case 'siren':
      siren(2, 0.09);
      break;

    case 'alarm':
      siren(4, 0.14);
      noise({ filter: 'lowpass', freq: 600, toFreq: 180, duration: 0.7, gain: 0.18 });
      break;

    case 'paper':
      // 紙をめくる: 短い高域ノイズを2回
      noise({ filter: 'highpass', freq: 3800, duration: 0.07, gain: 0.10 });
      noise({ filter: 'bandpass', freq: 5200, q: 1.2, at: 0.06, duration: 0.09, gain: 0.08 });
      break;

    case 'bell':
      // 休憩の合図。工場のチャイム。
      [880, 1174.7, 1318.5].forEach((f, i) => {
        tone({ type: 'sine', freq: f, at: i * 0.22, duration: 0.85, gain: 0.11 });
      });
      break;

    case 'beep':
      // 構内車両のバックブザー
      for (let i = 0; i < 3; i++) {
        tone({ type: 'square', freq: 1180, at: i * 0.24, duration: 0.12, gain: 0.05, lowpass: 2600 });
      }
      break;

    case 'horn':
      // トラックの発車。低く二度鳴らす。
      tone({ type: 'sawtooth', freq: 196, duration: 0.42, gain: 0.11, lowpass: 900 });
      tone({ type: 'sawtooth', freq: 147, duration: 0.42, gain: 0.09, lowpass: 900 });
      tone({ type: 'sawtooth', freq: 196, at: 0.5, duration: 0.55, gain: 0.11, lowpass: 900 });
      tone({ type: 'sawtooth', freq: 147, at: 0.5, duration: 0.55, gain: 0.09, lowpass: 900 });
      break;

    case 'chime':
      [0, 4, 7, 12].forEach((semi, i) => {
        tone({ type: 'triangle', freq: 523.25 * Math.pow(2, semi / 12), at: i * 0.09,
          duration: 0.5, gain: 0.09 });
      });
      break;

    case 'click':
      tone({ type: 'square', freq: 900, duration: 0.03, gain: 0.03, lowpass: 3000 });
      break;
  }
}

/** ゴロゴロ音: 低域ノイズを低周波で振幅変調する */
function purr(): void {
  const b = getBuses();
  if (!b) return;
  const t0 = b.ctx.currentTime;
  const dur = 1.5;

  const src = b.ctx.createBufferSource();
  src.buffer = b.noise;
  src.loop = true;

  const lp = b.ctx.createBiquadFilter();
  lp.type = 'lowpass';
  lp.frequency.value = 300;

  const env = b.ctx.createGain();
  env.gain.setValueAtTime(0.0001, t0);
  env.gain.exponentialRampToValueAtTime(0.16, t0 + 0.2);
  env.gain.setValueAtTime(0.16, t0 + dur - 0.35);
  env.gain.exponentialRampToValueAtTime(0.0001, t0 + dur);

  // 25Hz の振幅変調が「ゴロゴロ」の正体
  const am = b.ctx.createGain();
  am.gain.value = 0.45;
  const lfo = b.ctx.createOscillator();
  lfo.type = 'sine';
  lfo.frequency.value = 25;
  const lfoDepth = b.ctx.createGain();
  lfoDepth.gain.value = 0.45;
  lfo.connect(lfoDepth);
  lfoDepth.connect(am.gain);

  src.connect(lp);
  lp.connect(am);
  am.connect(env);
  env.connect(b.sfx);

  lfo.start(t0);
  src.start(t0);
  lfo.stop(t0 + dur + 0.05);
  src.stop(t0 + dur + 0.05);
}

/** 二音サイレン */
function siren(cycles: number, gain: number): void {
  for (let i = 0; i < cycles; i++) {
    const at = i * 0.55;
    tone({ type: 'sawtooth', freq: 640, toFreq: 900, at, duration: 0.27, gain, lowpass: 2400 });
    tone({ type: 'sawtooth', freq: 900, toFreq: 640, at: at + 0.27, duration: 0.27, gain, lowpass: 2400 });
  }
}
