import { useState, useRef } from 'react'
import { useStore } from '@nanostores/react'
import { Square, Archive, Dices, Sparkles, ArrowLeftRight } from 'lucide-react'
import { DownloadService } from './services/DownloadService'
import { generateRandomFormula } from './utils/bytebeatFormulas'
import { BytebeatTile } from './components/tile/BytebeatTile'
import { EffectsBar } from './components/controls/EffectsBar'
import { EngineControls } from './components/controls/EngineControls'
import { FormulaEditor } from './components/tile/FormulaEditor'
import { LFOPanel } from './components/controls/LFOPanel'
import { AudioContextWarning } from './components/modals/AudioContextWarning'
import { HelpModal } from './components/modals/HelpModal'
import { engineSettings, effectSettings } from './stores/settings'
import { exitMappingMode } from './stores/mappingMode'
import { useKeyboardShortcuts } from './hooks/useKeyboardShortcuts'
import { useTileGrid } from './hooks/useTileGrid'
import { usePlaybackControl } from './hooks/usePlaybackControl'
import { useFocusNavigation } from './hooks/useFocusNavigation'
import { useParameterSync } from './hooks/useParameterSync'
import { useLFOMapping } from './hooks/useLFOMapping'
import type { TileState } from './types/tiles'
import { createTileStateFromCurrent } from './utils/tileState'
import { DEFAULT_DOWNLOAD_OPTIONS, PLAYBACK_ID } from './constants/defaults'
import { getTileId, getTileFromGrid } from './utils/tileHelpers'

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

  const [downloading, setDownloading] = useState(false)
  const [customTile, setCustomTile] = useState<TileState>(() => createTileStateFromCurrent('t*(8&t>>9)'))
  const [showWarning, setShowWarning] = useState(true)
  const [showHelp, setShowHelp] = useState(false)
  const [mobileHeaderTab, setMobileHeaderTab] = useState<'global' | 'options' | 'modulate'>('global')
  const downloadServiceRef = useRef<DownloadService>(new DownloadService())

  const { tiles, setTiles, mode, regenerateAll, regenerateTile, switchMode } = useTileGrid()

  const { playing, queued, playbackPosition, playbackManager, play, stop, queue, cancelQueue, updateMode } =
    usePlaybackControl({ mode })

  const { focusedTile, setFocus, moveFocus } = useFocusNavigation({
    tiles,
    onFocusChange: (tile) => {
      if (tile !== 'custom') {
        const tileData = getTileFromGrid(tiles, tile.row, tile.col)
        if (tileData) {
          saveCurrentParams()
          loadParams(tileData)
        }
      } else {
        saveCurrentParams()
        loadParams(customTile)
      }
    }
  })

  const { saveCurrentParams, loadParams, handleEngineChange, handleEffectChange, randomizeParams, randomizeAllParams, interpolateParams } =
    useParameterSync({
      tiles,
      setTiles,
      customTile,
      setCustomTile,
      focusedTile,
      playbackManager,
      playing,
      playbackId: PLAYBACK_ID.CUSTOM
    })

  const { handleLFOChange, handleParameterMapClick, handleUpdateMappingDepth, handleRemoveMapping, getMappedLFOs } =
    useLFOMapping({
      playbackManager,
      saveCurrentParams
    })

  const handleRandom = () => {
    cancelQueue()
    regenerateAll()
  }

  const handleModeToggle = () => {
    const newMode = mode === 'bytebeat' ? 'fm' : 'bytebeat'
    stop()
    switchMode(newMode)
    updateMode(newMode)
  }

  const handleTileClick = (_formula: string, row: number, col: number, isDoubleClick: boolean = false) => {
    const id = getTileId(row, col)
    const tile = getTileFromGrid(tiles, row, col)

    if (!tile) return

    setFocus({ row, col })

    if (isDoubleClick || playing === null) {
      play(tile.formula, id, tile)
    } else {
      queue(id, () => {
        const queuedTile = getTileFromGrid(tiles, row, col)
        if (queuedTile) {
          play(queuedTile.formula, id, queuedTile)
        }
      })
    }
  }

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

  const handleDownloadAll = async () => {
    setDownloading(true)
    const formulas = tiles.map(row => row.map(tile => tile.formula))
    await downloadServiceRef.current.downloadAll(formulas, {
      duration: DEFAULT_DOWNLOAD_OPTIONS.DURATION,
      bitDepth: DEFAULT_DOWNLOAD_OPTIONS.BIT_DEPTH
    })
    setDownloading(false)
  }

  const handleDownloadFormula = (formula: string, filename: string) => {
    downloadServiceRef.current.downloadFormula(formula, filename, {
      duration: DEFAULT_DOWNLOAD_OPTIONS.DURATION,
      bitDepth: DEFAULT_DOWNLOAD_OPTIONS.BIT_DEPTH
    })
  }

  const handleCustomEvaluate = (formula: string) => {
    setFocus('custom')
    setCustomTile({ ...customTile, formula })
    play(formula, PLAYBACK_ID.CUSTOM, { ...customTile, formula })
  }

  const handleCustomStop = () => {
    if (playing === PLAYBACK_ID.CUSTOM) {
      stop()
    }
  }

  const handleCustomRandom = () => {
    return generateRandomFormula(engineValues.complexity)
  }

  const handleKeyboardSpace = () => {
    if (playing) {
      stop()
    } else if (focusedTile !== 'custom') {
      const tile = tiles[focusedTile.row]?.[focusedTile.col]
      if (tile) {
        handleTileClick(tile.formula, focusedTile.row, focusedTile.col, true)
      }
    }
  }

  const handleKeyboardEnter = () => {
    if (focusedTile !== 'custom') {
      const tile = tiles[focusedTile.row]?.[focusedTile.col]
      if (tile) {
        handleTileClick(tile.formula, focusedTile.row, focusedTile.col, false)
      }
    }
  }

  const handleKeyboardDoubleEnter = () => {
    if (focusedTile !== 'custom') {
      const tile = tiles[focusedTile.row]?.[focusedTile.col]
      if (tile) {
        handleTileClick(tile.formula, focusedTile.row, focusedTile.col, true)
      }
    }
  }

  const handleRegenerate = (row: number, col: number) => {
    const newTile = regenerateTile(row, col)
    const tileId = getTileId(row, col)

    if (playing === tileId) {
      play(newTile.formula, tileId, newTile)
    }
  }

  const handleKeyboardR = () => {
    if (focusedTile !== 'custom') {
      handleRegenerate(focusedTile.row, focusedTile.col)
    }
  }

  const handleKeyboardC = () => {
    const tileId = focusedTile === 'custom'
      ? PLAYBACK_ID.CUSTOM
      : getTileId(focusedTile.row, focusedTile.col)
    randomizeParams(tileId)
  }

  const handleDismissWarning = () => {
    setShowWarning(false)
  }

  useKeyboardShortcuts({
    onSpace: handleKeyboardSpace,
    onArrowUp: (shift) => moveFocus('up', shift ? 10 : 1),
    onArrowDown: (shift) => moveFocus('down', shift ? 10 : 1),
    onArrowLeft: (shift) => moveFocus('left', shift ? 10 : 1),
    onArrowRight: (shift) => moveFocus('right', shift ? 10 : 1),
    onEnter: handleKeyboardEnter,
    onDoubleEnter: handleKeyboardDoubleEnter,
    onR: handleKeyboardR,
    onShiftR: handleRandom,
    onC: handleKeyboardC,
    onShiftC: randomizeAllParams,
    onI: interpolateParams,
    onEscape: exitMappingMode
  })

  return (
    <div className="w-screen h-screen flex flex-col bg-black overflow-hidden">
      {showWarning && <AudioContextWarning onDismiss={handleDismissWarning} />}
      {showHelp && <HelpModal onClose={() => setShowHelp(false)} />}
      <header className="bg-black border-b-2 border-white px-2 lg:px-6 py-2 lg:py-3">
        {/* Mobile header */}
        <div className="lg:hidden">
          <div className="flex items-center justify-between mb-2">
            <h1
              onClick={() => setShowHelp(true)}
              className="font-mono text-[10px] tracking-[0.3em] text-white cursor-pointer hover:opacity-70 transition-opacity"
            >
              BRUIT
            </h1>
            <div className="flex gap-1">
              <div className="flex border-2 border-white">
                <button
                  onClick={handleModeToggle}
                  className={`px-2 py-1 font-mono text-[7px] tracking-[0.15em] transition-colors ${
                    mode === 'bytebeat'
                      ? 'bg-white text-black'
                      : 'bg-black text-white hover:bg-white/10'
                  }`}
                >
                  BYTE
                </button>
                <button
                  onClick={handleModeToggle}
                  className={`px-2 py-1 font-mono text-[7px] tracking-[0.15em] transition-colors ${
                    mode === 'fm'
                      ? 'bg-white text-black'
                      : 'bg-black text-white hover:bg-white/10'
                  }`}
                >
                  4OP-FM
                </button>
              </div>
              <div className="flex border-2 border-white">
                <button
                  onClick={() => setMobileHeaderTab('global')}
                  className={`px-2 py-1 font-mono text-[8px] tracking-[0.15em] transition-colors ${
                    mobileHeaderTab === 'global'
                      ? 'bg-white text-black'
                      : 'bg-black text-white hover:bg-white/10'
                  }`}
                >
                  GLOBAL
                </button>
                <button
                  onClick={() => setMobileHeaderTab('options')}
                  className={`px-2 py-1 font-mono text-[8px] tracking-[0.15em] transition-colors ${
                    mobileHeaderTab === 'options'
                      ? 'bg-white text-black'
                      : 'bg-black text-white hover:bg-white/10'
                  }`}
                >
                  OPTIONS
                </button>
                <button
                  onClick={() => setMobileHeaderTab('modulate')}
                  className={`px-2 py-1 font-mono text-[8px] tracking-[0.15em] transition-colors ${
                    mobileHeaderTab === 'modulate'
                      ? 'bg-white text-black'
                      : 'bg-black text-white hover:bg-white/10'
                  }`}
                >
                  MOD
                </button>
              </div>
            </div>
          </div>

          {mobileHeaderTab === 'global' && (
            <div className="flex gap-2">
              <button
                onClick={stop}
                disabled={!playing}
                className="flex-1 px-2 py-2 bg-black text-white border-2 border-white font-mono text-[9px] tracking-[0.2em] hover:bg-white hover:text-black transition-all disabled:opacity-30 disabled:cursor-not-allowed"
              >
                <Square size={12} strokeWidth={2} fill="currentColor" className="mx-auto" />
              </button>
              <button
                onClick={handleRandom}
                className="flex-1 px-2 py-2 bg-white text-black font-mono text-[9px] tracking-[0.2em] hover:bg-black hover:text-white border-2 border-white transition-all"
              >
                <Dices size={12} strokeWidth={2} className="mx-auto" />
              </button>
              <button
                onClick={randomizeAllParams}
                className="flex-1 px-2 py-2 bg-white text-black font-mono text-[9px] tracking-[0.2em] hover:bg-black hover:text-white border-2 border-white transition-all"
              >
                <Sparkles size={12} strokeWidth={2} className="mx-auto" />
              </button>
              <button
                onClick={interpolateParams}
                className="flex-1 px-2 py-2 bg-white text-black font-mono text-[9px] tracking-[0.2em] hover:bg-black hover:text-white border-2 border-white transition-all"
              >
                <ArrowLeftRight size={12} strokeWidth={2} className="mx-auto" />
              </button>
              <button
                onClick={handleDownloadAll}
                disabled={downloading}
                className="flex-1 px-2 py-2 bg-black text-white border-2 border-white font-mono text-[9px] tracking-[0.2em] hover:bg-white hover:text-black transition-all disabled:opacity-30 disabled:cursor-not-allowed"
              >
                <Archive size={12} strokeWidth={2} className="mx-auto" />
              </button>
            </div>
          )}

          {mobileHeaderTab === 'options' && (
            <EngineControls
              values={engineValues}
              onChange={handleEngineChange}
              onMapClick={handleParameterMapClick}
              getMappedLFOs={getMappedLFOs}
              showOnlySliders
            />
          )}

          {mobileHeaderTab === 'modulate' && (
            <EngineControls
              values={engineValues}
              onChange={handleEngineChange}
              onMapClick={handleParameterMapClick}
              getMappedLFOs={getMappedLFOs}
              showOnlyKnobs
            />
          )}
        </div>

        {/* Desktop header */}
        <div className="hidden lg:flex items-center gap-4">
          <div className="flex flex-col gap-1 flex-shrink-0">
            <h1
              onClick={() => setShowHelp(true)}
              className="font-mono text-sm tracking-[0.3em] text-white cursor-pointer hover:opacity-70 transition-opacity"
            >
              BRUITISTE
            </h1>
            <div className="flex border-2 border-white w-fit">
              <button
                onClick={handleModeToggle}
                className={`px-2 py-0.5 font-mono text-[7px] tracking-[0.15em] transition-colors whitespace-nowrap ${
                  mode === 'bytebeat'
                    ? 'bg-white text-black'
                    : 'bg-black text-white hover:bg-white/10'
                }`}
              >
                1-BIT
              </button>
              <button
                onClick={handleModeToggle}
                className={`px-2 py-0.5 font-mono text-[7px] tracking-[0.15em] transition-colors whitespace-nowrap ${
                  mode === 'fm'
                    ? 'bg-white text-black'
                    : 'bg-black text-white hover:bg-white/10'
                }`}
              >
                4OP-FM
              </button>
            </div>
          </div>
          <div className="flex-1">
            <EngineControls
              values={engineValues}
              onChange={handleEngineChange}
              onMapClick={handleParameterMapClick}
              getMappedLFOs={getMappedLFOs}
            />
          </div>
          <div className="flex gap-3 flex-shrink-0">
            <button
              onClick={stop}
              disabled={!playing}
              className="px-4 py-2 bg-black text-white border-2 border-white font-mono text-[10px] tracking-[0.2em] hover:bg-white hover:text-black transition-all disabled:opacity-30 disabled:cursor-not-allowed"
            >
              <Square size={12} strokeWidth={2} fill="currentColor" />
            </button>
            <button
              onClick={handleRandom}
              className="px-4 py-2 bg-white text-black font-mono text-[10px] tracking-[0.2em] hover:bg-black hover:text-white border-2 border-white transition-all"
            >
              <Dices size={12} strokeWidth={2} />
            </button>
            <button
              onClick={randomizeAllParams}
              className="px-4 py-2 bg-white text-black font-mono text-[10px] tracking-[0.2em] hover:bg-black hover:text-white border-2 border-white transition-all"
            >
              <Sparkles size={12} strokeWidth={2} />
            </button>
            <button
              onClick={interpolateParams}
              className="px-4 py-2 bg-white text-black font-mono text-[10px] tracking-[0.2em] hover:bg-black hover:text-white border-2 border-white transition-all flex items-center gap-1"
            >
              <ArrowLeftRight size={12} strokeWidth={2} />
              MORPH
            </button>
            <button
              onClick={handleDownloadAll}
              disabled={downloading}
              className="px-4 py-2 bg-black text-white border-2 border-white font-mono text-[10px] tracking-[0.2em] hover:bg-white hover:text-black transition-all disabled:opacity-30 disabled:cursor-not-allowed"
            >
              <Archive size={12} strokeWidth={2} />
            </button>
          </div>
        </div>
      </header>

      <LFOPanel onChange={handleLFOChange} onUpdateDepth={handleUpdateMappingDepth} onRemoveMapping={handleRemoveMapping} />

      <div className="flex-1 flex flex-col overflow-auto bg-white">
        {mode === 'bytebeat' && (
          <div className="grid grid-cols-1 lg:grid-cols-4 gap-[1px] bg-white p-[1px]">
            <div className="col-span-1 lg:col-span-4">
              <FormulaEditor
                formula={customTile.formula}
                isPlaying={playing === PLAYBACK_ID.CUSTOM}
                isFocused={focusedTile === 'custom'}
                playbackPosition={playing === PLAYBACK_ID.CUSTOM ? playbackPosition : 0}
                onEvaluate={handleCustomEvaluate}
                onStop={handleCustomStop}
                onRandom={handleCustomRandom}
              />
            </div>
          </div>
        )}

        <div className="flex-1 grid grid-cols-1 lg:grid-cols-4 auto-rows-min gap-[1px] bg-white p-[1px]">
          {tiles.map((row, i) =>
            row.map((tile, j) => {
              const id = getTileId(i, j)
              return (
                <BytebeatTile
                  key={id}
                  formula={tile.formula}
                  row={i}
                  col={j}
                  isPlaying={playing === id}
                  isQueued={queued === id}
                  isFocused={focusedTile !== 'custom' && focusedTile.row === i && focusedTile.col === j}
                  playbackPosition={playing === id ? playbackPosition : 0}
                  a={tile.engineParams.a ?? 8}
                  b={tile.engineParams.b ?? 16}
                  c={tile.engineParams.c ?? 32}
                  d={tile.engineParams.d ?? 64}
                  onPlay={handleTileClick}
                  onDoubleClick={handleTileDoubleClick}
                  onDownload={handleDownloadFormula}
                  onRegenerate={handleRegenerate}
                />
              )
            })
          )}
        </div>
      </div>

      <div className="flex-shrink-0">
        <EffectsBar
          values={effectValues}
          onChange={handleEffectChange}
          onMapClick={handleParameterMapClick}
          getMappedLFOs={getMappedLFOs}
        />
      </div>
    </div>
  )
}

export default App
