import { canEquipItem } from './claudeCraftEquipmentRules'
import { ACTION_DUNGEON_LOOT_MOBS, CLAUDECRAFT_DUNGEON_MOBS } from './claudeCraftDungeons'
import { getClaudeCraftItem } from './claudeCraftItems'
import {
  allocateTalentPoint,
  emptyTalentAllocation,
  normalizeTalentAllocation,
  pointsSpent,
  setTalentSpec,
  talentPointsAtLevel,
  type TalentAllocation,
} from './claudeCraftTalents'
import { EQUIP_SLOTS, type EquipSlot, type InvSlot, type ItemDef, type PlayerClass } from './claudeCraftTypes'

export type ActionDifficulty = 'ilvl-1' | 'ilvl-10' | 'ilvl-20' | 'ilvl-30'
export type ActionDungeonId =
  | 'bulldrome'
  | 'yian-kut-ku'
  | 'cyber-dragon'
export type ActionRunMode = 'hunt' | 'marathon' | 'boss'
export type ActionGearSource = ActionDungeonId
export type ActionCoinColor = 'white' | 'green' | 'blue' | 'purple'
export type ActionCoinWallet = Record<ActionGearSource, Record<ActionCoinColor, number>>
export type ActionEquipment = Partial<Record<EquipSlot, string>>
export type ActionSkinCatalog = 'class' | 'mech'

export type ActionCharacterAppearance = {
  skin: number
  skinCatalog: ActionSkinCatalog
}

export type ActionCharacterCreateInput = {
  appearance: ActionCharacterAppearance
  classId: PlayerClass
  name: string
}

export type ActionDifficultyTier = {
  id: ActionDifficulty
  label: string
  itemLevel: 1 | 10 | 20 | 30
  coinColor: ActionCoinColor
  coinLabel: string
  healthMultiplier: number
  damageMultiplier: number
  lootMultiplier: number
  experience: number
}

export type ActionRunReward = {
  coins: number
  coinName: string
  experience: number
  items: ItemDef[]
  leveledUp: boolean
}

export type ActionPvpRoundReward = {
  experience: number
  matchBonusExperience: number
  roundExperience: number
  leveledUp: boolean
}

export type ActionCharacter = {
  id: string
  name: string
  classId: PlayerClass
  appearance: ActionCharacterAppearance
  level: number
  experience: number
  copper: number
  actionCoins: ActionCoinWallet
  bulldromeCoins: number
  yianKutKuCoins: number
  bulldromeNormalClears: number
  bulldromeHardClears: number
  yianKutKuNormalClears: number
  yianKutKuHardClears: number
  inventory: InvSlot[]
  equipment: ActionEquipment
  talents: TalentAllocation
}

export type ActionCharacterRoster = {
  activeCharacterId: string | null
  characters: ActionCharacter[]
}

export const ACTION_EQUIP_SLOTS: Array<{
  slot: EquipSlot
  label: string
  glyph: string
}> = [
  { slot: 'mainhand', label: 'Weapon', glyph: '/' },
  { slot: 'helmet', label: 'Head', glyph: 'H' },
  { slot: 'shoulder', label: 'Shoulders', glyph: 'S' },
  { slot: 'chest', label: 'Chest', glyph: 'C' },
  { slot: 'waist', label: 'Waist', glyph: 'W' },
  { slot: 'legs', label: 'Legs', glyph: 'L' },
  { slot: 'gloves', label: 'Hands', glyph: 'G' },
  { slot: 'feet', label: 'Feet', glyph: 'B' },
]

const ACTION_GEAR_SOURCE_NAMES: Record<ActionGearSource, string> = {
  bulldrome: 'Bulldrome',
  'yian-kut-ku': 'Yian Kut-Ku',
  'cyber-dragon': 'Cyber Dragon',
}

const ACTION_GEAR_SOURCES = Object.keys(ACTION_GEAR_SOURCE_NAMES) as ActionGearSource[]

