import { useState, useRef, useEffect } from 'react'
import { useStore } from '@nanostores/react'
import { PlaybackManager } from './services/PlaybackManager'
import { DownloadService } from './services/DownloadService'
import { generateFormulaGrid, generateRandomFormula } from './utils/bytebeatFormulas'
import { BytebeatTile } from './components/BytebeatTile'
import { EffectsBar } from './components/EffectsBar'
import { EngineControls } from './components/EngineControls'
import { getSampleRateFromIndex } from './config/effects'
import { engineSettings, effectSettings } from './stores/settings'

function App() {
  const engineValues = useStore(engineSettings)
  const effectValues = useStore(effectSettings)

  const [formulas, setFormulas] = useState<string[][]>(() =>
    generateFormulaGrid(100, 2, engineValues.complexity)
  )
  const [playing, setPlaying] = useState<string | null>(null)
  const [queued, setQueued] = useState<string | null>(null)
  const [regenerating, setRegenerating] = useState<string | null>(null)
  const [playbackPosition, setPlaybackPosition] = useState<number>(0)
  const [downloading, setDownloading] = useState(false)
  const playbackManagerRef = useRef<PlaybackManager | null>(null)
  const downloadServiceRef = useRef<DownloadService>(new DownloadService())
  const animationFrameRef = useRef<number | null>(null)

  useEffect(() => {
    effectSettings.setKey('masterVolume', engineValues.masterVolume)
  }, [engineValues.masterVolume])

  const handleRandom = () => {
    setFormulas(generateFormulaGrid(100, 2, engineValues.complexity))
    setQueued(null)
  }

  const playFormula = (formula: string, id: string) => {
    const sampleRate = getSampleRateFromIndex(engineValues.sampleRate)
    const duration = engineValues.loopDuration

    if (!playbackManagerRef.current) {
      playbackManagerRef.current = new PlaybackManager({ sampleRate, duration })
    } else {
      playbackManagerRef.current.updateOptions({ sampleRate, duration })
    }

    playbackManagerRef.current.stop()
    playbackManagerRef.current.setEffects(effectValues)
    playbackManagerRef.current.setPitch(engineValues.pitch ?? 1)

    const success = playbackManagerRef.current.play(formula, sampleRate, duration)

    if (success) {
      setPlaying(id)
      setQueued(null)
      startPlaybackTracking()
    } else {
      console.error('Failed to play formula')
    }
  }

  const startPlaybackTracking = () => {
    if (animationFrameRef.current) {
      cancelAnimationFrame(animationFrameRef.current)
    }

    const updatePosition = () => {
      if (playbackManagerRef.current) {
        const position = playbackManagerRef.current.getPlaybackPosition()
        setPlaybackPosition(position)
        animationFrameRef.current = requestAnimationFrame(updatePosition)
      }
    }
    updatePosition()
  }

  const handleTileClick = (formula: string, row: number, col: number, isDoubleClick: boolean = false) => {
    const id = `${row}-${col}`

    if (playing === id) {
      playbackManagerRef.current?.stop()
      setPlaying(null)
      setQueued(null)
      if (animationFrameRef.current) {
        cancelAnimationFrame(animationFrameRef.current)
        animationFrameRef.current = null
      }
      return
    }

    if (isDoubleClick || playing === null) {
      playFormula(formula, id)
    } else {
      setQueued(id)
      if (playbackManagerRef.current) {
        playbackManagerRef.current.scheduleNextTrack(() => {
          const queuedFormula = formulas.flat()[parseInt(id.split('-')[0]) * 2 + parseInt(id.split('-')[1])]
          if (queuedFormula) {
            playFormula(queuedFormula, id)
          }
        })
      }
    }
  }

  const handleTileDoubleClick = (formula: string, row: number, col: number) => {
    handleTileClick(formula, row, col, true)
  }


  const handleEngineChange = (parameterId: string, value: number) => {
    engineSettings.setKey(parameterId as keyof typeof engineValues, value)

    if (parameterId === 'masterVolume' && playbackManagerRef.current) {
      playbackManagerRef.current.setEffects(effectValues)
    }

    if (parameterId === 'pitch' && playbackManagerRef.current) {
      playbackManagerRef.current.setPitch(value)
    }
  }

  const handleEffectChange = (parameterId: string, value: number | boolean) => {
    effectSettings.setKey(parameterId as any, value as any)
    if (playbackManagerRef.current) {
      playbackManagerRef.current.setEffects(effectValues)
    }
  }

  const handleDownloadAll = async () => {
    setDownloading(true)
    await downloadServiceRef.current.downloadAll(formulas, { duration: 10, bitDepth: 8 })
    setDownloading(false)
  }

  const handleDownloadFormula = (formula: string, filename: string) => {
    downloadServiceRef.current.downloadFormula(formula, filename, { duration: 10, bitDepth: 8 })
  }

  const handleRegenerate = (row: number, col: number) => {
    const id = `${row}-${col}`
    const newFormula = generateRandomFormula(engineValues.complexity)

    setFormulas(prevFormulas => {
      const newFormulas = [...prevFormulas]
      newFormulas[row] = [...newFormulas[row]]
      newFormulas[row][col] = newFormula
      return newFormulas
    })

    if (playing === id && playbackManagerRef.current) {
      setRegenerating(id)
      playbackManagerRef.current.scheduleNextTrack(() => {
        playFormula(newFormula, id)
        setRegenerating(null)
      })
    }
  }

  return (
    <div className="w-screen h-screen flex flex-col bg-black overflow-hidden">
      <header className="bg-black border-b-2 border-white px-6 py-3">
        <div className="flex items-center justify-between mb-3">
          <h1 className="font-mono text-sm tracking-[0.3em] text-white">BRUITISTE</h1>
          <div className="flex gap-4">
            <button
              onClick={handleRandom}
              className="px-6 py-2 bg-white text-black font-mono text-[11px] tracking-[0.2em] hover:bg-black hover:text-white border-2 border-white transition-all"
            >
              RANDOM
            </button>
            <button
              onClick={handleDownloadAll}
              disabled={downloading}
              className="px-6 py-2 bg-black text-white border-2 border-white font-mono text-[11px] tracking-[0.2em] hover:bg-white hover:text-black transition-all disabled:opacity-30 disabled:cursor-not-allowed"
            >
              {downloading ? 'DOWNLOADING...' : 'DOWNLOAD ALL'}
            </button>
          </div>
        </div>
        <EngineControls values={engineValues} onChange={handleEngineChange} />
      </header>

      <div className="flex-1 grid grid-cols-2 auto-rows-min gap-[1px] bg-white p-[1px] overflow-auto">
        {formulas.map((row, i) =>
          row.map((formula, j) => {
            const id = `${i}-${j}`
            return (
              <BytebeatTile
                key={id}
                formula={formula}
                row={i}
                col={j}
                isPlaying={playing === id}
                isQueued={queued === id}
                isRegenerating={regenerating === id}
                playbackPosition={playing === id ? playbackPosition : 0}
                onPlay={handleTileClick}
                onDoubleClick={handleTileDoubleClick}
                onDownload={handleDownloadFormula}
                onRegenerate={handleRegenerate}
              />
            )
          })
        )}
      </div>

      <EffectsBar values={effectValues} onChange={handleEffectChange} />
    </div>
  )
}

export default App
