import 'package:asciineverdie/data/class_data.dart';
import 'package:asciineverdie/data/game_text_l10n.dart' as l10n;
import 'package:asciineverdie/data/race_data.dart';
import 'package:asciineverdie/src/core/model/class_traits.dart';
import 'package:asciineverdie/src/core/engine/act_progression_service.dart';
import 'package:asciineverdie/src/core/engine/combat_tick_service.dart';
import 'package:asciineverdie/src/core/engine/death_handler.dart';
import 'package:asciineverdie/src/core/engine/game_mutations.dart';
import 'package:asciineverdie/src/core/engine/loot_handler.dart';
import 'package:asciineverdie/src/core/engine/market_service.dart';
import 'package:asciineverdie/src/core/engine/reward_service.dart';
import 'package:asciineverdie/src/core/engine/skill_service.dart';
import 'package:asciineverdie/src/core/engine/task_generator.dart';
import 'package:asciineverdie/src/core/model/combat_event.dart';
import 'package:asciineverdie/src/core/model/game_state.dart';
import 'package:asciineverdie/src/core/model/pq_config.dart';
import 'package:asciineverdie/src/core/util/balance_constants.dart';
import 'package:asciineverdie/src/core/util/pq_logic.dart' as pq_logic;

class ProgressTickResult {
  const ProgressTickResult({
    required this.state,
    this.leveledUp = false,
    this.completedQuest = false,
    this.completedAct = false,
    this.playerDied = false,
    this.gameComplete = false,
  });

  final GameState state;
  final bool leveledUp;
  final bool completedQuest;
  final bool completedAct;

  /// 플레이어 사망 여부 (Phase 4)
  final bool playerDied;

  /// 게임 클리어 여부 (Act V 완료)
  final bool gameComplete;

  bool get shouldAutosave =>
      leveledUp || completedQuest || completedAct || playerDied || gameComplete;
}

/// Drives quest/plot/task progression by applying queued actions and rewards.
class ProgressService {
  ProgressService({
    required this.config,
    required this.mutations,
    required this.rewards,
  }) : _taskGenerator = TaskGenerator(config: config),
       _lootHandler = LootHandler(mutations: mutations);

  final PqConfig config;
  final GameMutations mutations;
  final RewardService rewards;

  final TaskGenerator _taskGenerator;
  final LootHandler _lootHandler;
  static const _deathHandler = DeathHandler();

  /// 새 게임 초기화 (원본 GoButtonClick, Main.pas:741-767)
  /// Prologue 태스크들을 큐에 추가하고 첫 태스크 시작
  GameState initializeNewGame(GameState state) {
    // 초기 큐 설정 - ASCII NEVER DIE 세계관 프롤로그 (l10n 지원)
    final prologueTexts = l10n.prologueTexts;
    final initialQueue = <QueueEntry>[
      QueueEntry(
        kind: QueueKind.task,
        durationMillis: 10 * 1000,
        caption: prologueTexts[0],
        taskType: TaskType.load,
      ),
      QueueEntry(
        kind: QueueKind.task,
        durationMillis: 6 * 1000,
        caption: prologueTexts[1],
        taskType: TaskType.load,
      ),
      QueueEntry(
        kind: QueueKind.task,
        durationMillis: 6 * 1000,
        caption: prologueTexts[2],
        taskType: TaskType.load,
      ),
      QueueEntry(
        kind: QueueKind.task,
        durationMillis: 4 * 1000,
        caption: prologueTexts[3],
        taskType: TaskType.load,
      ),
      QueueEntry(
        kind: QueueKind.plot,
        durationMillis: 2 * 1000,
        caption: l10n.taskCompiling,
        taskType: TaskType.plot,
      ),
    ];

    // 첫 번째 태스크 시작 (원본 752줄)
    final taskResult = pq_logic.startTask(
      state.progress,
      l10n.taskCompiling,
      2 * 1000,
    );

    // ExpBar 초기화 (원본 743-746줄)
    final expBar = ProgressBarState(
      position: 0,
      max: ExpConstants.requiredExp(1),
    );

    // PlotBar 초기화 - Prologue 5분 (300초)
    final plotBar = const ProgressBarState(position: 0, max: 300);

    final progress = taskResult.progress.copyWith(
      exp: expBar,
      plot: plotBar,
      currentTask: TaskInfo(
        caption: '${l10n.taskCompiling}...',
        type: TaskType.load,
      ),
      plotStageCount: 1, // Prologue
      questCount: 0,
      plotHistory: [
        HistoryEntry(caption: l10n.taskPrologue, isComplete: false),
      ],
      questHistory: const [],
    );

    return _recalculateEncumbrance(
      state.copyWith(
        progress: progress,
        queue: QueueState(entries: initialQueue),
      ),
    );
  }