export const ACTION_DIFFICULTY_TIERS: ActionDifficultyTier[] = [
  {
    id: 'ilvl-1',
    label: 'iLvl 1',
    itemLevel: 1,
    coinColor: 'white',
    coinLabel: 'White Coins',
    healthMultiplier: 1,
    damageMultiplier: 1,
    lootMultiplier: 1,
    experience: 60,
  },
  {
    id: 'ilvl-10',
    label: 'iLvl 10',
    itemLevel: 10,
    coinColor: 'green',
    coinLabel: 'Green Coins',
    healthMultiplier: 1.55,
    damageMultiplier: 1.28,
    lootMultiplier: 2,
    experience: 110,
  },
  {
    id: 'ilvl-20',
    label: 'iLvl 20',
    itemLevel: 20,
    coinColor: 'blue',
    coinLabel: 'Blue Coins',
    healthMultiplier: 2.25,
    damageMultiplier: 1.62,
    lootMultiplier: 3,
    experience: 180,
  },
  {
    id: 'ilvl-30',
    label: 'iLvl 30',
    itemLevel: 30,
    coinColor: 'purple',
    coinLabel: 'Purple Coins',
    healthMultiplier: 3.1,
    damageMultiplier: 2.05,
    lootMultiplier: 4,
    experience: 280,
  },
]

export const ACTION_SAVE_KEY = 'i-want-to-heal:action-mode-save:v3'
export const ACTION_ROSTER_SAVE_KEY = 'i-want-to-heal:action-mode-roster:v1'
const LEGACY_ACTION_SAVE_KEYS = [
  'i-want-to-heal:action-mode-save:v2',
  'i-want-to-heal:action-mode-save:v1',
]
const CLAUDECRAFT_XP_TABLE = [
  400, 900, 1400, 2100, 2800, 3600, 4500, 5400, 6500, 7600, 8800, 10100, 11400, 12900, 14400, 16000,
  17700, 19400, 21300, 23200,
] as const
const MAX_ACTION_LEVEL = 20
export const ACTION_PVP_ROUND_EXPERIENCE = 30
export const ACTION_PVP_MATCH_WIN_BONUS_EXPERIENCE = 120
export const HEALER_ACTION_CLASSES: PlayerClass[] = ['priest', 'paladin', 'shaman', 'druid']
export const ACTION_CLASS_SKIN_COUNTS: Record<PlayerClass, number> = {
  warrior: 4,
  paladin: 2,
  hunter: 4,
  rogue: 4,
  priest: 4,
  shaman: 4,
  mage: 4,
  warlock: 4,
  druid: 4,
}

const DEFAULT_ACTION_CHARACTER: ActionCharacter = {
  id: 'action-local-1',
  name: 'Action Healer',
  classId: 'priest',
  appearance: { skin: 0, skinCatalog: 'class' },
  level: 1,
  experience: 0,
  copper: 0,
  actionCoins: createEmptyCoinWallet(),
  bulldromeCoins: 0,
  yianKutKuCoins: 0,
  bulldromeNormalClears: 0,
  bulldromeHardClears: 0,
  yianKutKuNormalClears: 0,
  yianKutKuHardClears: 0,
  inventory: [],
  equipment: {
    chest: 'apprentice_robe',
    mainhand: 'gnarled_staff',
  },
  talents: emptyTalentAllocation(),
}

export function loadActionRoster(): ActionCharacterRoster {
  const savedRoster = window.localStorage.getItem(ACTION_ROSTER_SAVE_KEY)
  if (savedRoster) {
    try {
      return normalizeRoster(JSON.parse(savedRoster))
    } catch {
      return { activeCharacterId: null, characters: [] }
    }
  }

  const legacyCharacter = loadLegacyActionCharacter()
  if (legacyCharacter) {
    return {
      activeCharacterId: legacyCharacter.id,
      characters: [legacyCharacter],
    }
  }

  return { activeCharacterId: null, characters: [] }
}

export function saveActionRoster(roster: ActionCharacterRoster) {
  window.localStorage.setItem(ACTION_ROSTER_SAVE_KEY, JSON.stringify(normalizeRoster(roster)))
}

export function loadActionCharacter(): ActionCharacter {
  const roster = loadActionRoster()
  return getActiveActionCharacter(roster) ?? DEFAULT_ACTION_CHARACTER
}

