import type { GlitchHostBridge } from './types';
import { HOST_SOUND_EVENT, HOST_STOP_SOUND_EVENT, type SoundId } from './hostBridge';

type OscillatorShape = OscillatorType;
type SoundStep = {
  frequency: number;
  durationMs: number;
  gain?: number;
  type?: OscillatorShape;
  delayMs?: number;
};

const COMPUTE_STEP_AUDIO_PATH = '/sounds/blblblbl.mp3';

const SOUND_PATTERNS: Record<string, SoundStep[]> = {
  'ui.button_click': [
    { frequency: 640, durationMs: 55, gain: 0.025, type: 'square' },
    { frequency: 880, durationMs: 40, gain: 0.018, type: 'triangle', delayMs: 36 }
  ],
  'ui.button_hover': [
    { frequency: 540, durationMs: 40, gain: 0.012, type: 'sine' }
  ],
  'machine.compute_start': [
    { frequency: 240, durationMs: 90, gain: 0.02, type: 'sawtooth' },
    { frequency: 320, durationMs: 90, gain: 0.018, type: 'sawtooth', delayMs: 82 },
    { frequency: 430, durationMs: 90, gain: 0.016, type: 'triangle', delayMs: 164 }
  ],
  'machine.compute_step': [
    { frequency: 780, durationMs: 26, gain: 0.009, type: 'square' }
  ],
  'machine.compute_done': [
    { frequency: 520, durationMs: 80, gain: 0.018, type: 'triangle' },
    { frequency: 780, durationMs: 110, gain: 0.018, type: 'triangle', delayMs: 70 }
  ],
  'machine.verdict_stable': [
    { frequency: 440, durationMs: 90, gain: 0.016, type: 'triangle' },
    { frequency: 554, durationMs: 90, gain: 0.016, type: 'triangle', delayMs: 65 },
    { frequency: 659, durationMs: 140, gain: 0.015, type: 'triangle', delayMs: 130 }
  ],
  'machine.verdict_alert': [
    { frequency: 180, durationMs: 120, gain: 0.022, type: 'sawtooth' },
    { frequency: 140, durationMs: 140, gain: 0.02, type: 'sawtooth', delayMs: 100 }
  ]
};

let sharedAudioContext: AudioContext | null = null;
let computeStepAudio: HTMLAudioElement | null = null;
let computeStepAudioFailed = false;
let computeStepAudioErrorLogged = false;

export type DevHostBridgeOptions = {
  isMuted?: () => boolean;
  onSound?: (id: string, payload?: Record<string, unknown>) => void;
  onEmit?: (type: string, payload?: unknown) => void;
};

function getAudioContext() {
  if (typeof window === 'undefined') return null;

  const AudioContextCtor = window.AudioContext || (window as typeof window & {
    webkitAudioContext?: typeof AudioContext;
  }).webkitAudioContext;

  if (!AudioContextCtor) return null;

  if (!sharedAudioContext) {
    sharedAudioContext = new AudioContextCtor();
  }

  if (sharedAudioContext.state === 'suspended') {
    void sharedAudioContext.resume();
  }

  return sharedAudioContext;
}

function scheduleTone(context: AudioContext, step: SoundStep) {
  const startAt = context.currentTime + (step.delayMs ?? 0) / 1000;
  const stopAt = startAt + step.durationMs / 1000;
  const oscillator = context.createOscillator();
  const gainNode = context.createGain();

  oscillator.type = step.type ?? 'triangle';
  oscillator.frequency.setValueAtTime(step.frequency, startAt);

  gainNode.gain.setValueAtTime(0.0001, startAt);
  gainNode.gain.exponentialRampToValueAtTime(step.gain ?? 0.02, startAt + 0.008);
  gainNode.gain.exponentialRampToValueAtTime(0.0001, stopAt);

  oscillator.connect(gainNode);
  gainNode.connect(context.destination);

  oscillator.start(startAt);
  oscillator.stop(stopAt + 0.02);
}

function playSynthPattern(id: string) {
  const context = getAudioContext();
  if (!context) return;

  const steps = SOUND_PATTERNS[id] ?? SOUND_PATTERNS['ui.button_click'];
  for (const step of steps) {
    scheduleTone(context, step);
  }
}

function playComputeStepAudio() {
  if (typeof window === 'undefined') return false;
  if (computeStepAudioFailed) return false;

  if (!computeStepAudio) {
    computeStepAudio = new Audio(COMPUTE_STEP_AUDIO_PATH);
    computeStepAudio.preload = 'auto';
    computeStepAudio.addEventListener('error', () => {
      computeStepAudioFailed = true;
      if (computeStepAudioErrorLogged) return;
      computeStepAudioErrorLogged = true;
      console.warn(`[glitch-host:sound] Failed to load ${COMPUTE_STEP_AUDIO_PATH}; using synth fallback.`);
    });
  }

  computeStepAudio.pause();
  computeStepAudio.currentTime = 0;

  const playAttempt = computeStepAudio.play();
  if (!playAttempt) return true;

  void playAttempt.catch((error) => {
    computeStepAudioFailed = true;
    playSynthPattern('machine.compute_step');
    if (computeStepAudioErrorLogged) return;
    computeStepAudioErrorLogged = true;
    console.warn(`[glitch-host:sound] Failed to play ${COMPUTE_STEP_AUDIO_PATH}; using synth fallback.`, error);
  });

  return true;
}

function stopComputeStepAudio() {
  if (!computeStepAudio) return;
  computeStepAudio.pause();
  computeStepAudio.currentTime = 0;
}

function playPattern(id: string) {
  if (id === 'machine.compute_step' && playComputeStepAudio()) {
    return;
  }

  playSynthPattern(id);
}

export function createDevHostBridge(options: DevHostBridgeOptions = {}): GlitchHostBridge {
  return {
    playSound(id, payload) {
      options.onSound?.(id, payload);
      if (options.isMuted?.()) return;
      playPattern(id);
    },
    stopSound(id, payload) {
      options.onSound?.(`stop:${id}`, payload);
      if (id === 'machine.compute_step') {
        stopComputeStepAudio();
      }
    },
    emit(type, payload) {
      options.onEmit?.(type, payload);
      console.log('[glitch-host:event]', type, payload);
    }
  };
}

export function attachWindowSoundBridge(host: GlitchHostBridge) {
  if (typeof window === 'undefined') return () => undefined;

  const onPlaySound = (event: Event) => {
    const customEvent = event as CustomEvent<{ id: SoundId; payload?: Record<string, unknown> }>;
    host.playSound?.(customEvent.detail.id, customEvent.detail.payload);
  };

  const onStopSound = (event: Event) => {
    const customEvent = event as CustomEvent<{ id: SoundId; payload?: Record<string, unknown> }>;
    host.stopSound?.(customEvent.detail.id, customEvent.detail.payload);
  };

  window.addEventListener(HOST_SOUND_EVENT, onPlaySound as EventListener);
  window.addEventListener(HOST_STOP_SOUND_EVENT, onStopSound as EventListener);
  return () => {
    window.removeEventListener(HOST_SOUND_EVENT, onPlaySound as EventListener);
    window.removeEventListener(HOST_STOP_SOUND_EVENT, onStopSound as EventListener);
  };
}