  /// Starts a task and tags its type (kill, plot, load, neutral).
  GameState startTask(
    GameState state, {
    required String caption,
    required int durationMillis,
    TaskType taskType = TaskType.neutral,
  }) {
    final taskResult = pq_logic.startTask(
      state.progress,
      caption,
      durationMillis,
    );
    final progress = taskResult.progress.copyWith(
      currentTask: TaskInfo(caption: taskResult.caption, type: taskType),
    );
    return state.copyWith(progress: progress);
  }

  /// Tick the timer loop (equivalent to Timer1Timer in the original code).
  ProgressTickResult tick(GameState state, int elapsedMillis) {
    final int clamped = elapsedMillis.clamp(0, 10000).toInt();

    // 1. 스킬 시스템 업데이트 (시간, 버프, MP 회복)
    var nextState = _updateSkillSystem(state, clamped);
    var progress = nextState.progress;
    var queue = nextState.queue;

    // 2. 태스크 바 진행 중이면 전투 틱 처리
    if (progress.task.position < progress.task.max) {
      return _processTaskInProgress(nextState, clamped);
    }

    // 3. 태스크 완료 처리
    final gain = progress.currentTask.type == TaskType.kill;
    final incrementSeconds = progress.task.max ~/ 1000;
    final int monsterExpReward =
        progress.currentCombat?.monsterStats.expReward ?? 0;
    var leveledUp = false;
    var questDone = false;
    var actDone = false;
    var gameComplete = false;

    // 4. 킬 태스크 완료 처리
    if (gain) {
      final killResult = _handleKillTaskCompletion(nextState, progress, queue);
      if (killResult.earlyReturn != null) return killResult.earlyReturn!;
      nextState = killResult.state;
      progress = killResult.progress;
      queue = killResult.queue;
    }

    // 5. 시장/판매/구매 태스크 완료 처리
    final marketResult = _handleMarketTaskCompletion(
      nextState,
      progress,
      queue,
    );
    if (marketResult.earlyReturn != null) return marketResult.earlyReturn!;
    nextState = marketResult.state;
    progress = marketResult.progress;
    queue = marketResult.queue;

    // 6. 경험치/레벨업 처리
    if (gain && nextState.traits.level < 100 && monsterExpReward > 0) {
      final expResult = _handleExpGain(nextState, progress, monsterExpReward);
      nextState = expResult.state;
      progress = expResult.progress;
      leveledUp = expResult.leveledUp;
    }

    // 7. 퀘스트 진행 처리
    final questResult = _handleQuestProgress(
      nextState,
      progress,
      queue,
      gain,
      incrementSeconds,
    );
    nextState = questResult.state;
    progress = questResult.progress;
    queue = questResult.queue;
    questDone = questResult.completed;

    // 8. 플롯 진행 및 Act Boss 소환 처리
    progress = _handlePlotProgress(nextState, progress, gain, incrementSeconds);

    // 9. 다음 태스크 디큐/생성
    final dequeueResult = _handleTaskDequeue(nextState, progress, queue);
    nextState = dequeueResult.state;
    progress = dequeueResult.progress;
    queue = dequeueResult.queue;
    actDone = dequeueResult.actDone;
    gameComplete = dequeueResult.gameComplete;

    nextState = _recalculateEncumbrance(
      nextState.copyWith(progress: progress, queue: queue),
    );

    return ProgressTickResult(
      state: nextState,
      leveledUp: leveledUp,
      completedQuest: questDone,
      completedAct: actDone,
      gameComplete: gameComplete,
    );
  }