function loadLegacyActionCharacter(): ActionCharacter | null {
  const saved = window.localStorage.getItem(ACTION_SAVE_KEY)
    ?? LEGACY_ACTION_SAVE_KEYS.map((key) => window.localStorage.getItem(key)).find(Boolean)
  if (!saved) return null

  try {
    return normalizeActionCharacter(JSON.parse(saved), DEFAULT_ACTION_CHARACTER.id)
  } catch {
    return null
  }
}

export function saveActionCharacter(character: ActionCharacter) {
  const roster = loadActionRoster()
  saveActionRoster(updateActionRosterCharacter(
    roster.characters.length ? roster : { activeCharacterId: character.id, characters: [character] },
    character,
  ))
  window.localStorage.setItem(ACTION_SAVE_KEY, JSON.stringify(character))
}

export function getActiveActionCharacter(roster: ActionCharacterRoster) {
  return roster.characters.find((character) => character.id === roster.activeCharacterId)
    ?? roster.characters[0]
    ?? null
}

export function createActionCharacter(input: ActionCharacterCreateInput): ActionCharacter {
  const classId = HEALER_ACTION_CLASSES.includes(input.classId) ? input.classId : 'priest'
  const id = `action-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`
  const starter = starterEquipmentForClass(classId)
  return {
    ...DEFAULT_ACTION_CHARACTER,
    id,
    name: normalizeCharacterName(input.name),
    classId,
    appearance: normalizeAppearance(input.appearance, classId),
    actionCoins: createEmptyCoinWallet(),
    inventory: [],
    equipment: starter,
    talents: emptyTalentAllocation(),
  }
}

export function addActionRosterCharacter(roster: ActionCharacterRoster, input: ActionCharacterCreateInput) {
  const character = createActionCharacter(input)
  return {
    activeCharacterId: character.id,
    characters: [...roster.characters, character],
  }
}

export function updateActionRosterCharacter(roster: ActionCharacterRoster, character: ActionCharacter): ActionCharacterRoster {
  const normalized = normalizeActionCharacter(character, character.id)
  const exists = roster.characters.some((candidate) => candidate.id === normalized.id)
  return normalizeRoster({
    activeCharacterId: roster.activeCharacterId ?? normalized.id,
    characters: exists
      ? roster.characters.map((candidate) => candidate.id === normalized.id ? normalized : candidate)
      : [...roster.characters, normalized],
  })
}

export function switchActionRosterCharacter(roster: ActionCharacterRoster, characterId: string): ActionCharacterRoster {
  if (!roster.characters.some((character) => character.id === characterId)) return roster
  return { ...roster, activeCharacterId: characterId }
}

export function completeActionDungeonHunt(
  character: ActionCharacter,
  dungeonId: ActionDungeonId,
  difficulty: ActionDifficulty,
): { character: ActionCharacter, reward: ActionRunReward } {
  const tier = getActionDifficultyTier(difficulty)
  const coinRoll = randomInt(1, 3)
  const coinReward = coinRoll * tier.lootMultiplier
  const loot = rollActionDungeonLoot(dungeonId, character.classId, tier.lootMultiplier)
  const experienceReward = getExperienceReward(dungeonId, tier)
  const nextExperience = character.experience + experienceReward
  const nextLevel = getActionLevel(nextExperience)
  const actionCoins = addActionCoins(character.actionCoins, dungeonId, tier.coinColor, coinReward)

  return {
    character: {
      ...character,
      level: nextLevel,
      experience: nextExperience,
      copper: character.copper + loot.copper,
      actionCoins,
      bulldromeCoins: dungeonId === 'bulldrome' ? actionCoins.bulldrome.white : character.bulldromeCoins,
      yianKutKuCoins: dungeonId === 'yian-kut-ku' ? actionCoins['yian-kut-ku'].white : character.yianKutKuCoins,
      bulldromeNormalClears: character.bulldromeNormalClears + (dungeonId === 'bulldrome' && difficulty === 'ilvl-1' ? 1 : 0),
      bulldromeHardClears: character.bulldromeHardClears + (dungeonId === 'bulldrome' && difficulty !== 'ilvl-1' ? 1 : 0),
      yianKutKuNormalClears: character.yianKutKuNormalClears + (dungeonId === 'yian-kut-ku' && difficulty === 'ilvl-1' ? 1 : 0),
      yianKutKuHardClears: character.yianKutKuHardClears + (dungeonId === 'yian-kut-ku' && difficulty !== 'ilvl-1' ? 1 : 0),
      inventory: addInventoryItems(character.inventory, loot.items.map((item) => item.id)),
    },
    reward: {
      coins: loot.copper,
      coinName: 'Copper',
      experience: experienceReward,
      items: loot.items,
      leveledUp: nextLevel > character.level,
    },
  }
}

