import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react'
import type { StepDirection } from '../gesture/types'
import type { ChartNote } from './sscParser'
import type { AudioPlayer } from './audioPlayer'
import { gameLog } from './gameLog'
import './ArrowGame.css'

type ArrowDirection = 'left' | 'up' | 'down' | 'right'
type Judgment = 'perfect' | 'good' | 'miss'

interface GameArrow {
  id: number
  direction: ArrowDirection
  targetTime: number  // performance.now() timestamp when arrow should reach target
  status: 'active' | Judgment
}

const SCROLL_DURATION = 3000
const PERFECT_WINDOW = 180
const GOOD_WINDOW = 350
const CLEANUP_DELAY = 600

const STEP_TO_ARROW: Record<StepDirection, ArrowDirection> = {
  left: 'left',
  right: 'right',
  forward: 'up',
  back: 'down',
}

const LANE_X: Record<ArrowDirection, string> = {
  left: 'var(--lane-left)',
  up: 'var(--lane-up)',
  down: 'var(--lane-down)',
  right: 'var(--lane-right)',
}

export interface ArrowGameHandle {
  handleStep: (direction: StepDirection) => void
}

interface Props {
  active?: boolean
  chartNotes?: ChartNote[]
  audioPlayer?: AudioPlayer | null
  onJudgment?: (judgment: 'perfect' | 'good' | 'miss', combo: number) => void
  onSongEnd?: () => void
  onProgress?: (progress: number) => void
}