  /// 스킬 시스템 업데이트 (시간, 버프 정리, MP 회복)
  GameState _updateSkillSystem(GameState state, int elapsedMs) {
    final skillService = SkillService(rng: state.rng);
    var skillSystem = skillService.updateElapsedTime(
      state.skillSystem,
      elapsedMs,
    );
    skillSystem = skillService.cleanupExpiredBuffs(skillSystem);

    var nextState = state.copyWith(skillSystem: skillSystem);

    // 비전투 시 MP 회복
    final isInCombat =
        state.progress.currentTask.type == TaskType.kill &&
        state.progress.currentCombat != null &&
        state.progress.currentCombat!.isActive;

    if (!isInCombat && nextState.stats.mp < nextState.stats.mpMax) {
      final mpRegen = skillService.calculateMpRegen(
        elapsedMs: elapsedMs,
        isInCombat: false,
        wis: nextState.stats.wis,
      );
      if (mpRegen > 0) {
        final newMp = (nextState.stats.mp + mpRegen).clamp(
          0,
          nextState.stats.mpMax,
        );
        nextState = nextState.copyWith(
          stats: nextState.stats.copyWith(mpCurrent: newMp),
        );
      }
    }

    return nextState;
  }

  /// 태스크 진행 중 처리 (전투 틱 포함)
  ProgressTickResult _processTaskInProgress(GameState state, int elapsedMs) {
    var progress = state.progress;
    final uncapped = progress.task.position + elapsedMs;
    final int newTaskPos = uncapped > progress.task.max
        ? progress.task.max
        : uncapped;

    var updatedCombat = progress.currentCombat;
    var updatedSkillSystem = state.skillSystem;
    var updatedPotionInventory = state.potionInventory;
    var nextState = state;

    // 킬 태스크 중 전투 진행
    if (progress.currentTask.type == TaskType.kill &&
        updatedCombat != null &&
        updatedCombat.isActive) {
      final combatTickService = CombatTickService(rng: state.rng);
      final combatResult = combatTickService.processTick(
        state: state,
        combat: updatedCombat,
        skillSystem: updatedSkillSystem,
        elapsedMs: elapsedMs,
      );
      updatedCombat = combatResult.combat;
      updatedSkillSystem = combatResult.skillSystem;
      if (combatResult.potionInventory != null) {
        updatedPotionInventory = combatResult.potionInventory!;
      }

      // 플레이어 사망 체크
      if (!updatedCombat.playerStats.isAlive) {
        final monsterName = updatedCombat.monsterStats.name;
        nextState = _deathHandler.processPlayerDeath(
          state,
          killerName: monsterName,
          cause: DeathCause.monster,
        );
        return ProgressTickResult(state: nextState, playerDied: true);
      }
    }

    progress = progress.copyWith(
      task: progress.task.copyWith(position: newTaskPos),
      currentCombat: updatedCombat,
    );
    nextState = _recalculateEncumbrance(
      state.copyWith(
        progress: progress,
        skillSystem: updatedSkillSystem,
        potionInventory: updatedPotionInventory,
      ),
    );
    return ProgressTickResult(state: nextState);
  }

