import { useStore } from '@nanostores/react'
import { useEffect, useRef } from 'react'

import { Button } from '@/components/ui/button'
import { useI18n } from '@/i18n'
import { iconSize, Loader2, Mic, Volume2, VolumeX } from '@/lib/icons'
import { cn } from '@/lib/utils'
import { stopVoicePlayback } from '@/lib/voice-playback'
import { $voicePlayback } from '@/store/voice-playback'

import type { VoiceActivityState } from './types'

type BrowserAudioContext = typeof AudioContext

interface ElementAnalyser {
  analyser: AnalyserNode
}

const elementAnalysers = new WeakMap<HTMLAudioElement, ElementAnalyser>()
let playbackAudioContext: AudioContext | null = null

function getPlaybackAudioContext(): AudioContext | null {
  if (playbackAudioContext && playbackAudioContext.state !== 'closed') {
    return playbackAudioContext
  }

  const audioWindow = window as Window & { webkitAudioContext?: BrowserAudioContext }
  const AudioContextCtor = window.AudioContext || audioWindow.webkitAudioContext

  if (!AudioContextCtor) {
    return null
  }

  playbackAudioContext = new AudioContextCtor()

  return playbackAudioContext
}

function formatElapsed(seconds: number) {
  const safeSeconds = Math.max(0, Math.floor(seconds))
  const minutes = Math.floor(safeSeconds / 60)
  const remainingSeconds = safeSeconds % 60

  return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`
}

function VoiceLevelBars({ level, active }: { active: boolean; level: number }) {
  const normalized = Math.max(0, Math.min(level, 1))
  const bars = [0.5, 0.78, 1, 0.78, 0.5]

  return (
    <div aria-hidden="true" className="flex h-4 items-center gap-0.5">
      {bars.map((weight, index) => {
        const height = active ? 0.25 + Math.min(0.68, normalized * weight) : 0.25

        return (
          <span
            className={cn(
              'w-0.5 rounded-full bg-current transition-[height,opacity] duration-100 ease-out',
              active ? 'opacity-80' : 'animate-pulse opacity-45'
            )}
            key={index}
            style={{ height: `${height * 100}%` }}
          />
        )
      })}
    </div>
  )
}

function getElementAnalyser(audioElement: HTMLAudioElement): ElementAnalyser | null {
  let entry = elementAnalysers.get(audioElement)

  if (!entry) {
    const context = getPlaybackAudioContext()

    if (!context) {
      return null
    }

    const source = context.createMediaElementSource(audioElement)
    const analyser = context.createAnalyser()

    analyser.fftSize = 512
    analyser.smoothingTimeConstant = 0.65
    source.connect(analyser)
    analyser.connect(context.destination)
    entry = { analyser }
    elementAnalysers.set(audioElement, entry)
  }

  void playbackAudioContext?.resume()

  return entry
}

const WAVE_W = 88
const WAVE_H = 16
const BAR_W = 2
const BAR_GAP = 5
const STEP = BAR_W + BAR_GAP
const BARS = Math.floor((WAVE_W + BAR_GAP) / STEP)
const X0 = Math.round((WAVE_W - (BARS * STEP - BAR_GAP)) / 2)

function PlaybackWaveform({ audioElement }: { audioElement: HTMLAudioElement | null }) {
  const canvasRef = useRef<HTMLCanvasElement | null>(null)

  useEffect(() => {
    const canvas = canvasRef.current

    if (!canvas || !audioElement) {
      return
    }

    const entry = getElementAnalyser(audioElement)
    const ctx = canvas.getContext('2d')

    if (!entry || !ctx) {
      return
    }

    const dpr = Math.max(1, window.devicePixelRatio || 1)
    const { analyser } = entry
    const buf = new Uint8Array(analyser.frequencyBinCount)
    const hi = Math.floor(buf.length * 0.9)

    canvas.width = Math.round(WAVE_W * dpr)
    canvas.height = Math.round(WAVE_H * dpr)
    canvas.style.width = `${WAVE_W}px`
    canvas.style.height = `${WAVE_H}px`
    ctx.setTransform(dpr, 0, 0, dpr, 0, 0)
    ctx.imageSmoothingEnabled = false
    ctx.fillStyle = getComputedStyle(canvas).color

    let raf = 0

    const tick = () => {
      analyser.getByteFrequencyData(buf)
      ctx.clearRect(0, 0, WAVE_W, WAVE_H)

      for (let i = 0; i < BARS; i++) {
        const a = Math.floor((i / BARS) * hi)
        const b = Math.floor(((i + 1) / BARS) * hi)
        let peak = 0

        for (let j = a; j < b; j++) {
          peak = Math.max(peak, buf[j] ?? 0)
        }

        const amp = Math.sqrt(peak / 255)
        const bh = Math.max(3, Math.round((0.18 + amp * 0.82) * WAVE_H))
        ctx.fillRect(X0 + i * STEP, Math.round((WAVE_H - bh) / 2), BAR_W, bh)
      }

      raf = requestAnimationFrame(tick)
    }

    tick()

    return () => cancelAnimationFrame(raf)
  }, [audioElement])

  return <canvas aria-hidden="true" className="block h-4 w-[88px]" ref={canvasRef} />
}

export function VoiceActivity({ state }: { state: VoiceActivityState }) {
  const { t } = useI18n()

  if (state.status === 'idle') {
    return null
  }

  const recording = state.status === 'recording'
  const title = recording ? t.composer.dictating : t.composer.transcribing

  return (
    <div
      aria-live="polite"
      className={cn(
        'flex h-8 items-center gap-2 rounded-xl border border-border/55 bg-muted/55 px-2.5 text-xs text-muted-foreground',
        'shadow-[inset_0_1px_0_rgba(255,255,255,0.35)] backdrop-blur-sm'
      )}
      role="status"
    >
      <div
        className={cn(
          'flex size-5 shrink-0 items-center justify-center rounded-full',
          recording ? 'bg-primary/15 text-primary' : 'bg-primary/10 text-primary'
        )}
      >
        {recording ? <Mic className={iconSize.xs} /> : <Loader2 className={cn('animate-spin', iconSize.xs)} />}
      </div>

      <div className="flex min-w-0 flex-1 items-center gap-2">
        <span className="truncate font-medium text-foreground/85">{title}</span>
        <span className="font-mono text-[0.6875rem] text-muted-foreground/85">
          {formatElapsed(state.elapsedSeconds)}
        </span>
      </div>

      <VoiceLevelBars active={recording} level={state.level} />
    </div>
  )
}

export function VoicePlaybackActivity() {
  const { t } = useI18n()
  const playback = useStore($voicePlayback)

  if (playback.status === 'idle') {
    return null
  }

  const preparing = playback.status === 'preparing'

  const title = preparing
    ? t.composer.preparingAudio
    : playback.source === 'voice-conversation'
      ? t.composer.speakingResponse
      : t.composer.readingAloud

  return (
    <div
      aria-live="polite"
      className={cn(
        'flex h-8 items-center gap-2 rounded-xl border border-primary/20 bg-primary/10 px-2.5 text-xs text-primary',
        'shadow-[inset_0_1px_0_rgba(255,255,255,0.35)] backdrop-blur-sm'
      )}
      role="status"
    >
      <div className="flex size-5 shrink-0 items-center justify-center rounded-full bg-primary/15 text-primary">
        {preparing ? <Loader2 className={cn('animate-spin', iconSize.xs)} /> : <Volume2 className={iconSize.xs} />}
      </div>

      <div className="flex min-w-0 flex-1 items-center gap-2">
        <span className="truncate font-medium text-foreground/85">{title}</span>
        {!preparing && <PlaybackWaveform audioElement={playback.audioElement} />}
      </div>

      <Button
        className="h-6 shrink-0 gap-1 rounded-full px-2 text-[0.6875rem]"
        onClick={stopVoicePlayback}
        size="sm"
        type="button"
        variant="ghost"
      >
        <VolumeX className={iconSize.xs} />
        Stop
      </Button>
    </div>
  )
}