export function completeActionPvpRound(
  character: ActionCharacter,
  playerWonRound: boolean,
  playerWonMatch: boolean,
): { character: ActionCharacter, reward: ActionPvpRoundReward } {
  const roundExperience = ACTION_PVP_ROUND_EXPERIENCE * (playerWonRound ? 2 : 1)
  const matchBonusExperience = playerWonMatch ? ACTION_PVP_MATCH_WIN_BONUS_EXPERIENCE : 0
  const experienceReward = roundExperience + matchBonusExperience
  const nextExperience = character.experience + experienceReward
  const nextLevel = getActionLevel(nextExperience)

  return {
    character: {
      ...character,
      level: nextLevel,
      experience: nextExperience,
    },
    reward: {
      experience: experienceReward,
      matchBonusExperience,
      roundExperience,
      leveledUp: nextLevel > character.level,
    },
  }
}

export function equipActionItem(character: ActionCharacter, itemId: string): ActionCharacter {
  const item = getClaudeCraftItem(itemId)
  if (!item?.slot || (item.kind !== 'weapon' && item.kind !== 'armor')) return character
  if (!canEquipItem(character.classId, item)) return character
  if (getInventoryCount(character.inventory, itemId) <= 0) return character

  const oldItemId = character.equipment[item.slot]
  return {
    ...character,
    equipment: {
      ...character.equipment,
      [item.slot]: itemId,
    },
    inventory: oldItemId
      ? addInventoryItems(removeInventoryItem(character.inventory, itemId), [oldItemId])
      : removeInventoryItem(character.inventory, itemId),
  }
}

export function unequipActionSlot(character: ActionCharacter, slot: EquipSlot): ActionCharacter {
  const itemId = character.equipment[slot]
  if (!itemId) return character
  const equipment = { ...character.equipment }
  delete equipment[slot]
  return {
    ...character,
    equipment,
    inventory: addInventoryItems(character.inventory, [itemId]),
  }
}

export function getEquippedActionItem(character: ActionCharacter, slot: EquipSlot) {
  const itemId = character.equipment[slot]
  return itemId ? getClaudeCraftItem(itemId) : null
}

export function getActionLevel(experience: number) {
  const lifetimeXp = Math.max(0, experience)
  let level = 1
  while (level < MAX_ACTION_LEVEL && lifetimeXp >= getActionXpToReachLevel(level + 1)) level += 1
  return level
}

export function getActionLevelProgress(character: ActionCharacter) {
  const currentLevelStart = getActionXpToReachLevel(character.level)
  const nextLevelStart = getActionXpToReachLevel(character.level + 1)
  const earnedThisLevel = Math.max(0, character.experience - currentLevelStart)
  const neededThisLevel = Math.max(1, nextLevelStart - currentLevelStart)
  return {
    currentLevelStart,
    nextLevelStart,
    percent: character.level >= MAX_ACTION_LEVEL
      ? 100
      : Math.max(0, Math.min(100, (earnedThisLevel / neededThisLevel) * 100)),
  }
}

export function getActionXpToReachLevel(level: number) {
  const targetLevel = Math.max(1, Math.min(MAX_ACTION_LEVEL, Math.floor(level)))
  let total = 0
  for (let currentLevel = 1; currentLevel < targetLevel; currentLevel += 1) {
    total += CLAUDECRAFT_XP_TABLE[currentLevel - 1] ?? CLAUDECRAFT_XP_TABLE[CLAUDECRAFT_XP_TABLE.length - 1]
  }
  return total
}