  /// 킬 태스크 완료 처리 (HP 회복, 전리품, 보스 처리)
  ({
    GameState state,
    ProgressState progress,
    QueueState queue,
    ProgressTickResult? earlyReturn,
  })
  _handleKillTaskCompletion(
    GameState state,
    ProgressState progress,
    QueueState queue,
  ) {
    var nextState = state;

    // 전투 후 HP 회복
    final combat = progress.currentCombat;
    if (combat != null && combat.isActive) {
      final remainingHp = combat.playerStats.hpCurrent;
      final maxHp = combat.playerStats.hpMax;
      final conBonus = nextState.stats.con ~/ 2;
      var healAmount = (maxHp * 0.5).round() + conBonus;

      final klass = ClassData.findById(nextState.traits.classId);
      if (klass != null) {
        final postCombatHealRate = klass.getPassiveValue(
          ClassPassiveType.postCombatHeal,
        );
        if (postCombatHealRate > 0) {
          healAmount += (maxHp * postCombatHealRate).round();
        }
      }

      final newHp = (remainingHp + healAmount).clamp(0, maxHp);
      nextState = nextState.copyWith(
        stats: nextState.stats.copyWith(hpCurrent: newHp),
      );
    }

    // 전리품 획득
    final lootResult = _lootHandler.winLoot(nextState);
    nextState = lootResult.state;

    // 물약 드랍 로그 추가
    var combatForReset = progress.currentCombat;
    if (lootResult.droppedPotion != null && combatForReset != null) {
      final potionDropEvent = CombatEvent.potionDrop(
        timestamp: nextState.skillSystem.elapsedMs,
        potionName: lootResult.droppedPotion!.name,
        isHp: lootResult.droppedPotion!.isHpPotion,
      );
      final updatedEvents = [...combatForReset.recentEvents, potionDropEvent];
      combatForReset = combatForReset.copyWith(
        recentEvents: updatedEvents.length > 10
            ? updatedEvents.sublist(updatedEvents.length - 10)
            : updatedEvents,
      );
      progress = progress.copyWith(currentCombat: combatForReset);
    }

    // Boss 승리 처리
    if (progress.pendingActCompletion) {
      final cinematicEntries = pq_logic.interplotCinematic(
        config,
        nextState.rng,
        nextState.traits.level,
        progress.plotStageCount,
      );
      queue = QueueState(entries: [...queue.entries, ...cinematicEntries]);
      progress = progress.copyWith(
        currentCombat: null,
        monstersKilled: progress.monstersKilled + 1,
        pendingActCompletion: false,
      );
    } else {
      progress = progress.copyWith(
        currentCombat: null,
        monstersKilled: progress.monstersKilled + 1,
      );
    }

    nextState = nextState.copyWith(progress: progress, queue: queue);

    // 최종 보스 처치 체크
    if (progress.finalBossState == FinalBossState.fighting) {
      progress = progress.copyWith(finalBossState: FinalBossState.defeated);
      nextState = nextState.copyWith(progress: progress);
      final actResult = completeAct(nextState);
      return (
        state: actResult.state,
        progress: actResult.state.progress,
        queue: actResult.state.queue,
        earlyReturn: ProgressTickResult(
          state: actResult.state,
          completedAct: true,
          gameComplete: true,
        ),
      );
    }

    return (
      state: nextState,
      progress: progress,
      queue: queue,
      earlyReturn: null,
    );
  }

  /// 시장/판매/구매 태스크 완료 처리
  ({
    GameState state,
    ProgressState progress,
    QueueState queue,
    ProgressTickResult? earlyReturn,
  })
  _handleMarketTaskCompletion(
    GameState state,
    ProgressState progress,
    QueueState queue,
  ) {
    var nextState = state;
    final marketService = MarketService(rng: state.rng);
    final taskType = progress.currentTask.type;

    if (taskType == TaskType.buying) {
      nextState = marketService.completeBuying(nextState);
      progress = nextState.progress;
    } else if (taskType == TaskType.market || taskType == TaskType.sell) {
      final sellResult = marketService.processSell(nextState);
      nextState = sellResult.state;
      progress = nextState.progress;
      queue = nextState.queue;

      if (sellResult.continuesSelling) {
        nextState = _recalculateEncumbrance(
          nextState.copyWith(progress: progress, queue: queue),
        );
        return (
          state: nextState,
          progress: progress,
          queue: queue,
          earlyReturn: ProgressTickResult(state: nextState),
        );
      }
    }

    return (
      state: nextState,
      progress: progress,
      queue: queue,
      earlyReturn: null,
    );
  }

