import { useCallback, useEffect, useMemo, useRef, useState, type CSSProperties, type ReactNode } from 'react'
import { ControllerStylePreview } from '../../../components/ControllerIcons'
import {
  useGameAction,
  useInput,
  type ControllerIconStyle,
  type InputAction,
} from '../../../input'
import { useDualScreen, useDualScreenWorkshopPublisher, type DualScreenWorkshopState } from '../../../dualScreen'
import { getGameMode } from '../../../gameRepository'
import {
  createDefaultIwt2Save,
  loadIwt2OnlineSave,
  setIwt2InfusionAbility,
  updateIwt2CharacterSettings,
  upgradeIwt2GearSlot,
  canAffordIwt2Costs,
  inventoryQuantity,
  writeIwt2OnlineSave,
  type Iwt2Save,
} from '../save/iwt2Repository'
import { IWT2_BOSS_METADATA, type Iwt2BossId } from '../content/bosses'
import { IWT2_CLASS_METADATA, IWT2_PARTY_ORDER, type Iwt2PlayerClassId } from '../content/classes'
import {
  findIwt2Difficulty,
  isIwt2DifficultyUnlocked,
  IWT2_DUNGEON_DIFFICULTIES,
  IWT2_RAID_DIFFICULTIES,
  type Iwt2Difficulty,
} from '../content/difficulties'
import {
  iwt2BossCoinRewardFor,
  iwt2BossCoinRewardsFor,
  iwt2BossPetRewardFor,
  type Iwt2BossCoinReward,
} from '../content/bossRewards'
import {
  abilitiesForHealer,
  IWT2_HEALER_METADATA,
  IWT2_HEALER_ORDER,
} from '../content/healerAbilities'
import {
  type Iwt2RoguelikeChoice,
  type Iwt2RoguelikeContentType,
  type Iwt2RoguelikeOpponentDebuffId,
  type Iwt2RoguelikeSelfBuffId,
  type Iwt2RoguelikeVariant,
} from '../content/roguelike'
import {
  IWT2_GEAR_SLOT_LABELS,
  IWT2_GEAR_SLOT_RECIPES,
  IWT2_GEAR_SLOTS,
  IWT2_GEAR_STAT_LABELS,
  iwt2GearUpgradeCosts,
  iwt2InfusionCosts,
  isIwt2InfusionUnlocked,
  type Iwt2GearStatId,
  type Iwt2GearSlotId,
} from '../content/gear'
import {
  IWT2_INFUSION_ABILITIES,
  iwt2InfusionAbilitiesForClass,
  type Iwt2InfusionAbilityId,
} from '../content/infusionAbilities'

type Iwt2NavAction = {
  key: string
  label: string
  detail?: string
  value?: string
  disabled?: boolean
  onConfirm: () => void
}

type Iwt2Mode = 'Raids'

type Iwt2ScreenShellProps = {
  title: string
  eyebrow?: string
  onBack: () => void
  children: ReactNode
}

type Iwt2ActionListProps = {
  actions: Iwt2NavAction[]
}

type Iwt2DifficultyOption = Iwt2Difficulty & {
  selected: boolean
  locked: boolean
}

type Iwt2InfoRow = {
  key: string
  label: string
  detail: string
  value: string
}

type Iwt2NavPosition = {
  column: number
  row: number
}

type Iwt2UpgradeNavEntry =
  | { kind: 'upgradeBuff', index: number, row: number, column: number }
  | { kind: 'upgradeDebuff', index: number, row: number, column: number }
  | { kind: 'upgradeContinue', row: number, column: number, disabled?: boolean }
  | { kind: 'upgradeLeave', row: number, column: number }

type Iwt2HunterProfileTab = 'stats' | 'collection'

type Iwt2HunterProfileNavEntry =
  | { kind: 'back', key: string, row: number, column: number }
  | { kind: 'tab', key: string, row: number, column: number, tab: Iwt2HunterProfileTab }
  | { kind: 'stat', key: string, row: number, column: number, index: number }
  | { kind: 'boss', key: string, row: number, column: number, bossId: Iwt2BossId }
  | { kind: 'item', key: string, row: number, column: number, itemKey: string }

type Iwt2HunterProfileBossEntry = {
  bossId: Iwt2BossId
  encounter: (typeof IWT2_BOSS_METADATA)[Iwt2BossId]
  kills: number
  coins: Array<Iwt2BossCoinReward & { quantity: number }>
  petId: string
  petName: string
  pets: number
  drops: number
}

type Iwt2HunterCollectionItem = {
  key: string
  glyph: string
  name: string
  chance: string
  quantity: number
  rarity: 'stat' | 'common' | 'uncommon' | 'rare' | 'epic' | 'legendary'
  source: string
}

type Iwt2GearNavEntry =
  | { kind: 'back', key: string, row: number, column: number }
  | { kind: 'class', key: string, row: number, column: number, classId: Iwt2PlayerClassId }
  | { kind: 'slot', key: string, row: number, column: number, slotId: Iwt2GearSlotId }
  | { kind: 'upgrade', key: string, row: number, column: number, disabled: boolean }
  | { kind: 'infusion', key: string, row: number, column: number, abilityId: Iwt2InfusionAbilityId, disabled: boolean }

const IWT2_HUNTER_PROFILE_DROP_COLUMNS = 6
const IWT2_NAME_MAX_LENGTH = 18
const IWT2_NAME_EDITOR_COLUMNS = 6
const IWT2_NAME_CHARACTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'.split('')

function formatIwt2SaveTimestamp(updatedAt: number | null) {
  if (!updatedAt) return 'No timestamp'
  return new Intl.DateTimeFormat(undefined, {
    dateStyle: 'medium',
    timeStyle: 'short',
  }).format(new Date(updatedAt))
}

function formatIwt2SaveSummary(save: Iwt2Save | null) {
  if (!save) return 'No save found'
  return `Level ${save.character.level} - ${formatIwt2SaveTimestamp(save.updatedAt)}`
}

function withBackAction(actions: Iwt2NavAction[], onBack: () => void): Iwt2NavAction[] {
  return [
    {
      key: 'back',
      label: 'Back',
      onConfirm: onBack,
    },
    ...actions,
  ]
}

function useIwt2ActionList(actions: Iwt2NavAction[]) {
  const [selectedIndex, setSelectedIndex] = useState(0)
  const activeIndex = clampToEnabled(actions, selectedIndex)

  useGameAction((action, device) => {
    if (device !== 'controller' || actions.length === 0) return
    if (action === 'navigateUp' || action === 'navigateLeft') {
      setSelectedIndex((current) => nextEnabledIndex(actions, current, -1))
    } else if (action === 'navigateDown' || action === 'navigateRight') {
      setSelectedIndex((current) => nextEnabledIndex(actions, current, 1))
    } else if (action === 'confirm') {
      if (!actions[activeIndex]?.disabled) actions[activeIndex]?.onConfirm()
    }
  })

  return { selectedIndex: activeIndex, setSelectedIndex }
}

const MODE_ACTIONS: Record<Iwt2Mode, Array<{
  bossId: Iwt2BossId
  label: string
  detail: string
  value: string
}>> = {
  Raids: [
    {
      bossId: 'bulldrome',
      label: 'Bristlemaw Raid Assignment',
      detail: 'Shared arena entry with raid-role notes: tank opens aggro, party spreads for charge lanes, then collapses after ground slam.',
      value: 'Start',
    },
    {
      bossId: 'yian-kut-ku',
      label: 'Emberquill Raid Assignment',
      detail: 'Shared arena entry with raid-role notes: assign ranged pressure, clear bird waves, and keep fire puddles away from party frames.',
      value: 'Start',
    },
    {
      bossId: 'great-jaggi',
      label: 'Packfang Alpha Raid Assignment',
      detail: 'Shared arena entry with raid-role notes: hold center, dodge pack lanes, and stabilize the paladin during howl windows.',
      value: 'Start',
    },
    {
      bossId: 'khezu',
      label: 'Palevolt Maw Raid Assignment',
      detail: 'Shared arena entry with raid-role notes: watch lightning markers, rotate through safe bands, and recover stunned allies fast.',
      value: 'Start',
    },
    {
      bossId: 'rathian',
      label: 'Verdant Wyrm Raid Assignment',
      detail: 'Shared arena entry with raid-role notes: dodge tail arcs, keep poison puddles out of the party path, and recover after sweeps.',
      value: 'Start',
    },
    {
      bossId: 'barroth',
      label: 'Mirehorn Raid Assignment',
      detail: 'Shared arena entry with raid-role notes: break mud armor, sidestep mud cones, and reposition through slow zones.',
      value: 'Start',
    },
    {
      bossId: 'tobi-kadachi',
      label: 'Stormtail Raid Assignment',
      detail: 'Shared arena entry with raid-role notes: track static charge, bait line pounces, and spread before chain shock.',
      value: 'Start',
    },
    {
      bossId: 'rimebastion',
      label: 'Rimebastion Raid Assignment',
      detail: 'Shared arena entry with raid-role notes: read ice wall lanes, dodge shard cones, and keep the tank away from corners.',
      value: 'Start',
    },
    {
      bossId: 'ember-mantis-duelist',
      label: 'Ember Mantis Duelist Raid Assignment',
      detail: 'Shared arena entry with raid-role notes: track sidesteps, cut through slash lanes, and spread before crossed blades.',
      value: 'Start',
    },
    {
      bossId: 'cinderback-ricochet',
      label: 'Cinderback Ricochet Raid Assignment',
      detail: 'Shared arena entry with raid-role notes: bait ricochet lanes, avoid lava trails, and recover after armor slam.',
      value: 'Start',
    },
    {
      bossId: 'obsidian-ram-golem',
      label: 'Obsidian Ram Golem Raid Assignment',
      detail: 'Shared arena entry with raid-role notes: bait charge lines, avoid quake fractures, and break armor safely.',
      value: 'Start',
    },
    {
      bossId: 'stormcoil-wyrm',
      label: 'Stormcoil Wyrm Raid Assignment',
      detail: 'Shared arena entry with raid-role notes: spread for chain lightning and rotate through charged ring gaps.',
      value: 'Start',
    },
    {
      bossId: 'venom-orchid-hydra',
      label: 'Venom Orchid Hydra Raid Assignment',
      detail: 'Shared arena entry with raid-role notes: dodge staggered poison cones and keep root lanes off the party.',
      value: 'Start',
    },
    {
      bossId: 'sandglass-scorpion',
      label: 'Sandglass Scorpion Raid Assignment',
      detail: 'Shared arena entry with raid-role notes: track burrows, wait out stinger eruptions, and avoid sinking sand.',
      value: 'Start',
    },
    {
      bossId: 'crystal-bat-matriarch',
      label: 'Crystal Bat Matriarch Raid Assignment',
      detail: 'Shared arena entry with raid-role notes: space for sonic rings, watch mirror lanes, and dodge swoops.',
      value: 'Start',
    },
    {
      bossId: 'hollowcrown-revenant',
      label: 'Hollowcrown Revenant Raid Assignment',
      detail: 'Shared arena entry with raid-role notes: avoid cursed hoof zones and sidestep ghost-antler charges.',
      value: 'Start',
    },
  ],
}

const CONTROLLER_ICON_OPTIONS: ControllerIconStyle[] = ['playstation', 'xbox', 'nintendo']
const IWT2_DUNGEON_BOSS_PAGE_SIZE = 5

const CONTROLLER_ICON_LABELS: Record<ControllerIconStyle, string> = {
  xbox: 'Xbox',
  playstation: 'PlayStation',
  nintendo: 'Nintendo',
}

