import { useEffect, useRef, type JSX } from 'react'
import type { ScopeKind } from '../../types/scope'
import type { ScopeSettings } from '../../types/settings'
import type {
  PrismResolvedTheme,
  ResolvedAstraTheme,
  ResolvedLUFSMeterTheme,
  ResolvedOscilloscopeTheme,
  ResolvedSpectrogramTheme,
  ResolvedSpectrumTheme,
  ResolvedVectorscopeTheme,
  ResolvedVUMeterTheme,
  ResolvedWaveformTheme,
} from '../../types/theme'
import { useSettingsStore } from '../stores/settingsStore'
import { useThemeStore } from '../stores/themeStore'
import AstraScopeModule from './AstraScopeModule'
import { SpectrumAnalyzer, type SpectrumAnalyzerDataSource } from '../visualizers/SpectrumAnalyzer'
import { Oscilloscope, type OscilloscopeDataSource } from '../visualizers/Oscilloscope'
import { Vectorscope, type VectorscopeDataSource } from '../visualizers/Vectorscope'
import { Spectrogram, type SpectrogramDataSource } from '../visualizers/Spectrogram'
import { VUMeter, type VUMeterDataSource } from '../visualizers/VUMeter'
import { LUFSMeter, type LUFSMeterDataSource } from '../visualizers/LUFSMeter'
import { Waveform, type WaveformDataSource } from '../visualizers/Waveform'
import type { FrameScheduler } from '../visualizers/frameScheduler'

type ScopeModuleTheme =
  | ResolvedSpectrumTheme
  | ResolvedOscilloscopeTheme
  | ResolvedVectorscopeTheme
  | ResolvedSpectrogramTheme
  | ResolvedVUMeterTheme
  | ResolvedLUFSMeterTheme
  | ResolvedWaveformTheme
  | ResolvedAstraTheme

interface ScopeModuleProps {
  scopeKind: ScopeKind
  theme?: ScopeModuleTheme
  settings?: ScopeSettings[ScopeKind]
  frameScheduler?: FrameScheduler
  dataSource?:
    | SpectrumAnalyzerDataSource
    | OscilloscopeDataSource
    | VectorscopeDataSource
    | SpectrogramDataSource
    | VUMeterDataSource
    | LUFSMeterDataSource
    | WaveformDataSource
}

interface Visualizer {
  start(): void
  stop(): void
  dispose(): void
  resize(): void
  setOptions(options: Record<string, unknown>): void
}

function getScopeTheme(theme: PrismResolvedTheme, kind: ScopeKind): ScopeModuleTheme {
  return theme[kind] as ScopeModuleTheme
}