  /// 경험치 획득 및 레벨업 처리
  ({GameState state, ProgressState progress, bool leveledUp}) _handleExpGain(
    GameState state,
    ProgressState progress,
    int monsterExpReward,
  ) {
    var nextState = state;
    var leveledUp = false;

    final race = RaceData.findById(nextState.traits.raceId);
    final expMultiplier = race?.expMultiplier ?? 1.0;
    final adjustedExp = (monsterExpReward * expMultiplier).round();
    final newExpPos = progress.exp.position + adjustedExp;

    if (newExpPos >= progress.exp.max) {
      final overflowExp = newExpPos - progress.exp.max;
      nextState = _levelUp(nextState);
      leveledUp = true;
      progress = nextState.progress;

      if (overflowExp > 0 && nextState.traits.level < 100) {
        progress = progress.copyWith(
          exp: progress.exp.copyWith(position: overflowExp),
        );
      }
    } else {
      progress = progress.copyWith(
        exp: progress.exp.copyWith(position: newExpPos),
      );
    }

    return (state: nextState, progress: progress, leveledUp: leveledUp);
  }

  /// 퀘스트 진행 처리
  ({GameState state, ProgressState progress, QueueState queue, bool completed})
  _handleQuestProgress(
    GameState state,
    ProgressState progress,
    QueueState queue,
    bool gain,
    int incrementSeconds,
  ) {
    var nextState = state;
    var questDone = false;

    final canQuestProgress =
        gain &&
        progress.plotStageCount > 1 &&
        progress.questCount > 0 &&
        progress.quest.max > 0;

    if (canQuestProgress) {
      if (progress.quest.position + incrementSeconds >= progress.quest.max) {
        nextState = completeQuest(nextState);
        questDone = true;
        progress = nextState.progress;
        queue = nextState.queue;
      } else {
        progress = progress.copyWith(
          quest: progress.quest.copyWith(
            position: progress.quest.position + incrementSeconds,
          ),
        );
      }
    }

    return (
      state: nextState,
      progress: progress,
      queue: queue,
      completed: questDone,
    );
  }

  /// 플롯 진행 및 Act Boss 소환 처리
  ProgressState _handlePlotProgress(
    GameState state,
    ProgressState progress,
    bool gain,
    int incrementSeconds,
  ) {
    if (gain &&
        progress.plot.max > 0 &&
        progress.plot.position >= progress.plot.max &&
        !progress.pendingActCompletion) {
      final actProgressionService = ActProgressionService(config: config);
      final actBoss = actProgressionService.createActBoss(state);
      return progress.copyWith(
        plot: progress.plot.copyWith(position: 0),
        currentCombat: actBoss,
        pendingActCompletion: true,
      );
    } else if (progress.currentTask.type != TaskType.load &&
        progress.plot.max > 0 &&
        !progress.pendingActCompletion) {
      final uncappedPlot = progress.plot.position + incrementSeconds;
      final int newPlotPos = uncappedPlot > progress.plot.max
          ? progress.plot.max
          : uncappedPlot;
      return progress.copyWith(
        plot: progress.plot.copyWith(position: newPlotPos),
      );
    }
    return progress;
  }