const IWT2_DUNGEON_ACTIONS: Array<Omit<Iwt2NavAction, 'onConfirm'> & { bossId: Iwt2BossId }> = [
  {
    bossId: 'bulldrome',
    key: 'bulldrome',
    label: 'Bristlemaw Arena',
    detail: 'Level 1 field boss. Charge lanes, tank pressure, and periodic ground slam.',
    value: 'Ready',
  },
  {
    bossId: 'yian-kut-ku',
    key: 'yian-kut-ku',
    label: 'Emberquill Arena',
    detail: 'Bouncing fireballs, capped fire puddles, and bird waves at 90% and 40% health.',
    value: 'Ready',
  },
  {
    bossId: 'great-jaggi',
    key: 'great-jaggi',
    label: 'Packfang Alpha Arena',
    detail: 'Pack howl calls slanted add lanes that punish standing still or grouping badly.',
    value: 'Ready',
  },
  {
    bossId: 'khezu',
    key: 'khezu',
    label: 'Palevolt Maw Arena',
    detail: 'Thunder rings create safe bands while lightning circles lock onto weak allies.',
    value: 'Ready',
  },
  {
    bossId: 'rathian',
    key: 'rathian',
    label: 'Verdant Wyrm Arena',
    detail: 'Poison tail arcs and targeted poison puddles punish stacked positioning.',
    value: 'Ready',
  },
  {
    bossId: 'barroth',
    key: 'barroth',
    label: 'Mirehorn Arena',
    detail: 'Breakable mud armor softens incoming damage while mud spray creates slow zones.',
    value: 'Ready',
  },
  {
    bossId: 'tobi-kadachi',
    key: 'tobi-kadachi',
    label: 'Stormtail Arena',
    detail: 'Static builds into line pounces while chain shock punishes grouped allies.',
    value: 'Ready',
  },
  {
    bossId: 'rimebastion',
    key: 'rimebastion',
    label: 'Rimebastion Arena',
    detail: 'Ice walls split the room, then shard lanes and cones punish trapped routes.',
    value: 'Ready',
  },
  {
    bossId: 'ember-mantis-duelist',
    key: 'ember-mantis-duelist',
    label: 'Ember Mantis Duelist Arena',
    detail: 'Fast sidesteps lead into line slashes and crossed lane attacks.',
    value: 'Ready',
  },
  {
    bossId: 'cinderback-ricochet',
    key: 'cinderback-ricochet',
    label: 'Cinderback Ricochet Arena',
    detail: 'Wall-bounce rolls leave lava trails before a heavy armor slam.',
    value: 'Ready',
  },
  {
    bossId: 'obsidian-ram-golem',
    key: 'obsidian-ram-golem',
    label: 'Obsidian Ram Golem Arena',
    detail: 'Volcanic charge lines crack armor, then quake fractures split the floor.',
    value: 'Ready',
  },
  {
    bossId: 'stormcoil-wyrm',
    key: 'stormcoil-wyrm',
    label: 'Stormcoil Wyrm Arena',
    detail: 'Charged rings and chain lightning punish clustered party movement.',
    value: 'Ready',
  },
  {
    bossId: 'venom-orchid-hydra',
    key: 'venom-orchid-hydra',
    label: 'Venom Orchid Hydra Arena',
    detail: 'Three heads sweep poison cones while roots and pods pressure escape paths.',
    value: 'Ready',
  },
  {
    bossId: 'sandglass-scorpion',
    key: 'sandglass-scorpion',
    label: 'Sandglass Scorpion Arena',
    detail: 'Burrows, delayed stinger eruptions, and shifting sand zones control space.',
    value: 'Ready',
  },
  {
    bossId: 'crystal-bat-matriarch',
    key: 'crystal-bat-matriarch',
    label: 'Crystal Bat Matriarch Arena',
    detail: 'Sonic rings, mirror shard lanes, and swoop dives punish bad spacing.',
    value: 'Ready',
  },
  {
    bossId: 'hollowcrown-revenant',
    key: 'hollowcrown-revenant',
    label: 'Hollowcrown Revenant Arena',
    detail: 'Cursed hoof zones and ghost-antler dash trails cut across the arena.',
    value: 'Ready',
  },
]

export function Iwt2ScreenShell({
  children,
  eyebrow = 'I Want To Heal 2',
  onBack,
  title,
}: Iwt2ScreenShellProps) {
  useGameAction((action, device) => {
    if (device === 'controller' && action === 'back') onBack()
  })

  return (
    <section className="content-screen iwt2-screen-shell" data-game-nav-active="true">
      <div className="screen-heading">
        <div>
          <p className="eyebrow">{eyebrow}</p>
          <h1>{title}</h1>
        </div>
        <button className="back-button" data-controller-nav="skip" onClick={onBack} type="button">
          Back
        </button>
      </div>
      {children}
    </section>
  )
}

export function Iwt2ActionList({ actions }: Iwt2ActionListProps) {
  const { selectedIndex, setSelectedIndex } = useIwt2ActionList(actions)
  const rowRefs = useRef<Array<HTMLButtonElement | null>>([])

  useEffect(() => {
    rowRefs.current[selectedIndex]?.scrollIntoView({
      block: 'nearest',
      inline: 'nearest',
    })
  }, [selectedIndex])

  return (
    <div className="iwt2-action-list">
      {actions.map((action, index) => (
        <button
          className={`iwt2-action-row ${selectedIndex === index ? 'game-selected selected' : ''}`}
          data-controller-nav="skip"
          data-game-selected={selectedIndex === index ? 'true' : undefined}
          disabled={action.disabled}
          key={action.key}
          onClick={action.onConfirm}
          onPointerDown={() => {
            if (!action.disabled) setSelectedIndex(index)
          }}
          ref={(element) => {
            rowRefs.current[index] = element
          }}
          type="button"
        >
          <span>
            <strong>{action.label}</strong>
            {action.detail && <small>{action.detail}</small>}
          </span>
          {action.value && <em>{action.value}</em>}
        </button>
      ))}
    </div>
  )
}

function Iwt2InfoList({ rows }: { rows: Iwt2InfoRow[] }) {
  return (
    <div className="iwt2-info-list">
      {rows.map((row) => (
        <div className="iwt2-info-row" key={row.key}>
          <span>
            <strong>{row.label}</strong>
            <small>{row.detail}</small>
          </span>
          <em>{row.value}</em>
        </div>
      ))}
    </div>
  )
}

function Iwt2DifficultyStrip({
  difficulties,
  onSelect,
  selected,
}: {
  difficulties: Iwt2DifficultyOption[]
  onSelect: (difficulty: Iwt2Difficulty) => void
  selected: (key: string) => boolean
}) {
  return (
    <div className="iwt2-difficulty-strip" aria-label="Difficulty options">
      {difficulties.map((difficulty) => (
        <button
          className={`${difficulty.selected ? 'active' : ''} ${selected(`difficulty-${difficulty.slug}`) ? 'game-selected' : ''}`}
          data-controller-nav="skip"
          data-game-selected={selected(`difficulty-${difficulty.slug}`) ? 'true' : undefined}
          disabled={difficulty.locked}
          key={difficulty.slug}
          onClick={() => {
            if (!difficulty.locked) onSelect(difficulty)
          }}
          type="button"
        >
          <strong>{difficulty.name}</strong>
          <small>{difficulty.locked ? `Lv ${difficulty.unlockLevel}` : `${difficulty.healthMultiplier.toFixed(2)}x HP`}</small>
        </button>
      ))}
    </div>
  )
}

export function Iwt2DungeonsScreen({
  difficultySlug,
  onBack,
  onDifficultyChange,
  onOpenBoss,
  onPreviewBoss,
  save,
}: {
  difficultySlug: string
  onBack: () => void
  onDifficultyChange: (difficultySlug: string) => void
  onOpenBoss: (bossId: Iwt2BossId, difficulty: Iwt2Difficulty) => void
  onPreviewBoss: (bossId: Iwt2BossId) => void
  save: Iwt2Save
}) {
  const [page, setPage] = useState(0)
  const [selectedKey, setSelectedKey] = useState('bulldrome')
  const selectedDifficulty = findIwt2Difficulty(IWT2_DUNGEON_DIFFICULTIES, difficultySlug)
  const difficultyLocked = !isIwt2DifficultyUnlocked(selectedDifficulty, save.character.level)
  const pageCount = Math.max(1, Math.ceil(IWT2_DUNGEON_ACTIONS.length / IWT2_DUNGEON_BOSS_PAGE_SIZE))
  const currentPage = Math.min(page, pageCount - 1)
  const pageStart = currentPage * IWT2_DUNGEON_BOSS_PAGE_SIZE
  const pageEnd = Math.min(IWT2_DUNGEON_ACTIONS.length, pageStart + IWT2_DUNGEON_BOSS_PAGE_SIZE)
  const visibleBosses = useMemo(
    () => IWT2_DUNGEON_ACTIONS.slice(pageStart, pageEnd),
    [pageEnd, pageStart],
  )
  const difficultyOptions = useMemo<Iwt2DifficultyOption[]>(() => (
    IWT2_DUNGEON_DIFFICULTIES.map((difficulty) => ({
      ...difficulty,
      locked: !isIwt2DifficultyUnlocked(difficulty, save.character.level),
      selected: difficulty.slug === selectedDifficulty.slug,
    }))
  ), [save.character.level, selectedDifficulty.slug])
  const navEntries = useMemo<Iwt2NavAction[]>(() => [
    {
      key: 'dungeons-page-prev',
      label: 'Previous Page',
      disabled: currentPage === 0,
      onConfirm: () => setPage((activePage) => Math.max(0, activePage - 1)),
    },
    {
      key: 'dungeons-page-next',
      label: 'Next Page',
      disabled: currentPage >= pageCount - 1,
      onConfirm: () => setPage((activePage) => Math.min(pageCount - 1, activePage + 1)),
    },
    {
      key: 'back',
      label: 'Back',
      onConfirm: onBack,
    },
    ...difficultyOptions.map((difficulty) => ({
      key: `difficulty-${difficulty.slug}`,
      label: difficulty.name,
      detail: difficulty.description,
      value: difficulty.locked ? `Lv ${difficulty.unlockLevel}` : `${difficulty.healthMultiplier.toFixed(2)}x HP`,
      disabled: difficulty.locked,
      onConfirm: () => onDifficultyChange(difficulty.slug),
    })),
    ...visibleBosses.map((entry) => {
      const reward = iwt2BossCoinRewardFor(entry.bossId, selectedDifficulty.slug)
      return {
        ...entry,
        detail: `${entry.detail} ${selectedDifficulty.name}: ${selectedDifficulty.healthMultiplier.toFixed(2)}x HP, ${selectedDifficulty.damageMultiplier.toFixed(2)}x damage. Reward: ${reward.name}.`,
        disabled: difficultyLocked,
        value: difficultyLocked ? `Lv ${selectedDifficulty.unlockLevel}` : selectedDifficulty.name,
        onConfirm: () => onOpenBoss(entry.bossId, selectedDifficulty),
      }
    }),
  ], [
    currentPage,
    difficultyLocked,
    difficultyOptions,
    onBack,
    onDifficultyChange,
    onOpenBoss,
    pageCount,
    selectedDifficulty,
    visibleBosses,
  ])
  const activeEntry = navEntries.find((entry) => entry.key === selectedKey && !entry.disabled)
    ?? navEntries.find((entry) => entry.key === visibleBosses[0]?.key)
    ?? navEntries.find((entry) => !entry.disabled)

  function selected(key: string) {
    return activeEntry?.key === key
  }

  function openDungeonNavEntry(entry: Iwt2NavAction | undefined) {
    if (!entry || entry.disabled) return
    entry.onConfirm()
  }

  function moveDungeonSelection(action: InputAction) {
    if (!action.startsWith('navigate')) return
    const activeIndex = Math.max(0, navEntries.findIndex((entry) => entry.key === activeEntry?.key))
    const direction = action === 'navigateUp' || action === 'navigateLeft' ? -1 : 1
    const nextIndex = nextEnabledIndex(navEntries, activeIndex, direction)
    const nextKey = navEntries[nextIndex]?.key ?? activeEntry?.key ?? 'back'
    setSelectedKey(nextKey)
    const nextBoss = visibleBosses.find((boss) => boss.key === nextKey)
    if (nextBoss) onPreviewBoss(nextBoss.bossId)
  }

  useGameAction((action, device) => {
    if (device !== 'controller') return
    if (action === 'back') {
      onBack()
      return
    }
    if (action === 'confirm') {
      openDungeonNavEntry(activeEntry)
      return
    }
    moveDungeonSelection(action)
  })

  return (
    <section className="content-screen iwt2-screen-shell" data-game-nav-active="true">
      <div className="screen-heading">
        <div>
          <p className="eyebrow">I Want To Heal 2</p>
          <h1>Dungeons</h1>
        </div>
        <div className="iwt2-heading-actions">
          <button
            className={`iwt2-pager-button ${selected('dungeons-page-prev') ? 'game-selected' : ''}`}
            data-controller-nav="skip"
            data-game-selected={selected('dungeons-page-prev') ? 'true' : undefined}
            disabled={currentPage === 0}
            onClick={() => setPage((activePage) => Math.max(0, activePage - 1))}
            onPointerDown={() => setSelectedKey('dungeons-page-prev')}
            type="button"
          >
            Prev
          </button>
          <span className="iwt2-page-counter">{currentPage + 1} / {pageCount}</span>
          <button
            className={`iwt2-pager-button ${selected('dungeons-page-next') ? 'game-selected' : ''}`}
            data-controller-nav="skip"
            data-game-selected={selected('dungeons-page-next') ? 'true' : undefined}
            disabled={currentPage >= pageCount - 1}
            onClick={() => setPage((activePage) => Math.min(pageCount - 1, activePage + 1))}
            onPointerDown={() => setSelectedKey('dungeons-page-next')}
            type="button"
          >
            Next
          </button>
          <button
            className={`back-button ${selected('back') ? 'game-selected' : ''}`}
            data-controller-nav="skip"
            data-game-selected={selected('back') ? 'true' : undefined}
            onClick={onBack}
            onPointerDown={() => setSelectedKey('back')}
            type="button"
          >
            Back
          </button>
        </div>
      </div>
      <Iwt2DifficultyStrip
        difficulties={difficultyOptions}
        onSelect={(difficulty) => {
          setSelectedKey(`difficulty-${difficulty.slug}`)
          onDifficultyChange(difficulty.slug)
        }}
        selected={selected}
      />
      <div className="iwt2-dungeon-list">
        <div className="iwt2-action-list">
          {visibleBosses.map((boss) => (
            <button
              className={`iwt2-action-row ${selected(boss.key) ? 'game-selected selected' : ''}`}
              data-controller-nav="skip"
              data-game-selected={selected(boss.key) ? 'true' : undefined}
              disabled={difficultyLocked}
              key={boss.key}
              onClick={() => onOpenBoss(boss.bossId, selectedDifficulty)}
              onPointerDown={() => {
                setSelectedKey(boss.key)
                onPreviewBoss(boss.bossId)
              }}
              type="button"
            >
              <span>
                <strong>{boss.label}</strong>
                {boss.detail && (
                  <small>
                    {boss.detail} {selectedDifficulty.name}: {selectedDifficulty.healthMultiplier.toFixed(2)}x HP, {selectedDifficulty.damageMultiplier.toFixed(2)}x damage. Reward: {iwt2BossCoinRewardFor(boss.bossId, selectedDifficulty.slug).name}.
                  </small>
                )}
              </span>
              <em>{difficultyLocked ? `Lv ${selectedDifficulty.unlockLevel}` : selectedDifficulty.name}</em>
            </button>
          ))}
        </div>
      </div>
    </section>
  )
}