export const ArrowGame = forwardRef<ArrowGameHandle, Props>(({ active = true, chartNotes, audioPlayer, onJudgment, onSongEnd, onProgress }, ref) => {
  const arrowsRef = useRef<GameArrow[]>([])
  const nextId = useRef(0)
  const [arrows, setArrows] = useState<GameArrow[]>([])
  const [, setScore] = useState({ perfect: 0, good: 0, miss: 0, combo: 0 })
  const comboRef = useRef(0)
  const [judgment, setJudgment] = useState<{ text: string; key: number; x: number; y: number; rot: number } | null>(null)
  const judgmentTimer = useRef<ReturnType<typeof setTimeout> | null>(null)
  const [targetPulse, setTargetPulse] = useState<{ dir: ArrowDirection; key: number } | null>(null)
  const songStartRef = useRef<number | null>(null)
  const spawnedUpToRef = useRef(0)
  const onSongEndRef = useRef(onSongEnd)
  onSongEndRef.current = onSongEnd
  const onProgressRef = useRef(onProgress)
  onProgressRef.current = onProgress

  const showJudgment = useCallback((text: string) => {
    if (judgmentTimer.current) clearTimeout(judgmentTimer.current)
    const x = 25 + Math.random() * 50
    const y = 25 + Math.random() * 40
    const rot = (Math.random() - 0.5) * 20
    setJudgment({ text, key: Date.now(), x, y, rot })
    judgmentTimer.current = setTimeout(() => setJudgment(null), 600)
  }, [])

  const lastStepRef = useRef<{ dir: ArrowDirection; time: number } | null>(null)

  useImperativeHandle(ref, () => ({
    handleStep(stepDir: StepDirection) {
      const dir = STEP_TO_ARROW[stepDir]
      const now = performance.now()

      // Debounce: ignore same direction within 200ms
      const last = lastStepRef.current
      if (last && last.dir === dir && now - last.time < 200) {
        gameLog(`step DEBOUNCED: ${dir} (${(now - last.time).toFixed(0)}ms since last)`)
        return
      }
      lastStepRef.current = { dir, time: now }

      setTargetPulse({ dir, key: now })

      const activeArrows = arrowsRef.current.filter(a => a.direction === dir && a.status === 'active')
      let best: GameArrow | null = null
      let bestDelta = Infinity

      for (const a of activeArrows) {
        const d = Math.abs(now - a.targetTime)
        if (d < bestDelta && d <= GOOD_WINDOW) {
          bestDelta = d
          best = a
        }
      }

      gameLog(`step ${dir}: ${activeArrows.length} active arrows, best=${best?.id ?? 'none'} delta=${bestDelta.toFixed(0)}ms`)

      if (!best) return // no arrow in range — ignore the step

      const j: Judgment = bestDelta <= PERFECT_WINDOW ? 'perfect' : 'good'
      best.status = j
      setArrows([...arrowsRef.current])
      comboRef.current += 1
      const combo = comboRef.current
      setScore(prev => ({
        ...prev,
        [j]: prev[j] + 1,
        combo,
      }))
      showJudgment(j === 'perfect' ? 'PERFECT' : 'GOOD')
      onJudgment?.(j, combo)

      const id = best.id
      setTimeout(() => {
        arrowsRef.current = arrowsRef.current.filter(a => a.id !== id)
        setArrows([...arrowsRef.current])
      }, CLEANUP_DELAY)
    },
  }))

  // All audio/spawn logic in one effect + one interval that polls activeRef
  const activeRef = useRef(active)
  activeRef.current = active

  useEffect(() => {
    gameLog(`effect: chartNotes=${chartNotes?.length ?? 'null'} audioPlayer=${audioPlayer ? 'yes' : 'null'}`)
    if (!chartNotes || !audioPlayer) {
      gameLog('effect: SKIP — missing chartNotes or audioPlayer')
      return
    }

    // Reset
    arrowsRef.current = []
    setArrows([])
    spawnedUpToRef.current = 0
    comboRef.current = 0
    setScore({ perfect: 0, good: 0, miss: 0, combo: 0 })

    const ap = audioPlayer
    songStartRef.current = null

    let wasActive = false
    let pausedAt = 0
    let tickCount = 0

    const unsubEnded = ap.onEnded(() => {
      gameLog('audio: ended event')
      onSongEndRef.current?.()
    })

    gameLog(`effect: audioPlayer ready=${ap.ready}, active=${activeRef.current}`)

    const iv = setInterval(() => {
      const isActive = activeRef.current
      tickCount++

      // Log state every 2 seconds (40 ticks)
      if (tickCount % 40 === 1) {
        gameLog(`tick: active=${isActive} wasActive=${wasActive} ready=${ap.ready} songStart=${songStartRef.current !== null} audioTime=${ap.currentTime.toFixed(1)} spawned=${spawnedUpToRef.current}/${chartNotes.length} arrows=${arrowsRef.current.length}`)
      }

      // Handle active transitions
      if (isActive && !wasActive) {
        if (songStartRef.current === null) {
          if (!ap.ready) {
            if (tickCount % 20 === 1) gameLog('tick: waiting for audio buffer...')
            return
          }
          gameLog('tick: playing audio (first play)')
          ap.play().then(() => {
            songStartRef.current = performance.now() - ap.currentTime * 1000
            gameLog(`tick: play() resolved, songStart set, audioTime=${ap.currentTime.toFixed(2)}`)
          }).catch((e) => gameLog(`tick: play() FAILED: ${e}`))
        } else {
          const pauseDuration = performance.now() - pausedAt
          gameLog(`tick: resuming, pauseDuration=${(pauseDuration/1000).toFixed(1)}s`)
          songStartRef.current += pauseDuration
          for (const a of arrowsRef.current) {
            a.targetTime += pauseDuration
          }
          ap.play().catch(() => {})
        }
        wasActive = true
      } else if (!isActive && wasActive) {
        gameLog('tick: pausing audio')
        pausedAt = performance.now()
        ap.pause()
        wasActive = false
      }

      // Report progress
      if (ap.duration) {
        onProgressRef.current?.(ap.currentTime / ap.duration)
      }

      // Spawn arrows when playing
      if (songStartRef.current === null || !isActive) return

      const now = performance.now()
      const songTime = (now - songStartRef.current) / 1000
      const lookAhead = songTime + SCROLL_DURATION / 1000

      const beforeCount = spawnedUpToRef.current
      while (spawnedUpToRef.current < chartNotes.length) {
        const note = chartNotes[spawnedUpToRef.current]
        if (note.time > lookAhead) break

        const targetTime = songStartRef.current + note.time * 1000
        arrowsRef.current.push({
          id: nextId.current++,
          direction: note.direction,
          targetTime,
          status: 'active',
        })
        spawnedUpToRef.current++
      }
      if (spawnedUpToRef.current > beforeCount) {
        gameLog(`tick: spawned ${spawnedUpToRef.current - beforeCount} arrows (${beforeCount}→${spawnedUpToRef.current}), songTime=${songTime.toFixed(1)}s`)
      }

      setArrows([...arrowsRef.current])
    }, 50)

    return () => {
      gameLog('effect: CLEANUP')
      clearInterval(iv)
      unsubEnded()
      songStartRef.current = null
    }
  }, [chartNotes, audioPlayer])

  // Miss detection + cleanup
  useEffect(() => {
    if (!active) return
    const iv = setInterval(() => {
      const now = performance.now()
      let changed = false

      for (const a of arrowsRef.current) {
        if (a.status === 'active' && now > a.targetTime + GOOD_WINDOW) {
          a.status = 'miss'
          changed = true
          comboRef.current = 0
          setScore(prev => ({ ...prev, miss: prev.miss + 1, combo: 0 }))
          showJudgment('MISS')
          onJudgment?.('miss', 0)

          const id = a.id
          setTimeout(() => {
            arrowsRef.current = arrowsRef.current.filter(x => x.id !== id)
            setArrows([...arrowsRef.current])
          }, CLEANUP_DELAY)
        }
      }

      if (changed) setArrows([...arrowsRef.current])
    }, 50)
    return () => clearInterval(iv)
  }, [active, showJudgment, onJudgment])

  return (
    <div className="arrow-game">
      {null /* combo moved to GamePage bottomHud */}

      {judgment && (
        <div
          key={judgment.key}
          className={`ag-judgment ag-judgment-${judgment.text.toLowerCase()}`}
          style={{
            left: `${judgment.x}%`,
            top: `${judgment.y}%`,
            transform: `translate(-50%, -50%) rotate(${judgment.rot}deg)`,
          }}
        >
          {judgment.text}
        </div>
      )}

      <div className="ag-targets">
        {(['left', 'up', 'down', 'right'] as ArrowDirection[]).map(dir => (
          <div
            key={targetPulse?.dir === dir ? `${dir}-${targetPulse.key}` : dir}
            className={`ag-target${targetPulse?.dir === dir ? ' ag-target-pulse' : ''}`}
            data-dir={dir}
            style={{ left: LANE_X[dir] }}
          >
            <Arrow direction={dir} />
          </div>
        ))}
      </div>

      {active && arrows.map(a => (
        <div
          key={a.id}
          className={`ag-arrow ag-arrow-${a.status}`}
          data-dir={a.direction}
          style={{
            left: LANE_X[a.direction],
            animationDuration: `${SCROLL_DURATION}ms`,
          }}
        >
          <Arrow direction={a.direction} />
        </div>
      ))}
    </div>
  )
})

function Arrow({ direction }: { direction: ArrowDirection }) {
  const rot: Record<ArrowDirection, number> = { up: 0, right: 90, down: 180, left: 270 }
  return (
    <svg viewBox="0 0 64 64" className="ag-arrow-svg" style={{ transform: `rotate(${rot[direction]}deg)` }}>
      <path d="M32 4L58 36H44v24H20V36H6z" fill="rgba(0,0,0,0.7)" />
      <path d="M32 4L58 36H44v24H20V36H6z" fill="currentColor" stroke="rgba(255,255,255,0.6)" strokeWidth="3" />
    </svg>
  )
}