export function getActionTalentPoints(character: Pick<ActionCharacter, 'level' | 'talents'>) {
  const total = talentPointsAtLevel(character.level)
  const spent = pointsSpent(character.talents)
  return {
    available: Math.max(0, total - spent),
    spent,
    total,
  }
}

export function chooseActionTalentSpec(character: ActionCharacter, specId: string): ActionCharacter {
  return {
    ...character,
    talents: setTalentSpec(character.classId, character.talents, specId),
  }
}

export function spendActionTalentPoint(character: ActionCharacter, nodeId: string, choiceId?: string): { character: ActionCharacter, ok: boolean, reason?: string } {
  const result = allocateTalentPoint(character.classId, character.talents, nodeId, getActionTalentPoints(character).total, choiceId)
  return {
    character: result.ok ? { ...character, talents: result.allocation } : character,
    ok: result.ok,
    reason: result.reason,
  }
}

export function respecActionTalents(character: ActionCharacter): ActionCharacter {
  return { ...character, talents: emptyTalentAllocation() }
}

export function getActionDifficultyTier(difficulty: ActionDifficulty) {
  return ACTION_DIFFICULTY_TIERS.find((tier) => tier.id === difficulty) ?? ACTION_DIFFICULTY_TIERS[0]
}

export function getActionCoinCount(
  character: Pick<ActionCharacter, 'actionCoins' | 'bulldromeCoins' | 'yianKutKuCoins'>,
  source: ActionGearSource,
  color: ActionCoinColor,
) {
  if (color === 'white' && source === 'yian-kut-ku') return character.yianKutKuCoins
  if (color === 'white' && source === 'bulldrome') return character.bulldromeCoins
  return character.actionCoins?.[source]?.[color] ?? 0
}

function rollActionDungeonLoot(dungeonId: ActionDungeonId, classId: PlayerClass, rolls: number) {
  const mobIds = ACTION_DUNGEON_LOOT_MOBS[dungeonId] ?? []
  const items: ItemDef[] = []
  let copper = 0

  for (let rollIndex = 0; rollIndex < Math.max(1, rolls); rollIndex += 1) {
    for (const mobId of mobIds) {
      const mob = CLAUDECRAFT_DUNGEON_MOBS[mobId]
      if (!mob) continue
      const result = rollMobLoot(mob.loot)
      copper += result.copper
      for (const itemId of result.itemIds) {
        const item = getClaudeCraftItem(itemId)
        if (!item || (item.kind !== 'weapon' && item.kind !== 'armor')) continue
        if (!canEquipItem(classId, item)) continue
        items.push(item)
      }
    }
  }

  return { copper, items }
}

function rollMobLoot(loot: Array<{ itemId?: string; copper?: number; chance: number; rollGroup?: string }>) {
  const itemIds: string[] = []
  let copper = 0
  const rolledGroups = new Set<string>()

  for (const entry of loot) {
    if (entry.rollGroup) {
      if (rolledGroups.has(entry.rollGroup)) continue
      rolledGroups.add(entry.rollGroup)
      const group = loot.filter((candidate) => candidate.rollGroup === entry.rollGroup)
      const roll = Math.random()
      let cumulative = 0
      for (const groupedEntry of group) {
        cumulative += groupedEntry.chance
        if (roll < cumulative) {
          if (groupedEntry.itemId) itemIds.push(groupedEntry.itemId)
          break
        }
      }
      continue
    }

    if (Math.random() >= entry.chance) continue
    if (entry.copper) copper += randomInt(Math.ceil(entry.copper * 0.6), Math.ceil(entry.copper * 1.4))
    if (entry.itemId) itemIds.push(entry.itemId)
  }

  return { copper, itemIds }
}