export function Iwt2ModeScreen({
  difficultySlug,
  mode,
  onBack,
  onDifficultyChange,
  onOpenBoss,
  onPreviewBoss,
  save,
}: {
  difficultySlug: string
  mode: Iwt2Mode
  onBack: () => void
  onDifficultyChange: (difficultySlug: string) => void
  onOpenBoss: (bossId: Iwt2BossId, difficulty: Iwt2Difficulty) => void
  onPreviewBoss: (bossId: Iwt2BossId) => void
  save: Iwt2Save
}) {
  const [selectedKey, setSelectedKey] = useState('back')
  const selectedDifficulty = findIwt2Difficulty(IWT2_RAID_DIFFICULTIES, difficultySlug)
  const difficultyLocked = !isIwt2DifficultyUnlocked(selectedDifficulty, save.character.level)
  const difficultyOptions = useMemo<Iwt2DifficultyOption[]>(() => (
    IWT2_RAID_DIFFICULTIES.map((difficulty) => ({
      ...difficulty,
      locked: !isIwt2DifficultyUnlocked(difficulty, save.character.level),
      selected: difficulty.slug === selectedDifficulty.slug,
    }))
  ), [save.character.level, selectedDifficulty.slug])
  const actions = useMemo<Iwt2NavAction[]>(
    () => withBackAction([
      ...difficultyOptions.map((difficulty) => ({
        key: `difficulty-${difficulty.slug}`,
        label: difficulty.name,
        detail: difficulty.description,
        value: difficulty.locked ? `Lv ${difficulty.unlockLevel}` : `${difficulty.healthMultiplier.toFixed(2)}x HP`,
        disabled: difficulty.locked,
        onConfirm: () => onDifficultyChange(difficulty.slug),
      })),
      ...MODE_ACTIONS[mode].map((entry) => {
        const reward = iwt2BossCoinRewardFor(entry.bossId, selectedDifficulty.slug)
        return {
          key: `${mode}-${entry.bossId}`,
          label: entry.label,
          detail: `${entry.detail} ${selectedDifficulty.name}: ${selectedDifficulty.healthMultiplier.toFixed(2)}x HP, ${selectedDifficulty.damageMultiplier.toFixed(2)}x damage. Reward: ${reward.name}.`,
          value: difficultyLocked ? `Lv ${selectedDifficulty.unlockLevel}` : selectedDifficulty.name,
          disabled: difficultyLocked,
          onConfirm: () => onOpenBoss(entry.bossId, selectedDifficulty),
        }
      }),
    ], onBack),
    [difficultyLocked, difficultyOptions, mode, onBack, onDifficultyChange, onOpenBoss, selectedDifficulty],
  )
  const activeEntry = actions.find((entry) => entry.key === selectedKey && !entry.disabled)
    ?? actions.find((entry) => !entry.disabled)
  const selected = (key: string) => activeEntry?.key === key

  useGameAction((action, device) => {
    if (device !== 'controller') return
    if (action === 'back') {
      onBack()
      return
    }
    if (action === 'confirm') {
      activeEntry?.onConfirm()
      return
    }
    if (!action.startsWith('navigate')) return
    const activeIndex = Math.max(0, actions.findIndex((entry) => entry.key === activeEntry?.key))
    const direction = action === 'navigateUp' || action === 'navigateLeft' ? -1 : 1
    const nextIndex = nextEnabledIndex(actions, activeIndex, direction)
    const nextKey = actions[nextIndex]?.key ?? activeEntry?.key ?? 'back'
    setSelectedKey(nextKey)
    const nextBoss = MODE_ACTIONS[mode].find((entry) => `${mode}-${entry.bossId}` === nextKey)
    if (nextBoss) onPreviewBoss(nextBoss.bossId)
  })

  return (
    <Iwt2ScreenShell title={mode} onBack={onBack}>
      <Iwt2DifficultyStrip
        difficulties={difficultyOptions}
        onSelect={(difficulty) => {
          setSelectedKey(`difficulty-${difficulty.slug}`)
          onDifficultyChange(difficulty.slug)
        }}
        selected={selected}
      />
      <div className="iwt2-action-list">
        {actions.filter((action) => !action.key.startsWith('difficulty-')).map((action) => (
          <button
            className={`iwt2-action-row ${selected(action.key) ? 'game-selected selected' : ''}`}
            data-controller-nav="skip"
            data-game-selected={selected(action.key) ? 'true' : undefined}
            disabled={action.disabled}
            key={action.key}
            onClick={action.onConfirm}
            onPointerDown={() => {
              if (action.disabled) return
              setSelectedKey(action.key)
              const boss = MODE_ACTIONS[mode].find((entry) => `${mode}-${entry.bossId}` === action.key)
              if (boss) onPreviewBoss(boss.bossId)
            }}
            type="button"
          >
            <span>
              <strong>{action.label}</strong>
              {action.detail && <small>{action.detail}</small>}
            </span>
            {action.value && <em>{action.value}</em>}
          </button>
        ))}
      </div>
    </Iwt2ScreenShell>
  )
}

