/** UI + fleet event cues — synthesized via Web Audio (no asset files). */

export type SoundCue =
  | 'click'
  | 'nav'
  | 'success'
  | 'error'
  | 'alert'
  | 'alertCritical'
  | 'share'
  | 'connect'
  | 'disconnect'
  | 'online'
  | 'offline';

export const SFX_STORAGE_KEY = 'aetherforge-sfx';
export const SFX_VOLUME_KEY = 'aetherforge-sfx-volume';

export function loadSoundEnabled(): boolean {
  try {
    const v = localStorage.getItem(SFX_STORAGE_KEY);
    return v === null ? true : v === '1';
  } catch {
    return true;
  }
}

export function loadSoundVolume(): number {
  try {
    const v = localStorage.getItem(SFX_VOLUME_KEY);
    if (v === null) return 0.4;
    const n = parseFloat(v);
    return Number.isFinite(n) ? Math.min(1, Math.max(0, n)) : 0.4;
  } catch {
    return 0.4;
  }
}

function persistEnabled(enabled: boolean) {
  try {
    localStorage.setItem(SFX_STORAGE_KEY, enabled ? '1' : '0');
  } catch {
    /* ignore */
  }
}

function persistVolume(volume: number) {
  try {
    localStorage.setItem(SFX_VOLUME_KEY, String(volume));
  } catch {
    /* ignore */
  }
}

const VIBRATE: Partial<Record<SoundCue, number | number[]>> = {
  click: 8,
  nav: 12,
  success: [12, 40, 18],
  error: [30, 50, 80],
  alert: [20, 30, 20],
  alertCritical: [40, 60, 40, 80],
  share: 14,
  connect: [10, 25],
  disconnect: [35, 20],
  online: [15, 35],
  offline: [25, 15],
};

type ToneSpec = {
  freq: number;
  duration: number;
  type?: OscillatorType;
  gain?: number;
  delay?: number;
};

function vibrateFor(cue: SoundCue) {
  if (typeof navigator === 'undefined' || !navigator.vibrate) return;
  const pattern = VIBRATE[cue];
  if (pattern !== undefined) navigator.vibrate(pattern);
}

export class HapticEngine {
  private ctx: AudioContext | null = null;
  private enabled = loadSoundEnabled();
  private volume = loadSoundVolume();
  private unlocked = false;

  isEnabled() {
    return this.enabled;
  }

  getVolume() {
    return this.volume;
  }

  setEnabled(enabled: boolean) {
    this.enabled = enabled;
    persistEnabled(enabled);
  }

  setVolume(volume: number) {
    this.volume = Math.min(1, Math.max(0, volume));
    persistVolume(this.volume);
  }

  /** Browsers require a user gesture before audio plays. */
  unlock() {
    if (this.unlocked) return;
    try {
      const Ctx =
        typeof window !== 'undefined'
          ? window.AudioContext ||
            (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext
          : undefined;
      if (!Ctx) return;
      if (!this.ctx) this.ctx = new Ctx();
      if (this.ctx.state === 'suspended') void this.ctx.resume();
      this.unlocked = true;
    } catch {
      /* ignore */
    }
  }

  play(cue: SoundCue) {
    if (!this.enabled) return;
    this.unlock();
    vibrateFor(cue);
    const specs = cueSpecs(cue);
    if (!specs.length) return;
    try {
      const Ctx = window.AudioContext ||
        (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;
      if (!Ctx) return;
      if (!this.ctx) this.ctx = new Ctx();
      const ctx = this.ctx;
      if (ctx.state === 'suspended') void ctx.resume();
      const master = ctx.createGain();
      master.gain.value = this.volume;
      master.connect(ctx.destination);
      const now = ctx.currentTime;
      for (const spec of specs) {
        this.scheduleTone(ctx, master, spec, now);
      }
    } catch {
      /* Audio blocked or unavailable */
    }
  }

  private scheduleTone(ctx: AudioContext, dest: GainNode, spec: ToneSpec, base: number) {
    const osc = ctx.createOscillator();
    const g = ctx.createGain();
    const t0 = base + (spec.delay ?? 0);
    const dur = spec.duration;
    const peak = (spec.gain ?? 0.12) * this.volume;
    osc.type = spec.type ?? 'sine';
    osc.frequency.setValueAtTime(spec.freq, t0);
    g.gain.setValueAtTime(0.0001, t0);
    g.gain.exponentialRampToValueAtTime(Math.max(peak, 0.0001), t0 + 0.008);
    g.gain.exponentialRampToValueAtTime(0.0001, t0 + dur);
    osc.connect(g);
    g.connect(dest);
    osc.start(t0);
    osc.stop(t0 + dur + 0.02);
  }
}

function cueSpecs(cue: SoundCue): ToneSpec[] {
  switch (cue) {
    case 'click':
      return [{ freq: 920, duration: 0.04, type: 'square', gain: 0.06 }];
    case 'nav':
      return [
        { freq: 440, duration: 0.05, gain: 0.07 },
        { freq: 660, duration: 0.06, delay: 0.04, gain: 0.06 },
      ];
    case 'success':
      return [
        { freq: 523, duration: 0.08, gain: 0.1 },
        { freq: 784, duration: 0.1, delay: 0.07, gain: 0.09 },
      ];
    case 'error':
      return [
        { freq: 180, duration: 0.12, type: 'sawtooth', gain: 0.11 },
        { freq: 140, duration: 0.14, delay: 0.1, type: 'sawtooth', gain: 0.09 },
      ];
    case 'alert':
      return [
        { freq: 740, duration: 0.07, type: 'triangle', gain: 0.09 },
        { freq: 620, duration: 0.08, delay: 0.09, type: 'triangle', gain: 0.08 },
      ];
    case 'alertCritical':
      return [
        { freq: 880, duration: 0.06, type: 'square', gain: 0.1 },
        { freq: 660, duration: 0.06, delay: 0.07, type: 'square', gain: 0.1 },
        { freq: 440, duration: 0.1, delay: 0.14, type: 'square', gain: 0.11 },
      ];
    case 'share':
      return [
        { freq: 1200, duration: 0.05, gain: 0.08 },
        { freq: 1600, duration: 0.06, delay: 0.05, gain: 0.07 },
      ];
    case 'connect':
      return [
        { freq: 330, duration: 0.07, gain: 0.08 },
        { freq: 495, duration: 0.09, delay: 0.06, gain: 0.08 },
      ];
    case 'disconnect':
      return [
        { freq: 400, duration: 0.1, gain: 0.08 },
        { freq: 260, duration: 0.12, delay: 0.08, gain: 0.07 },
      ];
    case 'online':
      return [
        { freq: 587, duration: 0.07, gain: 0.08 },
        { freq: 880, duration: 0.09, delay: 0.06, gain: 0.07 },
      ];
    case 'offline':
      return [
        { freq: 440, duration: 0.09, gain: 0.07 },
        { freq: 330, duration: 0.1, delay: 0.07, gain: 0.06 },
      ];
    default:
      return [];
  }
}

export const hapticEngine = new HapticEngine();