  /// 태스크 디큐 및 생성 처리
  ({
    GameState state,
    ProgressState progress,
    QueueState queue,
    bool actDone,
    bool gameComplete,
  })
  _handleTaskDequeue(
    GameState state,
    ProgressState progress,
    QueueState queue,
  ) {
    var nextState = state;
    var actDone = false;
    var gameComplete = false;

    final dq = pq_logic.dequeue(progress, queue);
    if (dq != null) {
      progress = dq.progress.copyWith(
        currentTask: TaskInfo(caption: dq.caption, type: dq.taskType),
      );
      queue = dq.queue;

      if (dq.kind == QueueKind.plot) {
        nextState = nextState.copyWith(progress: progress, queue: queue);
        final actResult = completeAct(nextState);
        nextState = actResult.state;
        actDone = true;
        gameComplete = actResult.gameComplete;
        progress = nextState.progress;
        queue = nextState.queue;
      }
    } else {
      nextState = nextState.copyWith(progress: progress, queue: queue);
      final newTaskResult = _taskGenerator.generateNextTask(nextState);
      progress = newTaskResult.progress;
      queue = newTaskResult.queue;
    }

    return (
      state: nextState,
      progress: progress,
      queue: queue,
      actDone: actDone,
      gameComplete: gameComplete,
    );
  }

  /// Advances quest completion, applies reward, and enqueues next quest task.
  GameState completeQuest(GameState state) {
    final result = pq_logic.completeQuest(
      config,
      state.rng,
      state.traits.level,
    );

    var nextState = _applyReward(state, result.reward);
    final questCount = nextState.progress.questCount + 1;

    // 퀘스트 히스토리 업데이트: 이전 퀘스트 완료 표시, 새 퀘스트 추가
    final updatedQuestHistory = [
      ...nextState.progress.questHistory.map(
        (e) => e.isComplete ? e : e.copyWith(isComplete: true),
      ),
      HistoryEntry(caption: result.caption, isComplete: false),
    ];

    // 퀘스트 몬스터 정보 저장 (Exterminate 타입용)
    // 원본 fQuest.Caption = monsterData, fQuest.Tag = monsterIndex
    final questMonster = result.monsterIndex != null
        ? QuestMonsterInfo(
            monsterData: result.monsterName!,
            monsterIndex: result.monsterIndex!,
          )
        : null;

    // Append quest entry to queue (task kind).
    final updatedQueue = QueueState(
      entries: [
        ...nextState.queue.entries,
        QueueEntry(
          kind: QueueKind.task,
          durationMillis: 50 + nextState.rng.nextInt(100),
          caption: result.caption,
          taskType: TaskType.neutral,
        ),
      ],
    );

    // Update quest progress bar with reset position.
    final progress = nextState.progress.copyWith(
      quest: ProgressBarState(
        position: 0,
        max: 50 + nextState.rng.nextInt(100),
      ),
      questCount: questCount,
      questHistory: updatedQuestHistory,
      currentQuestMonster: questMonster,
    );

    return _recalculateEncumbrance(
      nextState.copyWith(progress: progress, queue: updatedQueue),
    );
  }

  /// Advances plot to next act and applies any act-level rewards.
  /// Returns gameComplete=true if Final Boss was defeated (game ends).
  ({GameState state, bool gameComplete}) completeAct(GameState state) {
    final actProgressionService = ActProgressionService(config: config);

    // Act 보상 먼저 적용
    final actRewards = actProgressionService.getActRewards(
      state.progress.plotStageCount,
    );
    var nextState = state;
    for (final reward in actRewards) {
      nextState = _applyReward(nextState, reward);
    }

    // Act 완료 처리 (ActProgressionService 위임)
    final result = actProgressionService.completeAct(nextState);

    return (
      state: _recalculateEncumbrance(result.state),
      gameComplete: result.gameComplete,
    );
  }

  /// Developer-only cheat hooks for quickly finishing bars.
  GameState forceTaskComplete(GameState state) {
    final progress = state.progress.copyWith(
      task: state.progress.task.copyWith(position: state.progress.task.max),
    );
    return state.copyWith(progress: progress);
  }