export function Iwt2RoguelikeScreen({
  contentType,
  onBack,
  onCancelQueue,
  onContentTypeChange,
  onStart,
  onVariantChange,
  queueing = false,
  queueMessage,
  variant,
}: {
  contentType: Iwt2RoguelikeContentType
  onBack: () => void
  onCancelQueue: () => void
  onContentTypeChange: (contentType: Iwt2RoguelikeContentType) => void
  onStart: () => void
  onVariantChange: (variant: Iwt2RoguelikeVariant) => void
  queueing?: boolean
  queueMessage?: string
  variant: Iwt2RoguelikeVariant
}) {
  const gameMode = getGameMode()
  const [selectedIndex, setSelectedIndex] = useState(0)
  const actions = useMemo<Iwt2NavAction[]>(() => {
    const base = [
      {
        key: 'back',
        label: 'Back',
        onConfirm: onBack,
      },
      {
        key: 'variant-pve',
        label: 'PvE',
        onConfirm: () => onVariantChange('pve'),
      },
      {
        key: 'variant-pvp',
        label: 'PvP',
        onConfirm: () => onVariantChange('pvp'),
      },
    ] satisfies Iwt2NavAction[]
    if (variant === 'pve') {
      base.push(
        {
          key: 'pve-dungeon',
          label: 'Dungeon',
          onConfirm: () => onContentTypeChange('dungeon'),
        },
        {
          key: 'pve-raid',
          label: 'Raid',
          onConfirm: () => onContentTypeChange('raid'),
        },
        {
          key: 'pve-start',
          label: 'Start Run',
          onConfirm: onStart,
        },
      )
      return base
    }
    base.push(
      {
        key: 'pvp-dungeon',
        label: 'Dungeon',
        onConfirm: () => onContentTypeChange('dungeon'),
      },
      {
        key: 'pvp-raid',
        label: 'Raid',
        onConfirm: () => onContentTypeChange('raid'),
      },
      {
        key: 'pvp-stadium',
        label: 'Stadium',
        onConfirm: () => onContentTypeChange('stadium'),
      },
      {
        key: 'pvp-start',
        label: 'Start Match',
        onConfirm: onStart,
      },
    )
    return base
  }, [onBack, onContentTypeChange, onStart, onVariantChange, variant])
  const activeIndex = Math.min(selectedIndex, actions.length - 1)
  const selected = (key: string) => actions[activeIndex]?.key === key
  const open = (key: string) => actions.find((action) => action.key === key)?.onConfirm()
  const select = (key: string) => {
    const nextIndex = actions.findIndex((action) => action.key === key)
    if (nextIndex >= 0) setSelectedIndex(nextIndex)
  }
  const pveCardTitle = contentType === 'raid' ? 'Raid Roguelike' : 'Dungeon Roguelike'
  const pveCardCopy = contentType === 'raid'
    ? 'Ten-player party. Raid pools, lighter early scaling, and the same upgrade draft.'
    : 'Five-player party. Two random trash enemies and a boss with a lighter early ramp.'
  const pvpCardTitle = gameMode === 'offline' ? 'Offline CPU Match' : 'Queue Then CPU Fallback'
  const pvpCardCopy = contentType === 'stadium'
    ? 'Best-of-5 survival with dampening, equalized gear, and after-round buff buying.'
    : gameMode === 'offline'
      ? 'Offline mode always places you against a random CPU 1-5.'
      : 'Online mode searches briefly. If nobody is queued, a random CPU 1-5 takes the slot.'

  useGameAction((action, device) => {
    if (device !== 'controller') return
    if (queueing) {
      if (action === 'back' || action === 'confirm') onCancelQueue()
      return
    }
    if (action === 'back') onBack()
    else if (action.startsWith('navigate')) {
      setSelectedIndex((current) => moveSpatialSelection(actions, current, action))
    } else if (action === 'confirm') {
      actions[activeIndex]?.onConfirm()
    }
  })

  return (
    <section className="content-screen roguelike-screen" data-game-nav-active="true">
      <div className="screen-heading">
        <div>
          <p className="eyebrow">Endless Draft</p>
          <h1>Roguelike</h1>
        </div>
        <button
          className={`back-button ${selected('back') ? 'game-selected' : ''}`}
          data-controller-nav="skip"
          data-game-selected={selected('back') ? 'true' : undefined}
          onClick={onBack}
          onPointerDown={() => select('back')}
          type="button"
        >
          Back
        </button>
      </div>
      <div className="roguelike-variant-row">
        <button
          className={`text-button ${variant === 'pve' ? 'active' : ''} ${selected('variant-pve') ? 'game-selected' : ''}`}
          data-controller-nav="skip"
          data-game-selected={selected('variant-pve') ? 'true' : undefined}
          onClick={() => open('variant-pve')}
          onPointerDown={() => select('variant-pve')}
          type="button"
        >
          PvE
        </button>
        <button
          className={`text-button ${variant === 'pvp' ? 'active' : ''} ${selected('variant-pvp') ? 'game-selected' : ''}`}
          data-controller-nav="skip"
          data-game-selected={selected('variant-pvp') ? 'true' : undefined}
          onClick={() => open('variant-pvp')}
          onPointerDown={() => select('variant-pvp')}
          type="button"
        >
          PvP
        </button>
      </div>
      {variant === 'pve' && (
        <>
          <div className="roguelike-option-panel">
            <div>
              <p className="eyebrow">Run Type</p>
              <h2>PvE Roguelike</h2>
            </div>
            <div className="roguelike-timing-row">
              <button
                className={`text-button ${contentType === 'dungeon' ? 'active' : ''} ${selected('pve-dungeon') ? 'game-selected' : ''}`}
                data-controller-nav="skip"
                data-game-selected={selected('pve-dungeon') ? 'true' : undefined}
                onClick={() => open('pve-dungeon')}
                onPointerDown={() => select('pve-dungeon')}
                type="button"
              >
                Dungeon
              </button>
              <button
                className={`text-button ${contentType === 'raid' ? 'active' : ''} ${selected('pve-raid') ? 'game-selected' : ''}`}
                data-controller-nav="skip"
                data-game-selected={selected('pve-raid') ? 'true' : undefined}
                onClick={() => open('pve-raid')}
                onPointerDown={() => select('pve-raid')}
                type="button"
              >
                Raid
              </button>
            </div>
          </div>
          <div className="menu-card pvp-queue-panel">
            <span>{contentType === 'raid' ? 'R' : 'D'}</span>
            <div>
              <strong>{pveCardTitle}</strong>
              <small>{pveCardCopy}</small>
            </div>
            <button
              className={`text-button ${selected('pve-start') ? 'game-selected' : ''}`}
              data-controller-nav="skip"
              data-game-selected={selected('pve-start') ? 'true' : undefined}
              onClick={() => open('pve-start')}
              onPointerDown={() => select('pve-start')}
              type="button"
            >
              Start Run
            </button>
          </div>
        </>
      )}
      {variant === 'pvp' && (
        <>
          <div className="roguelike-option-panel">
            <div>
              <p className="eyebrow">Match Type</p>
              <h2>PvP Roguelike</h2>
            </div>
            <div className="roguelike-timing-row">
              <button
                className={`text-button ${contentType === 'dungeon' ? 'active' : ''} ${selected('pvp-dungeon') ? 'game-selected' : ''}`}
                data-controller-nav="skip"
                data-game-selected={selected('pvp-dungeon') ? 'true' : undefined}
                onClick={() => open('pvp-dungeon')}
                onPointerDown={() => select('pvp-dungeon')}
                type="button"
              >
                Dungeon
              </button>
              <button
                className={`text-button ${contentType === 'raid' ? 'active' : ''} ${selected('pvp-raid') ? 'game-selected' : ''}`}
                data-controller-nav="skip"
                data-game-selected={selected('pvp-raid') ? 'true' : undefined}
                onClick={() => open('pvp-raid')}
                onPointerDown={() => select('pvp-raid')}
                type="button"
              >
                Raid
              </button>
              <button
                className={`text-button ${contentType === 'stadium' ? 'active' : ''} ${selected('pvp-stadium') ? 'game-selected' : ''}`}
                data-controller-nav="skip"
                data-game-selected={selected('pvp-stadium') ? 'true' : undefined}
                onClick={() => open('pvp-stadium')}
                onPointerDown={() => select('pvp-stadium')}
                type="button"
              >
                Stadium
              </button>
            </div>
          </div>
          <div className="menu-card pvp-queue-panel">
            <span>{gameMode === 'offline' ? 'C' : 'Q'}</span>
            <div>
              <strong>{pvpCardTitle}</strong>
              <small>{queueMessage || pvpCardCopy}</small>
            </div>
            <button
              className={`text-button ${selected('pvp-start') ? 'game-selected' : ''}`}
              data-controller-nav="skip"
              data-game-selected={selected('pvp-start') ? 'true' : undefined}
              onClick={() => open('pvp-start')}
              onPointerDown={() => select('pvp-start')}
              type="button"
            >
              Start Match
            </button>
          </div>
        </>
      )}
      {queueing && (
        <div className="iwt2-pvp-queue-overlay" data-game-nav-active="true" role="dialog" aria-modal="true">
          <div className="iwt2-pvp-queue-panel">
            <div className="placeholder-runes">P V P</div>
            <p className="eyebrow">Matchmaking</p>
            <h2>Queuing for PvP</h2>
            <small>{queueMessage || 'Searching for an opponent...'}</small>
            <button
              className="iwt2-result-button is-secondary game-selected"
              data-controller-nav="skip"
              data-game-selected="true"
              onClick={onCancelQueue}
              type="button"
            >
              Cancel
            </button>
          </div>
        </div>
      )}
    </section>
  )
}

export function Iwt2RoguelikeUpgradeScreen({
  activeBuffSummary,
  activeDebuffSummary,
  contentType,
  debuffChoices,
  onBack,
  onChoose,
  selfChoices,
  stage,
  variant,
}: {
  activeBuffSummary?: string
  activeDebuffSummary?: string
  contentType: Iwt2RoguelikeContentType
  debuffChoices: Array<Iwt2RoguelikeChoice<Iwt2RoguelikeOpponentDebuffId>>
  onBack: () => void
  onChoose: (buffId: Iwt2RoguelikeSelfBuffId, debuffId?: Iwt2RoguelikeOpponentDebuffId) => void
  selfChoices: Array<Iwt2RoguelikeChoice<Iwt2RoguelikeSelfBuffId>>
  stage: number
  variant: Iwt2RoguelikeVariant
}) {
  const [selectedBuffId, setSelectedBuffId] = useState<Iwt2RoguelikeSelfBuffId | undefined>()
  const [selectedDebuffId, setSelectedDebuffId] = useState<Iwt2RoguelikeOpponentDebuffId | undefined>()
  const [selectedIndex, setSelectedIndex] = useState(0)
  const activeBuffId = selfChoices.some((choice) => choice.id === selectedBuffId) ? selectedBuffId : undefined
  const activeDebuffId = debuffChoices.some((choice) => choice.id === selectedDebuffId) ? selectedDebuffId : undefined

  const entries = useMemo<Iwt2UpgradeNavEntry[]>(() => {
    if (variant !== 'pvp') {
      return [
        ...selfChoices.map((_, index) => ({
          kind: 'upgradeBuff' as const,
          index,
          row: index,
          column: 0,
        })),
        {
          kind: 'upgradeLeave' as const,
          row: selfChoices.length,
          column: 0,
        },
      ]
    }
    const nextEntries: Iwt2UpgradeNavEntry[] = [
      ...selfChoices.map((_, index) => ({
        kind: 'upgradeBuff' as const,
        index,
        row: index,
        column: 0,
      })),
      ...debuffChoices.map((_, index) => ({
        kind: 'upgradeDebuff' as const,
        index,
        row: index,
        column: 1,
      })),
      {
        kind: 'upgradeContinue' as const,
        row: Math.max(selfChoices.length, debuffChoices.length),
        column: 1,
        disabled: !activeBuffId || !activeDebuffId,
      },
    ]
    return nextEntries
  }, [activeBuffId, activeDebuffId, debuffChoices, selfChoices, variant])

  const activeEntry = activeUpgradeEntry(entries, selectedIndex)
  const pvpUpgrade = variant === 'pvp'
  const buffHeader = pvpUpgrade ? 'Self Buff' : 'Run Buff'
  const headerEyebrow = pvpUpgrade && contentType === 'stadium' ? 'Stadium Buy Round' : 'Choose Edge'
  const title = pvpUpgrade
    ? contentType === 'stadium' ? `Round ${stage} Complete` : `Stage ${stage} Boss Cleared`
    : `Roguelike Stage ${stage} Complete`
  const continueLabel = contentType === 'stadium' ? 'Finished Buying' : 'Continue'

  function entrySelected(kind: Iwt2UpgradeNavEntry['kind'], index?: number) {
    if (!activeEntry || activeEntry.kind !== kind) return false
    if ('index' in activeEntry || index !== undefined) return 'index' in activeEntry && activeEntry.index === index
    return true
  }

  function setCursor(kind: Iwt2UpgradeNavEntry['kind'], index?: number) {
    const nextIndex = entries.findIndex((entry) => {
      if (entry.kind !== kind) return false
      if ('index' in entry || index !== undefined) return 'index' in entry && entry.index === index
      return true
    })
    if (nextIndex >= 0) setSelectedIndex(nextIndex)
  }

  function openEntry(entry: Iwt2UpgradeNavEntry | undefined) {
    if (!entry || upgradeEntryDisabled(entry)) return
    if (entry.kind === 'upgradeBuff') {
      const choice = selfChoices[entry.index]
      if (!choice) return
      if (pvpUpgrade) setSelectedBuffId(choice.id)
      else onChoose(choice.id)
    } else if (entry.kind === 'upgradeDebuff') {
      const choice = debuffChoices[entry.index]
      if (choice) setSelectedDebuffId(choice.id)
    } else if (entry.kind === 'upgradeContinue') {
      if (activeBuffId && activeDebuffId) onChoose(activeBuffId, activeDebuffId)
    } else if (entry.kind === 'upgradeLeave') {
      onBack()
    }
  }

  useGameAction((action, device) => {
    if (device !== 'controller') return
    if (action === 'confirm') {
      openEntry(activeEntry)
      return
    }
    if (action.startsWith('navigate')) {
      setSelectedIndex((current) => moveUpgradeSelection(entries, current, action))
      return
    }
    if (action === 'back') onBack()
  })

  return (
    <div className="result-screen" data-game-nav-active="true">
      <div className={`pvp-upgrade-dialog ${pvpUpgrade ? '' : 'pve-upgrade-dialog'}`}>
        {pvpUpgrade ? (
          <div className="pvp-upgrade-header">
            <div>
              <p className="eyebrow">{headerEyebrow}</p>
              <h2>{title}</h2>
            </div>
          </div>
        ) : (
          <>
            <p className="eyebrow">{title}</p>
            <h2>Choose Upgrade</h2>
            <p>Pick one upgrade before the next fight.</p>
          </>
        )}
        <div className="pvp-choice-columns">
          <div>
            <strong>{buffHeader}</strong>
            <div className="upgrade-choice-grid">
              {selfChoices.map((choice, index) => (
                <button
                  className={`${activeBuffId === choice.id && pvpUpgrade ? 'selected-upgrade' : ''} ${entrySelected('upgradeBuff', index) ? 'game-selected' : ''}`}
                  data-controller-nav="skip"
                  data-game-selected={entrySelected('upgradeBuff', index) ? 'true' : undefined}
                  key={choice.id}
                  onClick={() => {
                    if (pvpUpgrade) setSelectedBuffId(choice.id)
                    else onChoose(choice.id)
                  }}
                  onPointerDown={() => setCursor('upgradeBuff', index)}
                  type="button"
                >
                  <strong>{choice.name}</strong>
                  <small>{choice.description}</small>
                </button>
              ))}
            </div>
          </div>
          {pvpUpgrade && (
            <div>
              <strong>Opponent Debuff</strong>
              <div className="upgrade-choice-grid">
                {debuffChoices.map((choice, index) => (
                  <button
                    className={`${activeDebuffId === choice.id ? 'selected-upgrade' : ''} ${entrySelected('upgradeDebuff', index) ? 'game-selected' : ''}`}
                    data-controller-nav="skip"
                    data-game-selected={entrySelected('upgradeDebuff', index) ? 'true' : undefined}
                    key={choice.id}
                    onClick={() => setSelectedDebuffId(choice.id)}
                    onPointerDown={() => setCursor('upgradeDebuff', index)}
                    type="button"
                  >
                    <strong>{choice.name}</strong>
                    <small>{choice.description}</small>
                  </button>
                ))}
              </div>
            </div>
          )}
        </div>
        {!pvpUpgrade && activeBuffSummary && (
          <p className="roguelike-upgrade-list">
            Active: {activeBuffSummary}
          </p>
        )}
        {pvpUpgrade && (activeBuffSummary || activeDebuffSummary) && (
          <p className="roguelike-upgrade-list">
            Buffs: {activeBuffSummary || 'None'} | Debuffs: {activeDebuffSummary || 'None'}
          </p>
        )}
        {pvpUpgrade ? (
          <button
            className={`secondary-result-button ${entrySelected('upgradeContinue') ? 'game-selected' : ''}`}
            data-controller-nav="skip"
            data-game-selected={entrySelected('upgradeContinue') ? 'true' : undefined}
            disabled={!activeBuffId || !activeDebuffId}
            onClick={() => {
              if (activeBuffId && activeDebuffId) onChoose(activeBuffId, activeDebuffId)
            }}
            onPointerDown={() => setCursor('upgradeContinue')}
            type="button"
          >
            {continueLabel}
          </button>
        ) : (
          <button
            className={`secondary-result-button ${entrySelected('upgradeLeave') ? 'game-selected' : ''}`}
            data-controller-nav="skip"
            data-game-selected={entrySelected('upgradeLeave') ? 'true' : undefined}
            onClick={onBack}
            onPointerDown={() => setCursor('upgradeLeave')}
            type="button"
          >
            Leave Roguelike
          </button>
        )}
      </div>
    </div>
  )
}