export function scopeSettingsToOptions(
  kind: ScopeKind,
  settings: ScopeSettings[ScopeKind],
  theme: ScopeModuleTheme,
): Record<string, unknown> {
  switch (kind) {
    case 'spectrum': {
      const s = settings as ScopeSettings['spectrum']
      const t = theme as ResolvedSpectrumTheme
      return {
        lineColor: t.line,
        secondaryLineColor: t.sideLine,
        gradientColors: t.fillGradient,
        heatColors: t.heatColors,
        heatBaseColor: t.heatBase,
        backgroundColor: t.background,
        gridColor: t.guides,
        labelColor: t.labels,
        fftSize: s.fftSize,
        tiltDbPerOctave: s.tiltDbPerOctave,
        heatmapFill: s.heatmap,
        heatmapTiltDbPerOctave: s.heatmapTiltDbPerOctave,
        heatmapSmoothing: s.heatmapSmoothing,
        showGrid: s.showGrid,
        fillGradient: s.fillGradient,
        smoothing: s.smoothing,
        showSideLine: s.showSideLine,
      }
    }
    case 'oscilloscope': {
      const s = settings as ScopeSettings['oscilloscope']
      const t = theme as ResolvedOscilloscopeTheme
      return {
        lineColor: t.line,
        backgroundColor: t.background,
        gridMajorColor: t.guides,
        gridMinorColor: t.guidesSecondary,
        underfillColor: t.fill,
        pitchLock: s.pitchLock,
        underfillEnabled: s.underfillEnabled,
        showGrid: s.showGrid,
        lineWidth: s.lineWidth,
      }
    }
    case 'vectorscope': {
      const s = settings as ScopeSettings['vectorscope']
      const t = theme as ResolvedVectorscopeTheme
      return {
        lineColor: t.trace,
        backgroundColor: t.background,
        gridMajorColor: t.guides,
        gridMinorColor: t.guidesSecondary,
        labelColor: t.labels,
        bandColors: {
          low: t.bandLow,
          mid: t.bandMid,
          high: t.bandHigh,
        },
        mode: s.mode,
        multiband: s.multiband,
        showGrid: s.showGrid,
        persistence: s.persistence,
        lineWidth: s.lineWidth,
      }
    }
    case 'spectrogram': {
      const s = settings as ScopeSettings['spectrogram']
      const t = theme as ResolvedSpectrogramTheme
      return {
        lineColor: t.mono,
        heatColors: t.heatColors,
        backgroundColor: t.background,
        fftSize: s.fftSize,
        scrollSpeed: s.scrollSpeed,
        clarityMode: s.clarityMode,
        scaleMode: s.scaleMode,
        colorScheme: s.colorScheme,
      }
    }
    case 'vumeter': {
      const s = settings as ScopeSettings['vumeter']
      const t = theme as ResolvedVUMeterTheme
      return {
        backgroundColor: t.background,
        lineColor: t.level,
        trackColor: t.track,
        peakColor: t.peak,
        clipColor: t.clip,
        scaleColor: t.scale,
        labelColor: t.labels,
        mode: s.mode,
        orientation: s.orientation,
      }
    }
    case 'lufsmeter': {
      const s = settings as ScopeSettings['lufsmeter']
      const t = theme as ResolvedLUFSMeterTheme
      return {
        backgroundColor: t.background,
        lineColor: t.level,
        trackColor: t.track,
        targetColor: t.target,
        scaleColor: t.scale,
        labelColor: t.labels,
        mode: s.mode,
      }
    }
    case 'waveform': {
      const s = settings as ScopeSettings['waveform']
      const t = theme as ResolvedWaveformTheme
      return {
        backgroundColor: t.background,
        lineColor: t.line,
        gridMajorColor: t.guides,
        gridMinorColor: t.guidesSecondary,
        bandColors: {
          low: t.bandLow,
          mid: t.bandMid,
          high: t.bandHigh,
        },
        mode: s.mode,
        scrollSpeed: s.scrollSpeed,
        gainDb: s.gainDb,
        multiband: s.multiband,
      }
    }
    case 'astra':
      return {}
    default:
      return {}
  }
}

function createVisualizer(
  scopeKind: ScopeKind,
  canvas: HTMLCanvasElement,
  mySettings: ScopeSettings[ScopeKind],
  theme: ScopeModuleTheme,
  frameScheduler?: FrameScheduler,
  dataSource?: ScopeModuleProps['dataSource'],
): Visualizer | null {
  const opts = { ...scopeSettingsToOptions(scopeKind, mySettings, theme), frameScheduler }
  switch (scopeKind) {
    case 'spectrum':
      return new SpectrumAnalyzer(canvas, {
        ...opts,
        ...(dataSource ? { dataSource: dataSource as SpectrumAnalyzerDataSource } : {}),
      })
    case 'oscilloscope':
      return new Oscilloscope(canvas, {
        ...opts,
        ...(dataSource ? { dataSource: dataSource as OscilloscopeDataSource } : {}),
      })
    case 'vectorscope':
      return new Vectorscope(canvas, {
        ...opts,
        ...(dataSource ? { dataSource: dataSource as VectorscopeDataSource } : {}),
      })
    case 'spectrogram':
      return new Spectrogram(canvas, {
        ...opts,
        ...(dataSource ? { dataSource: dataSource as SpectrogramDataSource } : {}),
      })
    case 'vumeter':
      return new VUMeter(canvas, {
        ...opts,
        ...(dataSource ? { dataSource: dataSource as VUMeterDataSource } : {}),
      })
    case 'lufsmeter':
      return new LUFSMeter(canvas, {
        ...opts,
        ...(dataSource ? { dataSource: dataSource as LUFSMeterDataSource } : {}),
      })
    case 'waveform':
      return new Waveform(canvas, {
        ...opts,
        ...(dataSource ? { dataSource: dataSource as WaveformDataSource } : {}),
      })
    case 'astra':
      return null
    default:
      return null
  }
}