function getExperienceReward(dungeonId: ActionDungeonId, tier: ActionDifficultyTier) {
  if (dungeonId === 'yian-kut-ku') return Math.ceil(tier.experience * 1.25)
  if (dungeonId === 'cyber-dragon') return Math.ceil(tier.experience * 1.15)
  return tier.experience
}

function normalizeInventory(raw: unknown): InvSlot[] {
  if (!Array.isArray(raw)) return DEFAULT_ACTION_CHARACTER.inventory
  const slots = new Map<string, number>()
  for (const item of raw) {
    if (!item || typeof item !== 'object') continue
    const candidate = item as Partial<InvSlot> & { slug?: string }
    const itemId = typeof candidate.itemId === 'string'
      ? candidate.itemId
      : typeof candidate.slug === 'string' && getClaudeCraftItem(candidate.slug)
        ? candidate.slug
        : ''
    if (!itemId || !getClaudeCraftItem(itemId)) continue
    slots.set(itemId, (slots.get(itemId) ?? 0) + Math.max(1, Number(candidate.count ?? 1)))
  }
  return Array.from(slots, ([itemId, count]) => ({ itemId, count }))
}

function normalizeRoster(raw: unknown): ActionCharacterRoster {
  if (!raw || typeof raw !== 'object') return { activeCharacterId: null, characters: [] }
  const parsed = raw as Partial<ActionCharacterRoster> & { character?: unknown }
  const rawCharacters = Array.isArray(parsed.characters)
    ? parsed.characters
    : parsed.character
      ? [parsed.character]
      : []
  const seenIds = new Set<string>()
  const characters = rawCharacters
    .map((character, index) => normalizeActionCharacter(character, `action-local-${index + 1}`))
    .filter((character) => {
      if (seenIds.has(character.id)) return false
      seenIds.add(character.id)
      return true
    })
  const activeCharacterId = characters.some((character) => character.id === parsed.activeCharacterId)
    ? parsed.activeCharacterId ?? null
    : characters[0]?.id ?? null
  return { activeCharacterId, characters }
}

function normalizeActionCharacter(raw: unknown, fallbackId: string): ActionCharacter {
  const parsed = raw && typeof raw === 'object' ? raw as Partial<ActionCharacter> & { inventory?: unknown[] } : {}
  const experience = Number(parsed.experience ?? DEFAULT_ACTION_CHARACTER.experience)
  const classId = normalizePlayerClass(parsed.classId)
  return {
    ...DEFAULT_ACTION_CHARACTER,
    ...parsed,
    id: typeof parsed.id === 'string' && parsed.id ? parsed.id : fallbackId,
    name: normalizeCharacterName(parsed.name),
    classId,
    appearance: normalizeAppearance(parsed.appearance, classId),
    copper: Number(parsed.copper ?? 0),
    experience,
    level: getActionLevel(experience),
    actionCoins: normalizeCoinWallet(parsed),
    bulldromeCoins: Number(parsed.bulldromeCoins ?? DEFAULT_ACTION_CHARACTER.bulldromeCoins),
    yianKutKuCoins: Number(parsed.yianKutKuCoins ?? DEFAULT_ACTION_CHARACTER.yianKutKuCoins),
    bulldromeNormalClears: Number(parsed.bulldromeNormalClears ?? DEFAULT_ACTION_CHARACTER.bulldromeNormalClears),
    bulldromeHardClears: Number(parsed.bulldromeHardClears ?? DEFAULT_ACTION_CHARACTER.bulldromeHardClears),
    yianKutKuNormalClears: Number(parsed.yianKutKuNormalClears ?? DEFAULT_ACTION_CHARACTER.yianKutKuNormalClears),
    yianKutKuHardClears: Number(parsed.yianKutKuHardClears ?? DEFAULT_ACTION_CHARACTER.yianKutKuHardClears),
    inventory: normalizeInventory(parsed.inventory),
    equipment: normalizeEquipment(parsed.equipment),
    talents: normalizeTalentAllocation(parsed.talents, classId),
  }
}

