/**
 * Lane F — installAudio(world).
 *
 * Subscribes the synthesized one-shots to the frozen wire events and returns
 * the engine. Consumes events ONLY — never emits gameplay events.
 *
 * Unlock: the context is created on `ui:play` (Lane H's title screen) OR the
 * first real user gesture, whichever comes first. Both paths funnel through the
 * idempotent `engine.unlock()`, so the context is still created lazily and stays
 * `null` until a genuine gesture — the autoplay policy is respected either way.
 *
 * A small 🔈/🔇 button is pinned bottom-right; clicking it toggles mute (and
 * counts as the unlocking gesture).
 */
import type { PaintColor, World } from '../contracts'
import { AudioEngine } from './engine'
import { boing, clank, fanfare, sparkle, splat, squish, tick } from './sounds'

export interface AudioHandle {
  engine: AudioEngine
}

/** A finish is a new best when the reported best equals the run time. */
function isNewBest(p: { time?: number; best?: number | null } | undefined): boolean {
  if (!p || p.best == null || p.time == null) return false
  return p.time <= p.best + 1e-6
}

export function installAudio(world: World): AudioHandle {
  const engine = new AudioEngine()

  // ---- unlock paths (both idempotent) ------------------------------------
  world.events.on('ui:play', () => engine.unlock())

  // First-gesture fallback so the engine unlocks even if `ui:play` never fires
  // (e.g. a demo/embed without Lane H's title screen). Removes itself on use.
  const GESTURES = ['pointerdown', 'keydown', 'touchstart'] as const
  const onFirstGesture = (): void => {
    engine.unlock()
    for (const g of GESTURES) removeEventListener(g, onFirstGesture)
  }
  if (typeof addEventListener !== 'undefined') {
    for (const g of GESTURES) addEventListener(g, onFirstGesture, { passive: true })
  }

  // ---- event → sound wiring ----------------------------------------------
  // Every sound no-ops while dormant, so events before unlock are simply silent.
  world.events.on('blob:landed', (p: { impact?: number }) => squish(engine, p?.impact ?? 1))
  world.events.on('blob:jumped', () => boing(engine))
  world.events.on('paint:splatted', (p: { color: PaintColor; target: 'blob' | 'world' }) => {
    if (p?.target === 'blob') splat(engine, p.color) // world/ground decals stay silent
  })
  world.events.on('paint:request-cleanse', () => sparkle(engine))
  world.events.on('machine:signal', () => clank(engine))
  world.events.on('race:started', () => tick(engine))
  world.events.on('race:finished', (p: { time: number; best: number | null }) =>
    fanfare(engine, isNewBest(p)),
  )

  // ---- mute button (bottom-right) ----------------------------------------
  if (typeof document !== 'undefined') {
    mountMuteButton(engine)
  }

  return { engine }
}

function mountMuteButton(engine: AudioEngine): HTMLButtonElement {
  const btn = document.createElement('button')
  btn.type = 'button'
  btn.setAttribute('aria-label', 'Toggle sound')
  btn.style.cssText =
    'position:fixed;right:14px;bottom:14px;z-index:40;width:44px;height:44px;' +
    'border:none;border-radius:12px;cursor:pointer;font-size:20px;line-height:44px;' +
    'text-align:center;background:rgba(20,24,34,.78);color:#fff;' +
    'box-shadow:0 2px 8px rgba(0,0,0,.25);user-select:none;padding:0'
  const render = (): void => {
    btn.textContent = engine.muted ? '🔇' : '🔈'
    btn.style.opacity = engine.muted ? '0.6' : '1'
  }
  render()
  engine.onMuteChange(render)
  btn.addEventListener('click', () => {
    engine.unlock() // the click is a valid gesture — safe to create the ctx here
    engine.toggleMute()
  })
  document.body.appendChild(btn)
  return btn
}