export function Iwt2HunterProfileScreen({
  onBack,
  save,
}: {
  onBack: () => void
  save: Iwt2Save
}) {
  const [activeTab, setActiveTab] = useState<Iwt2HunterProfileTab>('stats')
  const [selectedIndex, setSelectedIndex] = useState(1)
  const [focusedItemKey, setFocusedItemKey] = useState<string | null>(null)
  const screenRef = useRef<HTMLElement | null>(null)
  const { enabled: dualScreenEnabled } = useDualScreen()
  const bosses = useMemo<Iwt2HunterProfileBossEntry[]>(() => (
    (Object.keys(IWT2_BOSS_METADATA) as Iwt2BossId[]).map((bossId) => {
      const coins = iwt2BossCoinRewardsFor(bossId).map((coin) => ({
        ...coin,
        quantity: save.collectionLog.dropsFound[coin.id] ?? 0,
      }))
      const pet = iwt2BossPetRewardFor(bossId)
      return {
        bossId,
        coins,
        drops: coins.reduce((total, coin) => total + coin.quantity, 0),
        encounter: IWT2_BOSS_METADATA[bossId],
        kills: save.collectionLog.bossKills[bossId] ?? 0,
        petId: pet.id,
        petName: pet.name,
        pets: save.collectionLog.bossPets[pet.id] ?? 0,
      }
    })
  ), [save.collectionLog.bossKills, save.collectionLog.bossPets, save.collectionLog.dropsFound])
  const [selectedBossId, setSelectedBossId] = useState<Iwt2BossId>(() => bosses[0]?.bossId ?? 'bulldrome')
  const selectedBoss = bosses.find((boss) => boss.bossId === selectedBossId) ?? bosses[0]
  const totalBossKills = bosses.reduce((total, boss) => total + boss.kills, 0)
  const totalDrops = bosses.reduce((total, boss) => total + boss.drops, 0)
  const totalPets = bosses.reduce((total, boss) => total + boss.pets, 0)
  const mostKilledBoss = bosses.reduce<Iwt2HunterProfileBossEntry | null>((best, boss) => {
    if (!best) return boss
    return boss.kills > best.kills ? boss : best
  }, null)
  const collectionItems = useMemo<Iwt2HunterCollectionItem[]>(() => {
    if (!selectedBoss) return []
    return [
      {
        chance: 'Defeats',
        glyph: 'K',
        key: `kills:${selectedBoss.bossId}`,
        name: 'Boss Kills',
        quantity: selectedBoss.kills,
        rarity: 'stat',
        source: selectedBoss.encounter.name,
      },
      ...selectedBoss.coins.map((coin) => ({
        chance: `Guaranteed 1-3 | iLvl ${coin.itemLevel}`,
        glyph: coin.glyph,
        key: `drop:${coin.id}`,
        name: coin.name,
        quantity: coin.quantity,
        rarity: coin.rarity,
        source: selectedBoss.encounter.name,
      })),
      {
        chance: '1 in 500',
        glyph: '*',
        key: `pet:${selectedBoss.petId}`,
        name: selectedBoss.petName,
        quantity: selectedBoss.pets,
        rarity: 'legendary',
        source: selectedBoss.encounter.name,
      },
    ]
  }, [selectedBoss])
  const statTiles = useMemo(() => [
    {
      key: 'stat:total-kills',
      label: 'Total Boss Kills',
      value: totalBossKills,
    },
    {
      detail: totalBossKills > 0 && mostKilledBoss ? `${mostKilledBoss.kills} kills` : '0 kills',
      key: 'stat:most-killed',
      label: 'Most Killed Boss',
      value: totalBossKills > 0 && mostKilledBoss ? mostKilledBoss.encounter.name : 'None',
    },
    {
      key: 'stat:hunter-level',
      label: 'Hunter Level',
      value: save.character.level,
    },
    {
      key: 'stat:experience',
      label: 'Experience',
      value: `${save.character.experience} XP`,
    },
    {
      key: 'stat:collection-drops',
      label: 'Collection Drops',
      value: totalDrops + totalPets,
      detail: `${totalPets} pets, ${save.inventory.length} inventory slots`,
    },
  ], [mostKilledBoss, save.character.experience, save.character.level, save.inventory.length, totalBossKills, totalDrops, totalPets])
  const focusedItem = collectionItems.find((item) => item.key === focusedItemKey) ?? collectionItems[0] ?? null
  const navEntries = useMemo<Iwt2HunterProfileNavEntry[]>(() => {
    const entries: Iwt2HunterProfileNavEntry[] = [
      { kind: 'back', key: 'back', row: 0, column: 0 },
      { kind: 'tab', key: 'tab:stats', row: 0, column: 1, tab: 'stats' },
      { kind: 'tab', key: 'tab:collection', row: 0, column: 2, tab: 'collection' },
    ]
    if (activeTab === 'stats') {
      entries.push(...statTiles.map((tile, index) => ({
        column: index,
        index,
        key: tile.key,
        kind: 'stat' as const,
        row: 1,
      })))
    } else {
      entries.push(
        ...bosses.map((boss, index) => ({
          bossId: boss.bossId,
          column: 0,
          key: `boss:${boss.bossId}`,
          kind: 'boss' as const,
          row: index + 1,
        })),
        ...collectionItems.map((item, index) => ({
          column: (index % IWT2_HUNTER_PROFILE_DROP_COLUMNS) + 1,
          itemKey: item.key,
          key: `item:${item.key}`,
          kind: 'item' as const,
          row: Math.floor(index / IWT2_HUNTER_PROFILE_DROP_COLUMNS) + 1,
        })),
      )
    }
    return entries
  }, [activeTab, bosses, collectionItems, statTiles])
  const activeIndex = Math.min(selectedIndex, Math.max(0, navEntries.length - 1))
  const activeEntry = navEntries[activeIndex]
  const itemDetailState = useMemo<DualScreenWorkshopState | null>(() => {
    if (activeTab !== 'collection' || !selectedBoss || !focusedItem) return null
    return {
      items: [
        {
          detail: focusedItem.source,
          glyph: focusedItem.glyph,
          meta: focusedItem.chance,
          status: focusedItem.quantity > 0 ? 'Collected' : 'Missing',
          title: 'Drop Rate',
        },
        {
          detail: 'I Want To Heal 2',
          meta: `${selectedBoss.kills}`,
          status: `${selectedBoss.drops} drops`,
          title: 'Boss Kills',
        },
      ],
      mode: 'collection',
      subtitle: selectedBoss.encounter.name,
      summary: `Owned x${focusedItem.quantity}`,
      title: focusedItem.name,
    }
  }, [activeTab, focusedItem, selectedBoss])
  useDualScreenWorkshopPublisher(itemDetailState, dualScreenEnabled)

  function selected(entryKey: string) {
    return activeEntry?.key === entryKey
  }

  function selectKey(entryKey: string) {
    const index = navEntries.findIndex((entry) => entry.key === entryKey)
    if (index >= 0) setSelectedIndex(index)
  }

  function selectTab(tab: Iwt2HunterProfileTab) {
    setActiveTab(tab)
    setSelectedIndex(tab === 'stats' ? 1 : 2)
  }

  function selectBoss(bossId: Iwt2BossId) {
    setSelectedBossId(bossId)
    setFocusedItemKey(null)
  }

  function moveSelection(action: InputAction) {
    if (!action.startsWith('navigate') || navEntries.length === 0) return
    const nextIndex = moveHunterProfileSelection(navEntries, selectedIndex, action)
    const nextEntry = navEntries[nextIndex]
    setSelectedIndex(nextIndex)
    if (nextEntry?.kind === 'item') setFocusedItemKey(nextEntry.itemKey)
  }

  function openEntry(entry: Iwt2HunterProfileNavEntry | undefined) {
    if (!entry) return
    if (entry.kind === 'back') onBack()
    else if (entry.kind === 'tab') selectTab(entry.tab)
    else if (entry.kind === 'boss') selectBoss(entry.bossId)
    else if (entry.kind === 'item') setFocusedItemKey(entry.itemKey)
  }

  useEffect(() => {
    if (!activeEntry) return
    screenRef.current
      ?.querySelector<HTMLElement>('[data-game-selected="true"]')
      ?.scrollIntoView({ block: 'nearest', inline: 'nearest' })
  }, [activeEntry])

  useGameAction((action, inputDevice) => {
    if (inputDevice !== 'controller') return
    if (action === 'back') {
      onBack()
      return
    }
    if (action === 'confirm') {
      openEntry(activeEntry)
      return
    }
    moveSelection(action)
  })

  return (
    <section className="content-screen hunter-profile-screen" data-game-nav-active="true" ref={screenRef}>
      <div className="screen-heading hunter-profile-heading">
        <div className="equipment-tabs hunter-profile-tabs" role="tablist" aria-label="Hunter profile tabs">
          <button
            aria-selected={activeTab === 'stats'}
            className={`equipment-tab ${activeTab === 'stats' ? 'active' : ''} ${selected('tab:stats') ? 'game-selected' : ''}`}
            data-controller-nav="skip"
            data-game-selected={selected('tab:stats') ? 'true' : undefined}
            onClick={() => selectTab('stats')}
            onPointerDown={() => selectKey('tab:stats')}
            role="tab"
            type="button"
          >
            Stats
          </button>
          <button
            aria-selected={activeTab === 'collection'}
            className={`equipment-tab ${activeTab === 'collection' ? 'active' : ''} ${selected('tab:collection') ? 'game-selected' : ''}`}
            data-controller-nav="skip"
            data-game-selected={selected('tab:collection') ? 'true' : undefined}
            onClick={() => selectTab('collection')}
            onPointerDown={() => selectKey('tab:collection')}
            role="tab"
            type="button"
          >
            Collection Log
          </button>
        </div>
        <button
          className={`back-button ${selected('back') ? 'game-selected' : ''}`}
          data-controller-nav="skip"
          data-game-selected={selected('back') ? 'true' : undefined}
          onClick={onBack}
          onPointerDown={() => selectKey('back')}
          type="button"
        >
          Back
        </button>
      </div>

      {activeTab === 'stats' && (
        <div className="hunter-stat-grid">
          {statTiles.map((tile) => (
            <article
              className={`hunter-stat-tile ${selected(tile.key) ? 'game-selected' : ''}`}
              data-game-selected={selected(tile.key) ? 'true' : undefined}
              key={tile.key}
              onPointerDown={() => selectKey(tile.key)}
            >
              <span>{tile.label}</span>
              <strong>{tile.value}</strong>
              {tile.detail && <small>{tile.detail}</small>}
            </article>
          ))}
        </div>
      )}

      {activeTab === 'collection' && (
        <div className="collection-log-layout">
          <div className="collection-boss-list" aria-label="Bosses">
            {bosses.map((boss) => (
              <button
                className={`collection-boss-button ${selectedBoss?.bossId === boss.bossId ? 'selected' : ''} ${selected(`boss:${boss.bossId}`) ? 'game-selected' : ''}`}
                data-controller-nav="skip"
                data-game-selected={selected(`boss:${boss.bossId}`) ? 'true' : undefined}
                key={boss.bossId}
                onClick={() => selectBoss(boss.bossId)}
                onPointerDown={() => selectKey(`boss:${boss.bossId}`)}
                type="button"
              >
                <span>{bossInitialsForProfile(boss.encounter.name)}</span>
                <strong>{boss.encounter.name}</strong>
              </button>
            ))}
          </div>

          {selectedBoss && (
            <article className="collection-boss-detail">
              <div className="collection-drop-list">
                {collectionItems.map((item) => (
                  <button
                    className={`collection-drop-row collection-rarity-${item.rarity} ${item.quantity <= 0 ? 'missing' : 'owned'} ${selected(`item:${item.key}`) ? 'game-selected' : ''}`}
                    data-controller-nav="skip"
                    data-game-selected={selected(`item:${item.key}`) ? 'true' : undefined}
                    key={item.key}
                    onClick={() => setFocusedItemKey(item.key)}
                    onFocus={() => setFocusedItemKey(item.key)}
                    onPointerDown={() => selectKey(`item:${item.key}`)}
                    type="button"
                  >
                    <span>{item.glyph}</span>
                    <strong>{item.name}</strong>
                    <small>{item.chance}</small>
                    <b>x{item.quantity}</b>
                  </button>
                ))}
              </div>
            </article>
          )}
        </div>
      )}
    </section>
  )
}