export default function ScopeModule({
  scopeKind,
  theme,
  settings,
  frameScheduler,
  dataSource,
}: ScopeModuleProps): JSX.Element {
  const containerRef = useRef<HTMLDivElement>(null)
  const canvasRef = useRef<HTMLCanvasElement>(null)
  const visualizerRef = useRef<Visualizer | null>(null)
  const initializedRef = useRef(false)

  const storeSettings = useSettingsStore((s) => s.scopeSettings[scopeKind])
  const activeTheme = useThemeStore((s) => s.activeTheme)
  const mySettings = settings ?? storeSettings
  const myTheme = theme ?? getScopeTheme(activeTheme, scopeKind)

  if (scopeKind === 'astra') {
    return (
      <AstraScopeModule
        theme={myTheme as ResolvedAstraTheme}
        settings={mySettings as ScopeSettings['astra']}
      />
    )
  }

  useEffect(() => {
    const canvas = canvasRef.current
    if (!canvas) return

    initializedRef.current = false
    const viz = createVisualizer(scopeKind, canvas, mySettings, myTheme, frameScheduler, dataSource)
    if (!viz) return

    visualizerRef.current = viz
    viz.start()

    requestAnimationFrame(() => {
      initializedRef.current = true
    })

    return () => {
      viz.dispose()
      visualizerRef.current = null
      initializedRef.current = false
    }
  }, [dataSource, frameScheduler, myTheme, mySettings, scopeKind])

  useEffect(() => {
    if (!visualizerRef.current || !initializedRef.current) return
    const opts = {
      ...scopeSettingsToOptions(scopeKind, mySettings, myTheme),
      frameScheduler,
      ...(dataSource ? { dataSource } : {}),
    }
    visualizerRef.current.setOptions(opts)
  }, [dataSource, frameScheduler, mySettings, myTheme, scopeKind])

  useEffect(() => {
    const container = containerRef.current
    const canvas = canvasRef.current
    if (!container || !canvas) return

    const resizeCanvas = (): void => {
      const rect = container.getBoundingClientRect()
      const width = Math.max(1, Math.floor(rect.width))
      const height = Math.max(1, Math.floor(rect.height))
      const dpr = window.devicePixelRatio || 1

      canvas.style.width = `${width}px`
      canvas.style.height = `${height}px`
      canvas.width = Math.max(1, Math.floor(width * dpr))
      canvas.height = Math.max(1, Math.floor(height * dpr))

      visualizerRef.current?.resize()
    }

    const observer = new ResizeObserver(resizeCanvas)
    observer.observe(container)
    resizeCanvas()

    return () => observer.disconnect()
  }, [])

  return (
    <div
      ref={containerRef}
      style={{
        minWidth: 0,
        height: '100%',
        position: 'relative',
        overflow: 'hidden',
      }}
    >
      <canvas
        ref={canvasRef}
        style={{
          position: 'absolute',
          top: 0,
          left: 0,
          width: '100%',
          height: '100%',
        }}
      />
    </div>
  )
}