  GameState forceQuestComplete(GameState state) {
    final progress = state.progress.copyWith(
      task: state.progress.task.copyWith(position: state.progress.task.max),
      quest: state.progress.quest.copyWith(position: state.progress.quest.max),
    );
    return state.copyWith(progress: progress);
  }

  GameState forcePlotComplete(GameState state) {
    // 다음 Act의 최소 몬스터 레벨까지 레벨업
    final nextPlotStage = state.progress.plotStageCount + 1;
    final targetLevel = ActMonsterLevel.forPlotStage(nextPlotStage);
    var nextState = state;

    // 현재 레벨이 목표 레벨보다 낮으면 레벨업 (최대 100레벨)
    while (nextState.traits.level < targetLevel &&
        nextState.traits.level < 100) {
      nextState = _levelUp(nextState);
    }

    // 모든 장비 슬롯을 목표 레벨에 맞는 장비로 교체 (전투 보상 드랍 공식 사용)
    final equipLevel = nextState.traits.level;
    for (var slotIndex = 0; slotIndex < Equipment.slotCount; slotIndex++) {
      nextState = mutations.winEquipByIndex(nextState, equipLevel, slotIndex);
    }

    // 태스크 바 완료 처리
    var progress = nextState.progress.copyWith(
      task: nextState.progress.task.copyWith(
        position: nextState.progress.task.max,
      ),
    );
    nextState = nextState.copyWith(progress: progress);

    // 디버그 모드에서는 completeAct 직접 호출하여 plotStageCount 즉시 업데이트
    // 시네마틱은 생략하고 바로 다음 Act로 진입
    final actResult = completeAct(nextState);
    return actResult.state;
  }

  GameState _applyReward(GameState state, pq_logic.RewardKind reward) {
    final updated = rewards.applyReward(state, reward);
    return _recalculateEncumbrance(updated);
  }

  GameState _levelUp(GameState state) {
    // 최대 레벨(100) 안전장치: 이미 100레벨이면 레벨업하지 않음
    if (state.traits.level >= 100) {
      return state;
    }

    final nextLevel = state.traits.level + 1;
    final rng = state.rng;

    // HP/MP 증가량 (PlayerScaling 기반 + 랜덤 변동)
    // 기존: CON/3 + 1 + random(0-3) → ~6-9 HP/레벨 (너무 낮음)
    // 신규: 18 + CON/5 + random(0-4) → ~20-25 HP/레벨 (생존율 개선)
    final hpGain = 18 + state.stats.con ~/ 5 + rng.nextInt(5);
    final mpGain = 6 + state.stats.intelligence ~/ 5 + rng.nextInt(3);

    var nextState = state.copyWith(
      traits: state.traits.copyWith(level: nextLevel),
      stats: state.stats.copyWith(
        hpMax: state.stats.hpMax + hpGain,
        mpMax: state.stats.mpMax + mpGain,
      ),
    );

    // Win two stats and a spell, matching the original leveling rules.
    nextState = mutations.winStat(nextState);
    nextState = mutations.winStat(nextState);
    nextState = mutations.winSpell(nextState, nextState.stats.wis, nextLevel);

    final expBar = ProgressBarState(
      position: 0,
      max: ExpConstants.requiredExp(nextLevel),
    );
    final progress = nextState.progress.copyWith(exp: expBar);
    nextState = nextState.copyWith(progress: progress);
    return _recalculateEncumbrance(nextState);
  }

  GameState _recalculateEncumbrance(GameState state) {
    // items에는 Gold가 포함되지 않음 (inventory.gold 필드로 관리)
    final encumValue = state.inventory.items.fold<int>(
      0,
      (sum, item) => sum + item.count,
    );
    final encumMax = 10 + state.stats.str;
    final encumBar = state.progress.encumbrance.copyWith(
      position: encumValue,
      max: encumMax,
    );
    final progress = state.progress.copyWith(encumbrance: encumBar);
    return state.copyWith(progress: progress);
  }
}