function bossInitialsForProfile(name: string) {
  return name
    .split(/\s+/)
    .filter(Boolean)
    .slice(0, 2)
    .map((part) => part[0]?.toUpperCase() ?? '')
    .join('')
}

export function Iwt2CustomizeCharacterScreen({
  onBack,
  onSaveUpdated,
  save,
}: {
  onBack: () => void
  onSaveUpdated: (save: Iwt2Save) => void
  save: Iwt2Save
}) {
  const [editingName, setEditingName] = useState(false)
  const activeAbilities = abilitiesForHealer(save.character.healerStyle)
  const abilityRows = activeAbilities.map((ability) => ({
    key: ability.id,
    label: ability.name,
    detail: `${ability.manaCost} resource. ${ability.cooldownSeconds}s cooldown.`,
    value: ability.icon,
  }))
  const actions = useMemo<Iwt2NavAction[]>(() => withBackAction([
    {
      key: 'profile-name',
      label: 'Profile Name',
      detail: 'IWT2 hunter callsign shown in header.',
      value: save.character.name,
      onConfirm: () => setEditingName(true),
    },
    ...IWT2_HEALER_ORDER.map((healerId) => {
      const healer = IWT2_HEALER_METADATA[healerId]
      return {
        key: `class-${healerId}`,
        label: healer.name,
        detail: healer.description,
        value: save.character.healerStyle === healerId ? 'Selected' : healer.icon,
        onConfirm: () => onSaveUpdated(updateIwt2CharacterSettings(save, {
          healerStyle: healerId,
        })),
      }
    }),
  ], onBack), [onBack, onSaveUpdated, save])

  if (editingName) {
    return (
      <Iwt2ScreenShell eyebrow="Character Workshop" title="Profile Name" onBack={() => setEditingName(false)}>
        <Iwt2ProfileNameEditor
          name={save.character.name}
          onCancel={() => setEditingName(false)}
          onSave={(name) => {
            onSaveUpdated(updateIwt2CharacterSettings(save, { name }))
            setEditingName(false)
          }}
        />
      </Iwt2ScreenShell>
    )
  }

  return (
    <Iwt2ScreenShell eyebrow="Character Workshop" title="Customize Character" onBack={onBack}>
      <div className="iwt2-customize-layout">
        <div>
          <p className="eyebrow">Healing Class</p>
          <Iwt2ActionList actions={actions} />
        </div>
        <div className="iwt2-loadout-preview">
          <div>
            <p className="eyebrow">Active Loadout</p>
            <h2>Ability Bar</h2>
            <span>Select a class, then start a run.</span>
          </div>
          <Iwt2InfoList rows={abilityRows} />
        </div>
      </div>
    </Iwt2ScreenShell>
  )
}

function Iwt2ProfileNameEditor({
  name,
  onCancel,
  onSave,
}: {
  name: string
  onCancel: () => void
  onSave: (name: string) => void
}) {
  const [draft, setDraft] = useState(name)
  const [selectedIndex, setSelectedIndex] = useState(0)
  const entries = useMemo(() => [
    ...IWT2_NAME_CHARACTERS.map((character) => ({
      key: `char-${character}`,
      label: character,
      onConfirm: () => setDraft((current) => `${current}${character}`.slice(0, IWT2_NAME_MAX_LENGTH)),
    })),
    {
      key: 'space',
      label: 'Space',
      onConfirm: () => setDraft((current) => `${current} `.slice(0, IWT2_NAME_MAX_LENGTH)),
    },
    {
      key: 'dash',
      label: '-',
      onConfirm: () => setDraft((current) => `${current}-`.slice(0, IWT2_NAME_MAX_LENGTH)),
    },
    {
      key: 'erase',
      label: 'Erase',
      onConfirm: () => setDraft((current) => current.slice(0, -1)),
    },
    {
      key: 'save',
      label: 'Save',
      onConfirm: () => onSave(draft),
    },
    {
      key: 'cancel',
      label: 'Cancel',
      onConfirm: onCancel,
    },
  ], [draft, onCancel, onSave])

  useGameAction((action, device) => {
    if (device !== 'controller') return
    if (action === 'back') {
      onCancel()
      return
    }
    if (action === 'confirm') {
      entries[selectedIndex]?.onConfirm()
      return
    }
    if (action === 'navigateLeft') {
      setSelectedIndex((current) => Math.max(0, current - 1))
    } else if (action === 'navigateRight') {
      setSelectedIndex((current) => Math.min(entries.length - 1, current + 1))
    } else if (action === 'navigateUp') {
      setSelectedIndex((current) => Math.max(0, current - IWT2_NAME_EDITOR_COLUMNS))
    } else if (action === 'navigateDown') {
      setSelectedIndex((current) => Math.min(entries.length - 1, current + IWT2_NAME_EDITOR_COLUMNS))
    }
  })

  return (
    <div className="iwt2-name-editor">
      <label>
        <span>Profile Name</span>
        <input
          data-controller-nav="skip"
          maxLength={IWT2_NAME_MAX_LENGTH}
          onChange={(event) => setDraft(event.target.value.slice(0, IWT2_NAME_MAX_LENGTH))}
          value={draft}
        />
      </label>
      <div className="iwt2-name-grid">
        {entries.map((entry, index) => (
          <button
            className={selectedIndex === index ? 'game-selected selected' : ''}
            data-controller-nav="skip"
            data-game-selected={selectedIndex === index ? 'true' : undefined}
            key={entry.key}
            onClick={entry.onConfirm}
            onPointerDown={() => setSelectedIndex(index)}
            type="button"
          >
            {entry.label}
          </button>
        ))}
      </div>
    </div>
  )
}

export function Iwt2CloudSaveScreen({
  onBack,
  onSaveUpdated,
  onlineBackupsAvailable,
  save,
}: {
  onBack: () => void
  onSaveUpdated: (save: Iwt2Save) => void
  onlineBackupsAvailable: boolean
  save: Iwt2Save
}) {
  const [onlineSave, setOnlineSave] = useState<Iwt2Save | null>(null)
  const [syncingOnlineSave, setSyncingOnlineSave] = useState(false)
  const [message, setMessage] = useState('')
  const statusMessage = message || (!onlineBackupsAvailable ? 'Online save unavailable in offline mode.' : '')

  const checkOnlineSave = useCallback(async (isCancelled: () => boolean) => {
    setSyncingOnlineSave(true)
    setMessage('Checking online save...')
    try {
      const result = await loadIwt2OnlineSave()
      if (isCancelled()) return
      setOnlineSave(result.save)
      setMessage(result.save ? 'Online save loaded.' : 'No online save yet.')
    } catch (reason) {
      if (isCancelled()) return
      setOnlineSave(null)
      setMessage(reason instanceof Error ? reason.message : 'Unable to check online save.')
    } finally {
      if (!isCancelled()) setSyncingOnlineSave(false)
    }
  }, [])

  useEffect(() => {
    let cancelled = false
    if (!onlineBackupsAvailable) {
      return () => {
        cancelled = true
      }
    }
    const timer = window.setTimeout(() => {
      void checkOnlineSave(() => cancelled)
    }, 0)
    return () => {
      cancelled = true
      window.clearTimeout(timer)
    }
  }, [checkOnlineSave, onlineBackupsAvailable])

  const handleUseLocalSave = useCallback(async () => {
    if (!onlineBackupsAvailable) {
      setMessage('Using local save. Sign in online to update the server copy.')
      return
    }
    setSyncingOnlineSave(true)
    setMessage('Uploading local save...')
    try {
      const result = await writeIwt2OnlineSave(save)
      setOnlineSave(result.save)
      setMessage('Online save now uses local progress.')
    } catch (reason) {
      setMessage(reason instanceof Error ? reason.message : 'Unable to upload local save.')
    } finally {
      setSyncingOnlineSave(false)
    }
  }, [onlineBackupsAvailable, save])

  const handleUseOnlineSave = useCallback(() => {
    if (!onlineBackupsAvailable || !onlineSave) return
    onSaveUpdated(onlineSave)
    setMessage('Local save now uses online progress.')
  }, [onlineBackupsAvailable, onlineSave, onSaveUpdated])

  const handleUseNewSave = useCallback(() => {
    onSaveUpdated(createDefaultIwt2Save())
    setMessage('Started a new local IWT2 save.')
  }, [onSaveUpdated])

  const actions = useMemo<Iwt2NavAction[]>(() => withBackAction([
    {
      key: 'use-local',
      label: 'Use Local Save',
      detail: formatIwt2SaveSummary(save),
      value: onlineBackupsAvailable ? 'Upload' : 'Current',
      disabled: syncingOnlineSave,
      onConfirm: () => {
        void handleUseLocalSave()
      },
    },
    {
      key: 'use-online',
      label: 'Use Online Save',
      detail: syncingOnlineSave ? 'Checking online save...' : formatIwt2SaveSummary(onlineSave),
      value: onlineSave ? 'Download' : 'Empty',
      disabled: syncingOnlineSave || !onlineBackupsAvailable || !onlineSave,
      onConfirm: handleUseOnlineSave,
    },
    {
      key: 'new-save',
      label: 'Use New Save File',
      detail: 'Start over with a fresh IWT2 character. Online save is not overwritten until you choose local save.',
      value: 'New',
      onConfirm: handleUseNewSave,
    },
  ], onBack), [
    handleUseLocalSave,
    handleUseNewSave,
    handleUseOnlineSave,
    onlineBackupsAvailable,
    onlineSave,
    onBack,
    save,
    syncingOnlineSave,
  ])

  return (
    <Iwt2ScreenShell title="Backup Slot" onBack={onBack}>
      {statusMessage && <p className="iwt2-screen-note">{statusMessage}</p>}
      <Iwt2ActionList actions={actions} />
    </Iwt2ScreenShell>
  )
}

