import { lazy, Suspense, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import type { ThreeActionSceneHandle } from './ThreeActionScene'
import {
  type ActionCharacter,
  type ActionDifficulty,
  type ActionRunMode,
} from '../actionMode'
import { createActionCombatProfile } from '../actionCombatProfile'
import {
  getActionSpellbook,
  getActionSpellDefinition,
  getActionSpellIconUrl,
  getActionSpellManaCost,
  getActionSpellTarget,
  getActionSpellTooltip,
} from '../actionCombatCore'
import {
  createBulldromeState,
  getEncounterHp,
  getEncounterTitle,
  getRaidFrames,
  type BulldromeState,
  type ActionDungeonId,
  type SpellSlot,
} from '../actionBoss/actionCombatSimulation'
import { CombatActionBar, CombatPartyFrames, CombatTargetFrame, type CombatActionSlot } from './CombatHud'
import { playCombatSound } from './CombatAudio'
import { PauseSettingsMenu } from './CombatPauseMenu'
import { ActionCharacterMenu, ActionInventoryMenu, ActionMenuOverlay, ActionTalentMenu } from './ActionEquipmentMenus'

type BulldromeBossSliceProps = {
  character?: ActionCharacter
  dungeonId?: ActionDungeonId
  difficulty?: ActionDifficulty
  runMode?: ActionRunMode
  onEquipItem?: (itemId: string) => void
  onExit: () => void
  onCharacterChange?: (character: ActionCharacter) => void
  onRunComplete?: () => void
}

const ThreeActionScene = lazy(() => import('./ThreeActionScene'))

const DUNGEON_SFX = {
  cast: '/audio/sfx/cast_holy.mp3',
  heal: '/audio/sfx/heal_impact.mp3',
  buff: '/audio/sfx/buff_apply.mp3',
  hit: '/audio/sfx/impact_flesh.mp3',
  hurt: '/audio/sfx/player_hurt.mp3',
  swing: '/audio/sfx/melee_swing_blade.mp3',
}

export function BulldromeBossSlice({
  character,
  dungeonId = 'bulldrome',
  difficulty = 'ilvl-1',
  onEquipItem,
  onCharacterChange,
  runMode = 'hunt',
  onExit,
  onRunComplete,
}: BulldromeBossSliceProps) {
  const combatProfile = useMemo(() => createActionCombatProfile(character), [character])
  const threeSceneRef = useRef<ThreeActionSceneHandle | null>(null)
  const completionSentRef = useRef(false)
  const rewardedBossKillsRef = useRef(0)
  const [paused, setPaused] = useState(false)
  const [pausePanel, setPausePanel] = useState<'settings' | 'character' | 'inventory' | 'talents'>('settings')
  const [renderUnavailable, setRenderUnavailable] = useState(false)
  const [state, setState] = useState<BulldromeState>(() => createBulldromeState(difficulty, dungeonId, runMode, combatProfile.classId, combatProfile.talentModifiers))
  const audioUnlockedRef = useRef(false)
  const seenHealEventIdsRef = useRef(new Set<string>())
  const previousLastHitRef = useRef<BulldromeState['lastHit']>(null)
  const handleRenderUnavailable = useCallback(() => {
    setRenderUnavailable(true)
  }, [])
  const raidFrames = useMemo(() => getRaidFrames(state), [state])
  const encounterHp = useMemo(() => getEncounterHp(state), [state])
  const encounterTitle = useMemo(() => getEncounterTitle(state), [state])
  const selectedTarget = useMemo(() => getDungeonTargetFrame(state), [state])

  useEffect(() => {
    const handleKeyDown = (event: KeyboardEvent) => {
      if (event.repeat) return

      if (event.key === 'Escape') {
        event.preventDefault()
        setPausePanel('settings')
        setPaused((current) => !current)
        return
      }

      if (paused) return

      if (event.key === 'ArrowUp' || event.key === 'ArrowDown') {
        event.preventDefault()
        const selectedIndex = Math.max(0, raidFrames.findIndex((frame) => frame.selected))
        const delta = event.key === 'ArrowDown' ? 1 : -1
        const nextFrame = raidFrames[(selectedIndex + delta + raidFrames.length) % raidFrames.length]
        if (nextFrame) {
          playCombatSound(DUNGEON_SFX.buff, { unlocked: audioUnlockedRef, volume: 0.22 })
          threeSceneRef.current?.selectTarget(nextFrame.id)
        }
      }

      if (event.key === 'Tab') {
        event.preventDefault()
        const target = [state.boss, ...state.adds].find((enemy) => enemy.hp > 0)
        if (target) {
          playCombatSound(DUNGEON_SFX.buff, { unlocked: audioUnlockedRef, volume: 0.22 })
          threeSceneRef.current?.selectTarget(target.id)
        }
      }

      const spellKey = event.key === '0' ? 10 : Number(event.key)
      if (getActionSpellbook(state.player.classId).bar.includes(spellKey as SpellSlot)) {
        event.preventDefault()
        playCombatSound(DUNGEON_SFX.cast, { unlocked: audioUnlockedRef })
        threeSceneRef.current?.castSpell(spellKey as SpellSlot)
      }
    }

    window.addEventListener('keydown', handleKeyDown)
    return () => window.removeEventListener('keydown', handleKeyDown)
  }, [paused, raidFrames, state.adds, state.boss, state.player.classId])

  useEffect(() => {
    for (const event of state.healEvents) {
      if (seenHealEventIdsRef.current.has(event.id)) continue
      seenHealEventIdsRef.current.add(event.id)
      playCombatSound(DUNGEON_SFX.heal, { unlocked: audioUnlockedRef, volume: 0.42 })
    }
    if (state.result !== 'playing') return
    if (state.lastHit && state.lastHit !== previousLastHitRef.current) {
      if (state.lastHit === 'player') playCombatSound(DUNGEON_SFX.hurt, { unlocked: audioUnlockedRef, volume: 0.38 })
      if (state.lastHit === 'boss') playCombatSound(DUNGEON_SFX.hit, { unlocked: audioUnlockedRef, volume: 0.35 })
    }
    previousLastHitRef.current = state.lastHit
  }, [state.healEvents, state.lastHit, state.result])

  useEffect(() => {
    if (runMode === 'marathon') {
      if (state.bossKills <= rewardedBossKillsRef.current) return
      rewardedBossKillsRef.current = state.bossKills
      onRunComplete?.()
      return
    }

    if (state.result !== 'win' || completionSentRef.current) return
    completionSentRef.current = true
    onRunComplete?.()
  }, [onRunComplete, runMode, state.bossKills, state.result])

  return (
    <main className="boss-slice-shell dungeon-combat-shell">
      <section className="boss-slice-stage">
        <div className="boss-slice-layout dungeon-combat-layout">
          <div className="boss-playfield-panel">
            <div className="boss-playfield-frame">
              <div className="arena-boss-window-party dungeon-window-party" aria-label="Party frames">
                <CombatPartyFrames
                  compact
                  frames={raidFrames}
                  onSelect={(id) => {
                    playCombatSound(DUNGEON_SFX.buff, { unlocked: audioUnlockedRef, volume: 0.22 })
                    threeSceneRef.current?.selectTarget(id)
                  }}
                />
              </div>
              <div className="boss-window-bossbar dungeon-encounter-frame">
                <strong>{encounterTitle}</strong>
                <span>{Math.ceil(encounterHp.hp)} / {encounterHp.maxHp}</span>
                <i>
                  <b style={{ width: `${Math.max(0, Math.min(100, (encounterHp.hp / encounterHp.maxHp) * 100))}%` }} />
                </i>
              </div>
              {state.player.currentCast && (
                <div className="boss-castbar boss-field-castbar">
                  <div>
                    <strong>{getActionSpellDefinition(state.player.classId, state.player.currentCast.spell, state.player.talentModifiers).name}</strong>
                    <span>{state.player.currentCast.remaining.toFixed(1)}s</span>
                  </div>
                  <i>
                    <b
                      style={{
                        width: `${Math.max(0, Math.min(100, ((state.player.currentCast.total - state.player.currentCast.remaining) / state.player.currentCast.total) * 100))}%`,
                      }}
                    />
                  </i>
                </div>
              )}
              {selectedTarget && (
                <CombatTargetFrame
                  className="dungeon-target-chip"
                  detail={selectedTarget.detail}
                  hp={selectedTarget.hp}
                  kindLabel={selectedTarget.kindLabel}
                  label={selectedTarget.label}
                  maxHp={selectedTarget.maxHp}
                  tone={selectedTarget.tone}
                />
              )}
              <CombatActionBar
                className="dungeon-actionbar"
                columns={getActionSpellbook(state.player.classId).bar.length}
                globalCooldown={state.player.globalCooldown}
                onCast={(slot) => {
                  playCombatSound(DUNGEON_SFX.cast, { unlocked: audioUnlockedRef })
                  threeSceneRef.current?.castSpell(slot)
                }}
                slots={createDungeonActionSlots(state, selectedTarget?.kind ?? 'ally')}
              />
              {renderUnavailable ? (
                <div className="boss-render-loading">3D renderer unavailable.</div>
              ) : (
                <Suspense fallback={<div className="boss-render-loading">Loading 3D hunt...</div>}>
                  <ThreeActionScene
                    difficulty={difficulty}
                    dungeonId={dungeonId}
                    onStateChange={setState}
                    onRenderUnavailable={handleRenderUnavailable}
                    paused={paused}
                    playerClassId={combatProfile.classId}
                    playerTalentModifiers={combatProfile.talentModifiers}
                    ref={threeSceneRef}
                    runMode={runMode}
                  />
                </Suspense>
              )}
              {paused && (
                pausePanel === 'settings' ? (
                  <PauseSettingsMenu
                    exitLabel="Exit Run"
                    onExit={onExit}
                    onOpenCharacter={character ? () => setPausePanel('character') : undefined}
                    onOpenInventory={character && onEquipItem ? () => setPausePanel('inventory') : undefined}
                    onOpenTalents={character ? () => setPausePanel('talents') : undefined}
                    onResume={() => setPaused(false)}
                  />
                ) : character && pausePanel === 'character' ? (
                  <ActionMenuOverlay
                    onBack={() => setPausePanel('settings')}
                    onClose={() => setPaused(false)}
                    title="Character"
                  >
                    <ActionCharacterMenu
                      character={character}
                      onOpenInventory={onEquipItem ? () => setPausePanel('inventory') : undefined}
                      onOpenTalents={() => setPausePanel('talents')}
                    />
                  </ActionMenuOverlay>
                ) : character && pausePanel === 'talents' ? (
                  <ActionMenuOverlay
                    onBack={() => setPausePanel('settings')}
                    onClose={() => setPaused(false)}
                    title="Talents"
                  >
                    <ActionTalentMenu character={character} onCharacterChange={onCharacterChange} />
                  </ActionMenuOverlay>
                ) : character && onEquipItem ? (
                  <ActionMenuOverlay
                    onBack={() => setPausePanel('settings')}
                    onClose={() => setPaused(false)}
                    title="Inventory"
                  >
                    <ActionInventoryMenu character={character} onEquip={onEquipItem} />
                  </ActionMenuOverlay>
                ) : null
              )}
            </div>
          </div>
        </div>
      </section>
    </main>
  )
}

function createDungeonActionSlots(state: BulldromeState, targetKind: 'ally' | 'enemy'): Array<CombatActionSlot<SpellSlot>> {
  return getActionSpellbook(state.player.classId).bar.map((slot) => {
    const spell = getActionSpellDefinition(state.player.classId, slot, state.player.talentModifiers)
    const manaCost = getActionSpellManaCost(state.player.classId, slot, { freeCast: state.player.storedMomentumReady, talentMods: state.player.talentModifiers })
    const canAfford = state.player.mana >= manaCost
    const spellTarget = getActionSpellTarget(state.player.classId, slot)
    const tooltip = getActionSpellTooltip(state.player.classId, slot)
    const needsEnemy = spellTarget === 'Enemy target'
    const hasTarget = needsEnemy ? targetKind === 'enemy' : targetKind === 'ally'
    return {
      canCast: canAfford && hasTarget,
      cooldown: state.player.spellCooldowns[slot],
      cooldownBase: spell.cooldown,
      globalClassName: `${canAfford ? '' : 'oom'} ${hasTarget ? '' : 'no-target'}`,
      iconUrl: getActionSpellIconUrl(state.player.classId, slot),
      id: slot,
      keybind: slot === 10 ? '0' : String(slot),
      name: spell.name,
      tooltip: {
        cast: spell.castTime > 0 ? `${spell.castTime}s` : 'Instant',
        cooldown: spell.cooldown > 0 ? `${spell.cooldown}s` : undefined,
        cost: `${manaCost} mana`,
        description: tooltip.description,
        rank: `Rank ${tooltip.rank} · highest at level 20`,
        school: tooltip.school,
        target: tooltip.target,
      },
    }
  })
}

function getDungeonTargetFrame(state: BulldromeState): {
  detail: string
  hp: number
  kind: 'ally' | 'enemy'
  kindLabel: string
  label: string
  maxHp: number
  tone: 'ally' | 'enemy'
} | null {
  const ally = [state.player, ...state.party].find((unit) => unit.id === state.targetId)
  if (ally) {
    return {
      detail: `${ally.role.toUpperCase()} · ${Math.ceil(ally.hp)} / ${ally.maxHp}`,
      hp: ally.hp,
      kind: 'ally',
      kindLabel: 'Target Ally',
      label: ally.name,
      maxHp: ally.maxHp,
      tone: 'ally',
    }
  }

  const enemy = [state.boss, ...state.adds].find((unit) => unit.id === state.targetId)
  if (!enemy) return null
  return {
    detail: `${enemy.kind.toUpperCase()} · ${Math.ceil(enemy.hp)} / ${enemy.maxHp}`,
    hp: enemy.hp,
    kind: 'enemy',
    kindLabel: enemy.id === state.boss.id ? 'Target Boss' : 'Target Mob',
    label: enemy.name,
    maxHp: enemy.maxHp,
    tone: 'enemy',
  }
}