function normalizeAppearance(raw: unknown, classId: PlayerClass): ActionCharacterAppearance {
  const parsed = raw && typeof raw === 'object' ? raw as Partial<ActionCharacterAppearance> : {}
  const maxSkin = ACTION_CLASS_SKIN_COUNTS[classId] - 1
  return {
    skin: Math.max(0, Math.min(maxSkin, Math.floor(Number(parsed.skin ?? 0)))),
    skinCatalog: parsed.skinCatalog === 'mech' ? 'mech' : 'class',
  }
}

function normalizeCharacterName(value: unknown) {
  const name = typeof value === 'string' ? value.trim().replace(/\s+/g, ' ') : ''
  if (name.length < 2) return DEFAULT_ACTION_CHARACTER.name
  return name.slice(0, 20)
}

function starterEquipmentForClass(classId: PlayerClass): ActionEquipment {
  if (classId === 'paladin' || classId === 'shaman') {
    return {
      chest: 'recruit_tunic',
      mainhand: 'worn_sword',
    }
  }
  return {
    chest: 'apprentice_robe',
    mainhand: 'gnarled_staff',
  }
}

function normalizeEquipment(raw: unknown): ActionEquipment {
  if (!raw || typeof raw !== 'object') return DEFAULT_ACTION_CHARACTER.equipment
  const equipment: ActionEquipment = {}
  const parsed = raw as Partial<Record<EquipSlot, string>>
  for (const slot of EQUIP_SLOTS) {
    const itemId = parsed[slot]
    const item = itemId ? getClaudeCraftItem(itemId) : null
    if (item?.slot === slot) equipment[slot] = itemId
  }
  return Object.keys(equipment).length > 0 ? equipment : DEFAULT_ACTION_CHARACTER.equipment
}

function addInventoryItems(inventory: InvSlot[], itemIds: string[]) {
  const next = inventory.map((slot) => ({ ...slot }))
  for (const itemId of itemIds) {
    const existing = next.find((slot) => slot.itemId === itemId)
    if (existing) existing.count += 1
    else next.push({ itemId, count: 1 })
  }
  return next
}

function removeInventoryItem(inventory: InvSlot[], itemId: string) {
  const next = inventory.map((slot) => ({ ...slot }))
  const slot = next.find((candidate) => candidate.itemId === itemId)
  if (!slot) return next
  slot.count -= 1
  return next.filter((candidate) => candidate.count > 0)
}

function getInventoryCount(inventory: InvSlot[], itemId: string) {
  return inventory.find((slot) => slot.itemId === itemId)?.count ?? 0
}

function normalizePlayerClass(value: unknown): PlayerClass {
  const classes: PlayerClass[] = ['warrior', 'paladin', 'hunter', 'rogue', 'priest', 'shaman', 'mage', 'warlock', 'druid']
  return classes.includes(value as PlayerClass) ? value as PlayerClass : DEFAULT_ACTION_CHARACTER.classId
}

function createEmptyCoinWallet(): ActionCoinWallet {
  return ACTION_GEAR_SOURCES.reduce((wallet, source) => {
    wallet[source] = { white: 0, green: 0, blue: 0, purple: 0 }
    return wallet
  }, {} as ActionCoinWallet)
}

function normalizeCoinWallet(parsed: Partial<ActionCharacter>) {
  const wallet = createEmptyCoinWallet()
  const saved = parsed.actionCoins
  for (const source of ACTION_GEAR_SOURCES) {
    for (const color of ['white', 'green', 'blue', 'purple'] as const) {
      wallet[source][color] = Number(saved?.[source]?.[color] ?? 0)
    }
  }
  wallet.bulldrome.white = Number(parsed.bulldromeCoins ?? wallet.bulldrome.white)
  wallet['yian-kut-ku'].white = Number(parsed.yianKutKuCoins ?? wallet['yian-kut-ku'].white)
  return wallet
}

function addActionCoins(
  wallet: ActionCoinWallet,
  source: ActionGearSource,
  color: ActionCoinColor,
  amount: number,
) {
  return {
    ...wallet,
    [source]: {
      ...wallet[source],
      [color]: Math.max(0, wallet[source][color] + amount),
    },
  }
}

function randomInt(min: number, max: number) {
  return Math.floor(Math.random() * (max - min + 1)) + min
}