export function Iwt2GearUpgradeScreen({
  onBack,
  onSaveUpdated,
  save,
}: {
  onBack: () => void
  onSaveUpdated: (save: Iwt2Save) => void
  save: Iwt2Save
}) {
  const [selectedClassId, setSelectedClassId] = useState<Iwt2PlayerClassId>('healer')
  const [selectedSlotId, setSelectedSlotId] = useState<Iwt2GearSlotId>('weapon')
  const [selectedIndex, setSelectedIndex] = useState(1)
  const [message, setMessage] = useState('Gear upgrades use IWT2 boss coins only.')
  const classProgress = save.gearProgress[selectedClassId]
  const selectedSlot = classProgress.slots[selectedSlotId]
  const selectedRecipe = IWT2_GEAR_SLOT_RECIPES[selectedClassId][selectedSlotId]
  const selectedBonus = gearBonusSummary(selectedRecipe.statId, selectedSlot.level, selectedClassId)
  const nextLevel = Math.min(5, selectedSlot.level + 1)
  const nextBonus = gearBonusSummary(selectedRecipe.statId, nextLevel, selectedClassId)
  const selectedClassName = gearClassDisplayName(selectedClassId, save)
  const upgradeCosts = iwt2GearUpgradeCosts(selectedClassId, selectedSlotId, selectedSlot.level)
  const canUpgrade = selectedSlot.level < 5 && canAffordIwt2Costs(save, upgradeCosts)
  const infusionUnlocked = isIwt2InfusionUnlocked(classProgress)
  const infusionAnchorSlot = selectedSlot.level >= 5
    ? selectedSlotId
    : IWT2_GEAR_SLOTS.find((slotId) => classProgress.slots[slotId].level >= 5) ?? selectedSlotId
  const infusionAbilities = iwt2InfusionAbilitiesForClass(selectedClassId)
  const navEntries = useMemo<Iwt2GearNavEntry[]>(() => {
    const entries: Iwt2GearNavEntry[] = [{ kind: 'back', key: 'back', row: 0, column: 0 }]
    IWT2_PARTY_ORDER.forEach((classId, index) => {
      entries.push({ kind: 'class', key: `class:${classId}`, row: index + 1, column: 0, classId })
    })
    IWT2_GEAR_SLOTS.forEach((slotId, index) => {
      entries.push({ kind: 'slot', key: `slot:${slotId}`, row: index + 1, column: 1, slotId })
    })
    entries.push({ kind: 'upgrade', key: 'upgrade', row: 6, column: 1, disabled: !canUpgrade })
    infusionAbilities.forEach((ability, index) => {
      const costs = iwt2InfusionCosts(selectedClassId, infusionAnchorSlot, ability.id)
      const selected = classProgress.infusionAbilityId === ability.id
      entries.push({
        kind: 'infusion',
        key: `infusion:${ability.id}`,
        row: index + 1,
        column: 2,
        abilityId: ability.id,
        disabled: selected || !infusionUnlocked || !canAffordIwt2Costs(save, costs),
      })
    })
    return entries
  }, [canUpgrade, classProgress.infusionAbilityId, infusionAbilities, infusionAnchorSlot, infusionUnlocked, save, selectedClassId])

  const activeEntry = navEntries[Math.min(selectedIndex, navEntries.length - 1)] ?? navEntries[0]

  useGameAction((action, device) => {
    if (device !== 'controller') return
    if (action === 'back') {
      onBack()
      return
    }
    if (action.startsWith('navigate')) {
      setSelectedIndex((current) => moveGearSelection(navEntries, current, action))
      return
    }
    if (action === 'confirm' && activeEntry) {
      activateEntry(activeEntry)
    }
  })

  function activateEntry(entry: Iwt2GearNavEntry) {
    if (entry.kind === 'back') {
      onBack()
      return
    }
    if (entry.kind === 'class') {
      setSelectedClassId(entry.classId)
      return
    }
    if (entry.kind === 'slot') {
      setSelectedSlotId(entry.slotId)
      return
    }
    if (entry.kind === 'upgrade') {
      if (entry.disabled) return
      try {
        const nextSave = upgradeIwt2GearSlot(save, selectedClassId, selectedSlotId)
        onSaveUpdated(nextSave)
        setMessage(`${selectedClassName} ${IWT2_GEAR_SLOT_LABELS[selectedSlotId]} upgraded to +${selectedSlot.level + 1}.`)
      } catch (error) {
        setMessage(error instanceof Error ? error.message : 'Upgrade failed.')
      }
      return
    }
    if (entry.kind === 'infusion') {
      if (entry.disabled) return
      try {
        const nextSave = setIwt2InfusionAbility(save, selectedClassId, infusionAnchorSlot, entry.abilityId)
        onSaveUpdated(nextSave)
        setMessage(`${IWT2_INFUSION_ABILITIES[entry.abilityId].name} infused into slot 6.`)
      } catch (error) {
        setMessage(error instanceof Error ? error.message : 'Infusion failed.')
      }
    }
  }

  return (
    <section className="content-screen iwt2-screen-shell iwt2-gear-screen" data-game-nav-active="true">
      <div className="iwt2-gear-layout">
        <section className="iwt2-gear-column">
          <button
            className={`back-button iwt2-gear-back ${activeEntry?.kind === 'back' ? 'game-selected' : ''}`}
            data-controller-nav="skip"
            data-game-selected={activeEntry?.kind === 'back' ? 'true' : undefined}
            onClick={onBack}
            onPointerDown={() => selectGearEntry(navEntries, setSelectedIndex, 'back')}
            type="button"
          >
            Back
          </button>
          <div className="iwt2-gear-class-list">
            {IWT2_PARTY_ORDER.map((classId) => {
              const metadata = IWT2_CLASS_METADATA[classId]
              const selected = selectedClassId === classId
              const focused = activeEntry?.kind === 'class' && activeEntry.classId === classId
              const classInfusion = save.gearProgress[classId].infusionAbilityId
              const highest = Math.max(...IWT2_GEAR_SLOTS.map((slotId) => save.gearProgress[classId].slots[slotId].level))
              const className = gearClassDisplayName(classId, save)
              const classSubtitle = gearClassSubtitle(classId, save)
              return (
                <button
                  className={`iwt2-gear-class ${selected ? 'active' : ''} ${focused ? 'game-selected' : ''}`}
                  data-controller-nav="skip"
                  data-game-selected={focused ? 'true' : undefined}
                  key={classId}
                  onClick={() => setSelectedClassId(classId)}
                  onPointerDown={() => selectGearEntry(navEntries, setSelectedIndex, `class:${classId}`)}
                  type="button"
                >
                  <span style={{ '--class-color': metadata.color } as CSSProperties}>{metadata.icon}</span>
                  <div>
                    <strong>{className}</strong>
                    <small>{classSubtitle}</small>
                    <small>Top +{highest}{classInfusion ? ' | Slot 6 set' : ''}</small>
                  </div>
                </button>
              )
            })}
          </div>
        </section>

        <section className="iwt2-gear-column iwt2-gear-slots-panel">
          <div className="iwt2-gear-slot-list">
            {IWT2_GEAR_SLOTS.map((slotId) => {
              const recipe = IWT2_GEAR_SLOT_RECIPES[selectedClassId][slotId]
              const slot = classProgress.slots[slotId]
              const focused = activeEntry?.kind === 'slot' && activeEntry.slotId === slotId
              const selected = selectedSlotId === slotId
              return (
                <button
                  className={`iwt2-gear-slot ${selected ? 'active' : ''} ${focused ? 'game-selected' : ''}`}
                  data-controller-nav="skip"
                  data-game-selected={focused ? 'true' : undefined}
                  key={slotId}
                  onClick={() => setSelectedSlotId(slotId)}
                  onPointerDown={() => selectGearEntry(navEntries, setSelectedIndex, `slot:${slotId}`)}
                  type="button"
                >
                  <span>
                    <strong>{IWT2_GEAR_SLOT_LABELS[slotId]}</strong>
                    <small>{IWT2_GEAR_STAT_LABELS[recipe.statId]}</small>
                  </span>
                  <em>+{slot.level}</em>
                </button>
              )
            })}
          </div>
          <button
            className={`iwt2-gear-upgrade-button ${activeEntry?.kind === 'upgrade' ? 'game-selected' : ''}`}
            data-controller-nav="skip"
            data-game-selected={activeEntry?.kind === 'upgrade' ? 'true' : undefined}
            disabled={!canUpgrade}
            onClick={() => activateEntry({ kind: 'upgrade', key: 'upgrade', row: 6, column: 1, disabled: !canUpgrade })}
            onPointerDown={() => selectGearEntry(navEntries, setSelectedIndex, 'upgrade')}
            type="button"
          >
            Upgrade to +{Math.min(5, selectedSlot.level + 1)}
          </button>
        </section>

        <section className="iwt2-gear-column iwt2-gear-detail-panel">
          <div className="iwt2-gear-detail">
            <p>
              <strong>{IWT2_GEAR_SLOT_LABELS[selectedSlotId]} +{selectedSlot.level}</strong>
              <small>{IWT2_GEAR_STAT_LABELS[selectedRecipe.statId]} from {bossName(selectedRecipe.primaryBossId)} and {bossName(selectedRecipe.secondaryBossId)} coins.</small>
            </p>
            <div className="iwt2-gear-bonus-grid">
              <span>
                <strong>Current bonus</strong>
                <small>{selectedBonus.text}</small>
              </span>
              <span>
                <strong>{selectedSlot.level >= 5 ? 'Max rank' : `Upgrade preview +${selectedSlot.level} -> +${nextLevel}`}</strong>
                <small>{selectedSlot.level >= 5 ? infusionAnchorText(classProgress.slots[selectedSlotId].level) : `${selectedBonus.label}: ${selectedBonus.value} -> ${nextBonus.value}`}</small>
              </span>
            </div>
            <div className="iwt2-gear-cost-list">
              {upgradeCosts.length === 0 ? (
                <span>Max rank reached.</span>
              ) : upgradeCosts.map((cost) => {
                const owned = inventoryQuantity(save.inventory, cost.itemId)
                return (
                  <span className={owned >= cost.quantity ? 'met' : 'missing'} key={cost.itemId}>
                    {owned}/{cost.quantity} {cost.itemName}
                  </span>
                )
              })}
            </div>
          </div>

          <div className="iwt2-infusion-list">
            {infusionAbilities.map((ability) => {
              const selected = classProgress.infusionAbilityId === ability.id
              const focused = activeEntry?.kind === 'infusion' && activeEntry.abilityId === ability.id
              const costs = iwt2InfusionCosts(selectedClassId, infusionAnchorSlot, ability.id)
              const disabled = selected || !infusionUnlocked || !canAffordIwt2Costs(save, costs)
              return (
                <button
                  className={`iwt2-infusion-row ${selected ? 'active' : ''} ${focused ? 'game-selected' : ''}`}
                  data-controller-nav="skip"
                  data-game-selected={focused ? 'true' : undefined}
                  disabled={disabled}
                  key={ability.id}
                  onClick={() => activateEntry({ kind: 'infusion', key: `infusion:${ability.id}`, row: 0, column: 2, abilityId: ability.id, disabled })}
                  onPointerDown={() => selectGearEntry(navEntries, setSelectedIndex, `infusion:${ability.id}`)}
                  type="button"
                >
                  <span>{ability.icon}</span>
                  <div>
                    <strong>{ability.name}</strong>
                    <small>{ability.description}</small>
                    <small>{selected ? 'Selected' : infusionUnlocked ? infusionCostText(save, costs) : 'Unlock: any slot to +5'}</small>
                  </div>
                </button>
              )
            })}
          </div>
          <footer className="iwt2-gear-message">{message}</footer>
        </section>
      </div>
    </section>
  )
}

function gearClassDisplayName(classId: Iwt2PlayerClassId, save: Iwt2Save): string {
  if (classId === 'healer') return save.character.name
  return IWT2_CLASS_METADATA[classId].name
}

function gearClassSubtitle(classId: Iwt2PlayerClassId, save: Iwt2Save): string {
  if (classId === 'healer') return IWT2_HEALER_METADATA[save.character.healerStyle].name
  const role = IWT2_CLASS_METADATA[classId].role
  return role === 'damage' ? 'Damage' : 'Tank'
}

function gearBonusSummary(
  statId: Iwt2GearStatId,
  level: number,
  classId: Iwt2PlayerClassId,
): { label: string, text: string, value: string } {
  const bonus = Math.max(0, level)
  if (statId === 'maxHealth') return gearBonus('Max health', `+${bonus * 4}%`)
  if (statId === 'moveSpeed') return gearBonus('Move speed', `+${formatPercent(bonus * 2.5)}%`)
  if (statId === 'damage') return gearBonus('Attack damage', `+${bonus * 4}%`)
  if (statId === 'attackCooldown') {
    const label = classId === 'healer' ? 'Ability cooldown' : 'Attack cooldown'
    return gearBonus(label, `-${bonus * 3}%`)
  }
  if (statId === 'projectileSpeed') return gearBonus('Projectile speed', `+${bonus * 3}%`)
  if (statId === 'hazardDamageTaken') return gearBonus('Hazard damage taken', `-${bonus * 3}%`)
  if (statId === 'stunResist') return gearBonus('Stun/knockdown duration', `-${bonus * 8}%`)
  if (statId === 'healingPower') return gearBonus('Ability healing', `+${bonus * 4}%`)
  return gearBonus('Bonus', '+0%')
}

function gearBonus(label: string, value: string): { label: string, text: string, value: string } {
  return { label, text: `${label} ${value}`, value }
}

function infusionAnchorText(level: number): string {
  return level >= 5 ? 'Infusion anchor available.' : 'No further bonus.'
}

function formatPercent(value: number): string {
  return Number.isInteger(value) ? String(value) : value.toFixed(1)
}

function clampToEnabled(actions: Iwt2NavAction[], index: number) {
  if (actions.length === 0) return 0
  const bounded = Math.min(Math.max(0, index), actions.length - 1)
  if (!actions[bounded]?.disabled) return bounded
  for (let offset = 1; offset < actions.length; offset += 1) {
    const previous = bounded - offset
    const next = bounded + offset
    if (previous >= 0 && !actions[previous]?.disabled) return previous
    if (next < actions.length && !actions[next]?.disabled) return next
  }
  return 0
}

function nextEnabledIndex(actions: Iwt2NavAction[], current: number, direction: -1 | 1) {
  if (actions.length === 0) return 0
  let index = current
  for (let steps = 0; steps < actions.length; steps += 1) {
    index = Math.min(actions.length - 1, Math.max(0, index + direction))
    if (!actions[index]?.disabled) return index
    if (index === 0 || index === actions.length - 1) break
  }
  return clampToEnabled(actions, current)
}

function moveGearSelection(entries: Iwt2GearNavEntry[], current: number, action: string): number {
  if (entries.length === 0) return 0
  const active = entries[Math.min(current, entries.length - 1)] ?? entries[0]
  const candidates = entries
    .map((entry, index) => ({ entry, index }))
    .filter(({ entry, index }) => {
      if (index === current) return false
      if (action === 'navigateLeft') return entry.column < active.column
      if (action === 'navigateRight') return entry.column > active.column
      if (action === 'navigateUp') return entry.column === active.column && entry.row < active.row
      if (action === 'navigateDown') return entry.column === active.column && entry.row > active.row
      return false
    })
  if (candidates.length === 0) return current
  candidates.sort((a, b) => {
    const primaryA = action === 'navigateLeft' || action === 'navigateRight'
      ? Math.abs(a.entry.column - active.column)
      : Math.abs(a.entry.row - active.row)
    const primaryB = action === 'navigateLeft' || action === 'navigateRight'
      ? Math.abs(b.entry.column - active.column)
      : Math.abs(b.entry.row - active.row)
    if (primaryA !== primaryB) return primaryA - primaryB
    const secondaryA = action === 'navigateLeft' || action === 'navigateRight'
      ? Math.abs(a.entry.row - active.row)
      : Math.abs(a.entry.column - active.column)
    const secondaryB = action === 'navigateLeft' || action === 'navigateRight'
      ? Math.abs(b.entry.row - active.row)
      : Math.abs(b.entry.column - active.column)
    return secondaryA - secondaryB
  })
  return candidates[0]?.index ?? current
}

function selectGearEntry(
  entries: Iwt2GearNavEntry[],
  setSelectedIndex: (value: number) => void,
  key: string,
) {
  const index = entries.findIndex((entry) => entry.key === key)
  if (index >= 0) setSelectedIndex(index)
}

function bossName(bossId: Iwt2BossId): string {
  return IWT2_BOSS_METADATA[bossId].name
}

function infusionCostText(save: Iwt2Save, costs: Array<{ itemId: string, itemName: string, quantity: number }>): string {
  return costs
    .map((cost) => `${inventoryQuantity(save.inventory, cost.itemId)}/${cost.quantity} ${cost.itemName}`)
    .join(' | ')
}

function activeUpgradeEntry(entries: Iwt2UpgradeNavEntry[], index: number) {
  if (entries.length === 0) return undefined
  return entries[Math.min(index, entries.length - 1)]
}

function moveHunterProfileSelection(
  entries: Iwt2HunterProfileNavEntry[],
  current: number,
  action: InputAction,
) {
  if (!action.startsWith('navigate') || entries.length === 0) return current
  const bounded = Math.min(current, entries.length - 1)
  const active = entries[bounded]
  if (!active) return 0
  const candidates = entries
    .map((entry, index) => ({ entry, index }))
    .filter(({ index }) => index !== bounded)
    .filter(({ entry }) => {
      if (action === 'navigateLeft') return entry.column < active.column
      if (action === 'navigateRight') return entry.column > active.column
      if (action === 'navigateUp') return entry.row < active.row
      return entry.row > active.row
    })
  if (candidates.length === 0) return bounded
  candidates.sort((a, b) => {
    const aPrimary = Math.abs(a.entry.row - active.row) + Math.abs(a.entry.column - active.column)
    const bPrimary = Math.abs(b.entry.row - active.row) + Math.abs(b.entry.column - active.column)
    const aSecondary = action === 'navigateLeft' || action === 'navigateRight'
      ? Math.abs(a.entry.row - active.row)
      : Math.abs(a.entry.column - active.column)
    const bSecondary = action === 'navigateLeft' || action === 'navigateRight'
      ? Math.abs(b.entry.row - active.row)
      : Math.abs(b.entry.column - active.column)
    return aPrimary - bPrimary || aSecondary - bSecondary || a.index - b.index
  })
  return candidates[0]?.index ?? bounded
}

function upgradeEntryDisabled(entry: Iwt2UpgradeNavEntry) {
  return entry.kind === 'upgradeContinue' && Boolean(entry.disabled)
}

function moveUpgradeSelection(entries: Iwt2UpgradeNavEntry[], current: number, action: string) {
  if (entries.length === 0) return 0
  const bounded = Math.min(current, entries.length - 1)
  const active = entries[bounded]
  const continueIndex = entries.findIndex((entry) => entry.kind === 'upgradeContinue' || entry.kind === 'upgradeLeave')
  const lastChoiceIndex = Math.max(0, continueIndex >= 0 ? continueIndex - 1 : entries.length - 1)
  if (action === 'navigateRight') return Math.min(lastChoiceIndex, bounded + 1)
  if (action === 'navigateLeft') return Math.max(0, bounded - 1)
  if (action === 'navigateDown') return continueIndex >= 0 ? continueIndex : bounded
  if (action === 'navigateUp' && active?.kind === 'upgradeContinue') return lastChoiceIndex
  if (action === 'navigateUp' && active?.kind === 'upgradeLeave') return lastChoiceIndex
  return bounded
}

function moveSpatialSelection(actions: Iwt2NavAction[], current: number, action: string) {
  if (actions.length === 0) return 0
  const bounded = Math.min(current, actions.length - 1)
  const active = actions[bounded]
  if (!active) return bounded
  const activePosition = roguelikeActionPosition(active.key)
  const candidates = actions
    .map((entry, index) => ({ entry, index, position: roguelikeActionPosition(entry.key) }))
    .filter(({ index, position }) => {
      if (index === bounded) return false
      if (action === 'navigateLeft') return position.row === activePosition.row && position.column < activePosition.column
      if (action === 'navigateRight') return position.row === activePosition.row && position.column > activePosition.column
      if (action === 'navigateUp') return position.row < activePosition.row
      return position.row > activePosition.row
    })
  if (candidates.length === 0) return bounded
  candidates.sort((a, b) => {
    const aPrimary = Math.abs(a.position.row - activePosition.row)
      + Math.abs(a.position.column - activePosition.column)
    const bPrimary = Math.abs(b.position.row - activePosition.row)
      + Math.abs(b.position.column - activePosition.column)
    const aSecondary = action === 'navigateLeft' || action === 'navigateRight'
      ? Math.abs(a.position.column - activePosition.column)
      : Math.abs(a.position.row - activePosition.row)
    const bSecondary = action === 'navigateLeft' || action === 'navigateRight'
      ? Math.abs(b.position.column - activePosition.column)
      : Math.abs(b.position.row - activePosition.row)
    return aPrimary - bPrimary || aSecondary - bSecondary || a.index - b.index
  })
  return candidates[0]?.index ?? bounded
}

function roguelikeActionPosition(key: string): Iwt2NavPosition {
  if (key === 'back') return { row: 0, column: 0 }
  if (key === 'variant-pve') return { row: 1, column: 0 }
  if (key === 'variant-pvp') return { row: 1, column: 1 }
  if (key === 'pve-dungeon' || key === 'pvp-dungeon') return { row: 2, column: 0 }
  if (key === 'pve-raid' || key === 'pvp-raid') return { row: 2, column: 1 }
  if (key === 'pvp-stadium') return { row: 2, column: 2 }
  if (key === 'pvp-start') return { row: 3, column: 1 }
  return { row: 3, column: 0 }
}

export function Iwt2SettingsScreen({ onBack }: { onBack: () => void }) {
  const {
    controllerIconStyle,
    directPartyTargeting,
    setControllerIconStyle,
    setDirectPartyTargeting,
  } = useInput()
  const actions = useMemo<Iwt2NavAction[]>(() => withBackAction([
    {
      key: 'direct-targeting',
      label: 'Target Party Members With Bindings',
      detail: 'When enabled, party frames show direct target button icons instead of D-pad cycling.',
      value: directPartyTargeting ? 'On' : 'Off',
      onConfirm: () => setDirectPartyTargeting(!directPartyTargeting),
    },
    ...CONTROLLER_ICON_OPTIONS.map((style) => ({
      key: `icons-${style}`,
      label: `${CONTROLLER_ICON_LABELS[style]} Button Icons`,
      detail: 'Changes controller glyphs shown on IWT2 frames and spell controls.',
      value: controllerIconStyle === style ? 'Selected' : '',
      onConfirm: () => setControllerIconStyle(style),
    })),
  ], onBack), [
    controllerIconStyle,
    directPartyTargeting,
    onBack,
    setControllerIconStyle,
    setDirectPartyTargeting,
  ])

  return (
    <Iwt2ScreenShell title="Settings" onBack={onBack}>
      <div className="iwt2-settings-panel">
        <Iwt2ActionList actions={actions} />
        <div className="iwt2-controller-preview" aria-label="Current controller icon style">
          <span>{CONTROLLER_ICON_LABELS[controllerIconStyle]}</span>
          <ControllerStylePreview iconStyle={controllerIconStyle} />
        </div>
      </div>
    </Iwt2ScreenShell>
  )
}
