import 'dart:math' as math;

import 'package:askiineverdie/data/game_text_l10n.dart' as l10n;
import 'package:askiineverdie/data/skill_data.dart';
import 'package:askiineverdie/src/core/engine/combat_calculator.dart';
import 'package:askiineverdie/src/core/engine/game_mutations.dart';
import 'package:askiineverdie/src/core/engine/potion_service.dart';
import 'package:askiineverdie/src/core/engine/reward_service.dart';
import 'package:askiineverdie/src/core/engine/skill_service.dart';
import 'package:askiineverdie/src/core/model/combat_event.dart';
import 'package:askiineverdie/src/core/model/combat_state.dart';
import 'package:askiineverdie/src/core/model/combat_stats.dart';
import 'package:askiineverdie/src/core/model/equipment_item.dart';
import 'package:askiineverdie/src/core/model/equipment_slot.dart';
import 'package:askiineverdie/src/core/model/game_state.dart';
import 'package:askiineverdie/src/core/model/monster_combat_stats.dart';
import 'package:askiineverdie/src/core/model/potion.dart';
import 'package:askiineverdie/src/core/model/pq_config.dart';
import 'package:askiineverdie/src/core/model/skill.dart';
import 'package:askiineverdie/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,
  });

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

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

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

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

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

  /// 새 게임 초기화 (원본 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: pq_logic.levelUpTime(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) {
    // 500ms 제한: 5x 배속 (50ms * 5 = 250ms) + 여유 공간
    // 원본은 100ms 제한이었으나 배속 기능 지원을 위해 확장
    final int clamped = elapsedMillis.clamp(0, 500).toInt();
    var progress = state.progress;
    var queue = state.queue;
    var nextState = state;
    var leveledUp = false;
    var questDone = false;
    var actDone = false;

    // 스킬 시스템 시간 업데이트 (Phase 3)
    final skillService = SkillService(rng: state.rng);
    var skillSystem = skillService.updateElapsedTime(
      state.skillSystem,
      clamped,
    );

    // 만료된 버프 정리
    skillSystem = skillService.cleanupExpiredBuffs(skillSystem);

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

    if (!isInCombat && nextState.stats.mp < nextState.stats.mpMax) {
      final mpRegen = skillService.calculateMpRegen(
        elapsedMs: clamped,
        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),
        );
      }
    }

    nextState = nextState.copyWith(skillSystem: skillSystem);

    // Advance task bar if still running.
    if (progress.task.position < progress.task.max) {
      final uncapped = progress.task.position + clamped;
      final int newTaskPos = uncapped > progress.task.max
          ? progress.task.max
          : uncapped;

      // 킬 태스크 중 전투 진행 (스킬 자동 사용, DOT, 물약 포함)
      var updatedCombat = progress.currentCombat;
      var updatedSkillSystem = nextState.skillSystem;
      var updatedPotionInventory = nextState.potionInventory;
      if (progress.currentTask.type == TaskType.kill &&
          updatedCombat != null &&
          updatedCombat.isActive) {
        final combatResult = _processCombatTickWithSkills(
          nextState,
          updatedCombat,
          updatedSkillSystem,
          clamped,
        );
        updatedCombat = combatResult.combat;
        updatedSkillSystem = combatResult.skillSystem;
        if (combatResult.potionInventory != null) {
          updatedPotionInventory = combatResult.potionInventory!;
        }

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

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

    final gain = progress.currentTask.type == TaskType.kill;
    final incrementSeconds = progress.task.max ~/ 1000;

    // 킬 태스크 완료 시 전투 결과 반영 및 전리품 획득
    if (gain) {
      // 전투 결과에 따라 플레이어 HP 업데이트 + 전투 후 회복
      final combat = progress.currentCombat;
      if (combat != null && combat.isActive) {
        // 전투 중 남은 HP
        final remainingHp = combat.playerStats.hpCurrent;
        final maxHp = combat.playerStats.hpMax;

        // 전투 승리 시 HP 회복 (50% + CON/2)
        // 아이들 게임 특성상 전투 사이 HP가 회복되어야 지속 플레이 가능
        final conBonus = nextState.stats.con ~/ 2;
        final healAmount = (maxHp * 0.5).round() + conBonus;
        final newHp = (remainingHp + healAmount).clamp(0, maxHp);

        nextState = nextState.copyWith(
          stats: nextState.stats.copyWith(hpCurrent: newHp),
        );
      }

      // 전리품 획득 (원본 Main.pas:625-630)
      final lootResult = _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);
      }

      // 전투 상태 초기화, 몬스터 처치 수 증가 및 물약 사용 기록 초기화
      progress = progress.copyWith(
        currentCombat: null,
        monstersKilled: progress.monstersKilled + 1,
      );
      final resetPotionInventory = nextState.potionInventory.resetBattleUsage();
      nextState = nextState.copyWith(
        progress: progress,
        potionInventory: resetPotionInventory,
      );
    }

    // 시장/판매/구매 태스크 완료 시 처리 (원본 Main.pas:631-649)
    final taskType = progress.currentTask.type;
    if (taskType == TaskType.buying) {
      // 장비 구매 완료 (원본 631-634)
      nextState = _completeBuying(nextState);
      progress = nextState.progress;
    } else if (taskType == TaskType.market || taskType == TaskType.sell) {
      // 시장 도착 또는 판매 완료 (원본 635-649)
      final sellResult = _processSell(nextState);
      nextState = sellResult.state;
      progress = nextState.progress;
      queue = nextState.queue;

      // 판매 중이면 다른 로직 건너뛰기
      if (sellResult.continuesSelling) {
        nextState = _recalculateEncumbrance(
          nextState.copyWith(progress: progress, queue: queue),
        );
        return ProgressTickResult(
          state: nextState,
          leveledUp: false,
          completedQuest: false,
          completedAct: false,
        );
      }
    }

    // Gain XP / level up.
    if (gain) {
      if (progress.exp.position >= progress.exp.max) {
        nextState = _levelUp(nextState);
        leveledUp = true;
        progress = nextState.progress;
      } else {
        final uncappedExp = progress.exp.position + incrementSeconds;
        final int newExpPos = uncappedExp > progress.exp.max
            ? progress.exp.max
            : uncappedExp;
        progress = progress.copyWith(
          exp: progress.exp.copyWith(position: newExpPos),
        );
      }
    }

    // Advance quest bar after Act I.
    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,
          ),
        );
      }
    }

    // 플롯(plot) 바가 완료되면 InterplotCinematic 트리거
    // (원본 Main.pas:1301-1304)
    if (gain &&
        progress.plot.max > 0 &&
        progress.plot.position >= progress.plot.max) {
      // InterplotCinematic을 호출하여 시네마틱 이벤트 큐에 추가
      final cinematicEntries = pq_logic.interplotCinematic(
        config,
        nextState.rng,
        nextState.traits.level,
        nextState.progress.plotStageCount,
      );
      queue = QueueState(entries: [...queue.entries, ...cinematicEntries]);
      // 플롯 바를 0으로 리셋하지 않음 - completeAct에서 처리됨
    } else if (progress.currentTask.type != TaskType.load &&
        progress.plot.max > 0) {
      final uncappedPlot = progress.plot.position + incrementSeconds;
      final int newPlotPos = uncappedPlot > progress.plot.max
          ? progress.plot.max
          : uncappedPlot;
      progress = progress.copyWith(
        plot: progress.plot.copyWith(position: newPlotPos),
      );
    }

    // Dequeue next scripted task if available.
    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;

      // plot 타입이 dequeue 되면 completeAct 실행 (원본 Main.pas 로직)
      if (dq.kind == QueueKind.plot) {
        nextState = nextState.copyWith(progress: progress, queue: queue);
        nextState = completeAct(nextState);
        actDone = true;
        progress = nextState.progress;
        queue = nextState.queue;
      }
    } else {
      // 큐가 비어있으면 새 태스크 생성 (원본 Dequeue 667-684줄)
      nextState = nextState.copyWith(progress: progress, queue: queue);
      final newTaskResult = _generateNextTask(nextState);
      progress = newTaskResult.progress;
      queue = newTaskResult.queue;
    }

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

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

  /// 큐가 비어있을 때 다음 태스크 생성 (원본 Dequeue 667-684줄)
  ({ProgressState progress, QueueState queue}) _generateNextTask(
    GameState state,
  ) {
    var progress = state.progress;
    final queue = state.queue;
    final oldTaskType = progress.currentTask.type;

    // 1. Encumbrance가 가득 찼으면 시장으로 이동 (원본 667-669줄)
    if (progress.encumbrance.position >= progress.encumbrance.max &&
        progress.encumbrance.max > 0) {
      final taskResult = pq_logic.startTask(
        progress,
        l10n.taskHeadingToMarket(),
        4 * 1000,
      );
      progress = taskResult.progress.copyWith(
        currentTask: TaskInfo(
          caption: taskResult.caption,
          type: TaskType.market,
        ),
      );
      return (progress: progress, queue: queue);
    }

    // 2. kill 태스크가 아니었고 heading도 아니면 heading 또는 buying 태스크 실행
    // (원본 670-677줄)
    if (oldTaskType != TaskType.kill && oldTaskType != TaskType.neutral) {
      // Gold가 충분하면 장비 구매 (원본 671-673줄)
      final gold = _getGold(state);
      final equipPrice = _equipPrice(state.traits.level);
      if (gold > equipPrice) {
        final taskResult = pq_logic.startTask(
          progress,
          l10n.taskUpgradingHardware(),
          5 * 1000,
        );
        progress = taskResult.progress.copyWith(
          currentTask: TaskInfo(
            caption: taskResult.caption,
            type: TaskType.buying,
          ),
        );
        return (progress: progress, queue: queue);
      }

      // Gold가 부족하면 전장으로 이동 (원본 674-676줄)
      final taskResult = pq_logic.startTask(
        progress,
        l10n.taskEnteringDebugZone(),
        4 * 1000,
      );
      progress = taskResult.progress.copyWith(
        currentTask: TaskInfo(
          caption: taskResult.caption,
          type: TaskType.neutral,
        ),
      );
      return (progress: progress, queue: queue);
    }

    // 3. MonsterTask 실행 (원본 678-684줄)
    final level = state.traits.level;

    // 원본 Main.pas:548-551: 25% 확률로 Quest Monster 사용
    // fQuest.Caption이 비어있지 않으면 해당 몬스터 데이터 전달
    final questMonster = state.progress.currentQuestMonster;
    final questMonsterData = questMonster?.monsterData;
    final questLevel = questMonsterData != null
        ? int.tryParse(questMonsterData.split('|').elementAtOrNull(1) ?? '') ??
              0
        : null;

    final monsterResult = pq_logic.monsterTask(
      config,
      state.rng,
      level,
      questMonsterData,
      questLevel,
    );

    // 전투 스탯 생성
    final playerCombatStats = CombatStats.fromStats(
      stats: state.stats,
      equipment: state.equipment,
      level: level,
    );

    // 전투용 몬스터 레벨 조정 (밸런스)
    // config의 raw 레벨이 플레이어보다 너무 높으면 전투가 불가능
    // 플레이어 레벨 ±3 범위로 제한 (최소 1)
    final effectiveMonsterLevel = monsterResult.level
        .clamp(math.max(1, level - 3), level + 3)
        .toInt();

    final monsterCombatStats = MonsterCombatStats.fromLevel(
      name: monsterResult.displayName,
      level: effectiveMonsterLevel,
      speedType: MonsterCombatStats.inferSpeedType(monsterResult.baseName),
    );

    // 전투 상태 초기화
    final combatState = CombatState.start(
      playerStats: playerCombatStats,
      monsterStats: monsterCombatStats,
    );

    // 태스크 지속시간 계산 (CombatCalculator 기반)
    final combatCalculator = CombatCalculator(rng: state.rng);
    final durationMillis = combatCalculator.estimateCombatDurationMs(
      player: playerCombatStats,
      monster: monsterCombatStats,
    );

    final taskResult = pq_logic.startTask(
      progress,
      l10n.taskDebugging(monsterResult.displayName),
      durationMillis,
    );

    progress = taskResult.progress.copyWith(
      currentTask: TaskInfo(
        caption: taskResult.caption,
        type: TaskType.kill,
        monsterBaseName: monsterResult.baseName,
        monsterPart: monsterResult.part,
        monsterLevel: monsterResult.level,
      ),
      currentCombat: combatState,
    );

    return (progress: progress, queue: queue);
  }

  /// 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.
  GameState completeAct(GameState state) {
    final actResult = pq_logic.completeAct(state.progress.plotStageCount);
    var nextState = state;
    for (final reward in actResult.rewards) {
      nextState = _applyReward(nextState, reward);
    }

    final plotStages = nextState.progress.plotStageCount + 1;

    // 플롯 히스토리 업데이트: 이전 플롯 완료 표시, 새 플롯 추가
    final updatedPlotHistory = [
      ...nextState.progress.plotHistory.map(
        (e) => e.isComplete ? e : e.copyWith(isComplete: true),
      ),
      HistoryEntry(caption: actResult.actTitle, isComplete: false),
    ];

    var updatedProgress = nextState.progress.copyWith(
      plot: ProgressBarState(position: 0, max: actResult.plotBarMaxSeconds),
      plotStageCount: plotStages,
      plotHistory: updatedPlotHistory,
    );

    nextState = nextState.copyWith(progress: updatedProgress);

    // Act I 완료 후(Prologue -> Act I) 첫 퀘스트 시작 (원본 Main.pas 로직)
    // plotStages == 2는 Prologue(1) -> Act I(2) 전환을 의미
    if (plotStages == 2) {
      nextState = _startFirstQuest(nextState);
    }

    return _recalculateEncumbrance(nextState);
  }

  /// 첫 퀘스트 시작 (Act I 시작 시)
  GameState _startFirstQuest(GameState state) {
    final result = pq_logic.completeQuest(
      config,
      state.rng,
      state.traits.level,
    );

    // 퀘스트 바 초기화
    final questBar = ProgressBarState(
      position: 0,
      max: 50 + state.rng.nextInt(100),
    );

    // 첫 퀘스트 히스토리 추가
    final questHistory = [
      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;

    // 첫 퀘스트 추가
    final updatedQueue = QueueState(
      entries: [
        ...state.queue.entries,
        QueueEntry(
          kind: QueueKind.task,
          durationMillis: 50 + state.rng.nextInt(100),
          caption: result.caption,
          taskType: TaskType.neutral,
        ),
      ],
    );

    final progress = state.progress.copyWith(
      quest: questBar,
      questCount: 1,
      questHistory: questHistory,
      currentQuestMonster: questMonster,
    );

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

  /// 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) {
    final progress = state.progress.copyWith(
      task: state.progress.task.copyWith(position: state.progress.task.max),
      plot: state.progress.plot.copyWith(position: state.progress.plot.max),
    );
    return state.copyWith(progress: progress);
  }

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

  GameState _levelUp(GameState state) {
    final nextLevel = state.traits.level + 1;
    final rng = state.rng;
    final hpGain = state.stats.con ~/ 3 + 1 + rng.nextInt(4);
    final mpGain = state.stats.intelligence ~/ 3 + 1 + rng.nextInt(4);

    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: pq_logic.levelUpTime(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);
  }

  /// 킬 태스크 완료 시 전리품 획득 (원본 Main.pas:625-630)
  /// 전리품 획득 결과
  ///
  /// [state] 업데이트된 게임 상태
  /// [droppedPotion] 드랍된 물약 (없으면 null)
  ({GameState state, Potion? droppedPotion}) _winLoot(GameState state) {
    final taskInfo = state.progress.currentTask;
    final monsterPart = taskInfo.monsterPart ?? '';
    final monsterBaseName = taskInfo.monsterBaseName ?? '';

    var resultState = state;

    // 부위가 '*'이면 WinItem 호출 (특수 아이템)
    if (monsterPart == '*') {
      resultState = mutations.winItem(resultState);
    } else if (monsterPart.isNotEmpty && monsterBaseName.isNotEmpty) {
      // 원본: Add(Inventory, LowerCase(Split(fTask.Caption,1) + ' ' +
      //       ProperCase(Split(fTask.Caption,3))), 1);
      // 예: "goblin Claw" 형태로 인벤토리 추가
      final itemName =
          '${monsterBaseName.toLowerCase()} ${_properCase(monsterPart)}';

      // 인벤토리에 추가
      final items = [...resultState.inventory.items];
      final existing = items.indexWhere((e) => e.name == itemName);
      if (existing >= 0) {
        items[existing] = items[existing].copyWith(
          count: items[existing].count + 1,
        );
      } else {
        items.add(InventoryEntry(name: itemName, count: 1));
      }

      resultState = resultState.copyWith(
        inventory: resultState.inventory.copyWith(items: items),
      );
    }

    // 물약 드랍 시도
    final potionService = const PotionService();
    final rng = resultState.rng;
    final (updatedPotionInventory, droppedPotion) = potionService.tryPotionDrop(
      playerLevel: resultState.traits.level,
      inventory: resultState.potionInventory,
      roll: rng.nextInt(100),
      typeRoll: rng.nextInt(100),
    );

    return (
      state: resultState.copyWith(
        rng: rng,
        potionInventory: updatedPotionInventory,
      ),
      droppedPotion: droppedPotion,
    );
  }

  /// 첫 글자만 대문자로 변환 (원본 ProperCase)
  String _properCase(String s) {
    if (s.isEmpty) return s;
    return s[0].toUpperCase() + s.substring(1);
  }

  /// 인벤토리에서 Gold 수량 반환
  int _getGold(GameState state) {
    return state.inventory.gold;
  }

  /// 장비 가격 계산 (원본 Main.pas:612-616)
  /// Result := 5 * Level^2 + 10 * Level + 20
  int _equipPrice(int level) {
    return 5 * level * level + 10 * level + 20;
  }

  /// 장비 구매 완료 처리 (원본 Main.pas:631-634)
  GameState _completeBuying(GameState state) {
    final level = state.traits.level;
    final price = _equipPrice(level);

    // Gold 차감 (inventory.gold 필드 사용)
    final newGold = math.max(0, state.inventory.gold - price);
    var nextState = state.copyWith(
      inventory: state.inventory.copyWith(gold: newGold),
    );

    // 장비 획득 (WinEquip)
    // 원본 Main.pas:797 - posn := Random(Equips.Items.Count); (11개 슬롯)
    final slotIndex = nextState.rng.nextInt(Equipment.slotCount);
    nextState = mutations.winEquipByIndex(nextState, level, slotIndex);

    // 물약 자동 구매 (남은 골드의 20% 사용)
    final potionService = const PotionService();
    final purchaseResult = potionService.autoPurchasePotions(
      playerLevel: level,
      inventory: nextState.potionInventory,
      gold: nextState.inventory.gold,
      spendRatio: 0.20,
    );

    if (purchaseResult.success && purchaseResult.newInventory != null) {
      nextState = nextState.copyWith(
        inventory: nextState.inventory.copyWith(gold: purchaseResult.newGold),
        potionInventory: purchaseResult.newInventory,
      );
    }

    return nextState;
  }

  /// 판매 처리 결과
  ({GameState state, bool continuesSelling}) _processSell(GameState state) {
    final taskType = state.progress.currentTask.type;
    var items = [...state.inventory.items];
    var goldAmount = state.inventory.gold;

    // sell 태스크 완료 시 아이템 판매 (원본 Main.pas:636-643)
    if (taskType == TaskType.sell) {
      // 첫 번째 아이템 찾기 (items에는 Gold가 없음)
      if (items.isNotEmpty) {
        final item = items.first;
        final level = state.traits.level;

        // 가격 계산: 수량 * 레벨
        var price = item.count * level;

        // " of " 포함 시 보너스 (원본 639-640)
        if (item.name.contains(' of ')) {
          price =
              price *
              (1 + pq_logic.randomLow(state.rng, 10)) *
              (1 + pq_logic.randomLow(state.rng, level));
        }

        // 아이템 삭제
        items.removeAt(0);

        // Gold 추가 (inventory.gold 필드 사용)
        goldAmount += price;
      }
    }

    // 판매할 아이템이 남아있는지 확인
    final hasItemsToSell = items.isNotEmpty;

    if (hasItemsToSell) {
      // 다음 아이템 판매 태스크 시작
      final nextItem = items.first;
      final translatedName = l10n.translateItemNameL10n(nextItem.name);
      final itemDesc = l10n.indefiniteL10n(translatedName, nextItem.count);
      final taskResult = pq_logic.startTask(
        state.progress,
        l10n.taskSelling(itemDesc),
        1 * 1000,
      );
      final progress = taskResult.progress.copyWith(
        currentTask: TaskInfo(caption: taskResult.caption, type: TaskType.sell),
      );
      return (
        state: state.copyWith(
          inventory: state.inventory.copyWith(gold: goldAmount, items: items),
          progress: progress,
        ),
        continuesSelling: true,
      );
    }

    // 판매 완료 - 인벤토리 업데이트만 하고 다음 태스크로
    return (
      state: state.copyWith(
        inventory: state.inventory.copyWith(gold: goldAmount, items: items),
      ),
      continuesSelling: false,
    );
  }

  /// 전투 틱 처리 (스킬 자동 사용, DOT, 물약 포함)
  ///
  /// [state] 현재 게임 상태
  /// [combat] 현재 전투 상태
  /// [skillSystem] 스킬 시스템 상태
  /// [elapsedMs] 경과 시간 (밀리초)
  /// Returns: 업데이트된 전투 상태, 스킬 시스템 상태, 물약 인벤토리
  ({
    CombatState combat,
    SkillSystemState skillSystem,
    PotionInventory? potionInventory,
  })
  _processCombatTickWithSkills(
    GameState state,
    CombatState combat,
    SkillSystemState skillSystem,
    int elapsedMs,
  ) {
    if (!combat.isActive || combat.isCombatOver) {
      return (combat: combat, skillSystem: skillSystem, potionInventory: null);
    }

    final calculator = CombatCalculator(rng: state.rng);
    final skillService = SkillService(rng: state.rng);
    final potionService = const PotionService();
    var playerStats = combat.playerStats;
    var monsterStats = combat.monsterStats;
    var playerAccumulator = combat.playerAttackAccumulatorMs + elapsedMs;
    var monsterAccumulator = combat.monsterAttackAccumulatorMs + elapsedMs;
    var totalDamageDealt = combat.totalDamageDealt;
    var totalDamageTaken = combat.totalDamageTaken;
    var turnsElapsed = combat.turnsElapsed;
    var updatedSkillSystem = skillSystem;
    var activeDoTs = [...combat.activeDoTs];
    var usedPotionTypes = {...combat.usedPotionTypes};
    var activeDebuffs = [...combat.activeDebuffs];
    PotionInventory? updatedPotionInventory;

    // 새 전투 이벤트 수집
    final newEvents = <CombatEvent>[];
    final timestamp = updatedSkillSystem.elapsedMs;

    // =========================================================================
    // 만료된 디버프 정리
    // =========================================================================
    activeDebuffs = activeDebuffs
        .where((debuff) => !debuff.isExpired(timestamp))
        .toList();

    // =========================================================================
    // DOT 틱 처리
    // =========================================================================
    var dotDamageThisTick = 0;
    final updatedDoTs = <DotEffect>[];

    for (final dot in activeDoTs) {
      final (updatedDot, ticksTriggered) = dot.tick(elapsedMs);

      if (ticksTriggered > 0) {
        final damage = dot.damagePerTick * ticksTriggered;
        dotDamageThisTick += damage;

        // DOT 데미지 이벤트 생성 (skillId → name 변환)
        final dotSkillName =
            SkillData.getSkillById(dot.skillId)?.name ?? dot.skillId;
        newEvents.add(
          CombatEvent.dotTick(
            timestamp: timestamp,
            skillName: dotSkillName,
            damage: damage,
            targetName: monsterStats.name,
          ),
        );
      }

      // 만료되지 않은 DOT만 유지
      if (updatedDot.isActive) {
        updatedDoTs.add(updatedDot);
      }
    }

    // DOT 데미지 적용
    if (dotDamageThisTick > 0 && monsterStats.isAlive) {
      final newMonsterHp = (monsterStats.hpCurrent - dotDamageThisTick).clamp(
        0,
        monsterStats.hpMax,
      );
      monsterStats = monsterStats.copyWith(hpCurrent: newMonsterHp);
      totalDamageDealt += dotDamageThisTick;
    }

    activeDoTs = updatedDoTs;

    // =========================================================================
    // 긴급 물약 자동 사용 (HP < 30%)
    // =========================================================================
    final hpRatio = playerStats.hpCurrent / playerStats.hpMax;
    if (hpRatio <= PotionService.emergencyHpThreshold) {
      final emergencyPotion = potionService.selectEmergencyHpPotion(
        currentHp: playerStats.hpCurrent,
        maxHp: playerStats.hpMax,
        inventory: state.potionInventory,
        playerLevel: state.traits.level,
      );

      if (emergencyPotion != null && !usedPotionTypes.contains(PotionType.hp)) {
        final result = potionService.usePotion(
          potionId: emergencyPotion.id,
          inventory: state.potionInventory,
          currentHp: playerStats.hpCurrent,
          maxHp: playerStats.hpMax,
          currentMp: playerStats.mpCurrent,
          maxMp: playerStats.mpMax,
        );

        if (result.success) {
          playerStats = playerStats.copyWith(hpCurrent: result.newHp);
          usedPotionTypes = {...usedPotionTypes, PotionType.hp};
          updatedPotionInventory = result.newInventory;

          newEvents.add(
            CombatEvent.playerPotion(
              timestamp: timestamp,
              potionName: emergencyPotion.name,
              healAmount: result.healedAmount,
              isHp: true,
            ),
          );
        }
      }
    }

    // 플레이어 공격 체크
    if (playerAccumulator >= playerStats.attackDelayMs) {
      // SpellBook에서 사용 가능한 스킬 ID 목록 조회
      var availableSkillIds = skillService.getAvailableSkillIdsFromSpellBook(
        state.spellBook,
      );
      // SpellBook에 스킬이 없으면 기본 스킬 사용
      if (availableSkillIds.isEmpty) {
        availableSkillIds = SkillData.defaultSkillIds;
      }

      final selectedSkill = skillService.selectAutoSkill(
        player: playerStats,
        monster: monsterStats,
        skillSystem: updatedSkillSystem,
        availableSkillIds: availableSkillIds,
        activeDoTs: activeDoTs,
        activeDebuffs: activeDebuffs,
      );

      if (selectedSkill != null && selectedSkill.isAttack) {
        // 스펠 랭크 조회 (SpellBook 기반)
        final spellRank = skillService.getSkillRankFromSpellBook(
          state.spellBook,
          selectedSkill.id,
        );
        // 랭크 스케일링 적용된 공격 스킬 사용
        final skillResult = skillService.useAttackSkillWithRank(
          skill: selectedSkill,
          player: playerStats,
          monster: monsterStats,
          skillSystem: updatedSkillSystem,
          rank: spellRank,
        );
        playerStats = skillResult.updatedPlayer;
        monsterStats = skillResult.updatedMonster;
        totalDamageDealt += skillResult.result.damage;
        updatedSkillSystem = skillResult.updatedSkillSystem;

        // 스킬 공격 이벤트 생성
        newEvents.add(
          CombatEvent.playerSkill(
            timestamp: timestamp,
            skillName: selectedSkill.name,
            damage: skillResult.result.damage,
            targetName: monsterStats.name,
            attackDelayMs: playerStats.attackDelayMs,
          ),
        );
      } else if (selectedSkill != null && selectedSkill.isDot) {
        // DOT 스킬 사용
        final skillResult = skillService.useDotSkill(
          skill: selectedSkill,
          player: playerStats,
          skillSystem: updatedSkillSystem,
          playerInt: state.stats.intelligence,
          playerWis: state.stats.wis,
        );
        playerStats = skillResult.updatedPlayer;
        updatedSkillSystem = skillResult.updatedSkillSystem;

        // DOT 효과 추가
        if (skillResult.dotEffect != null) {
          activeDoTs.add(skillResult.dotEffect!);
        }

        // DOT 스킬 사용 이벤트 생성
        newEvents.add(
          CombatEvent.playerSkill(
            timestamp: timestamp,
            skillName: selectedSkill.name,
            damage: skillResult.result.damage,
            targetName: monsterStats.name,
            attackDelayMs: playerStats.attackDelayMs,
          ),
        );
      } else if (selectedSkill != null && selectedSkill.isHeal) {
        // 회복 스킬 사용
        final skillResult = skillService.useHealSkill(
          skill: selectedSkill,
          player: playerStats,
          skillSystem: updatedSkillSystem,
        );
        playerStats = skillResult.updatedPlayer;
        updatedSkillSystem = skillResult.updatedSkillSystem;

        // 회복 이벤트 생성
        newEvents.add(
          CombatEvent.playerHeal(
            timestamp: timestamp,
            healAmount: skillResult.result.healedAmount,
            skillName: selectedSkill.name,
          ),
        );
      } else if (selectedSkill != null && selectedSkill.isBuff) {
        // 버프 스킬 사용
        final skillResult = skillService.useBuffSkill(
          skill: selectedSkill,
          player: playerStats,
          skillSystem: updatedSkillSystem,
        );
        playerStats = skillResult.updatedPlayer;
        updatedSkillSystem = skillResult.updatedSkillSystem;

        // 버프 이벤트 생성
        newEvents.add(
          CombatEvent.playerBuff(
            timestamp: timestamp,
            skillName: selectedSkill.name,
          ),
        );
      } else if (selectedSkill != null && selectedSkill.isDebuff) {
        // 디버프 스킬 사용
        final skillResult = skillService.useDebuffSkill(
          skill: selectedSkill,
          player: playerStats,
          skillSystem: updatedSkillSystem,
          currentDebuffs: activeDebuffs,
        );
        playerStats = skillResult.updatedPlayer;
        updatedSkillSystem = skillResult.updatedSkillSystem;

        // 디버프 효과 추가 (기존 같은 디버프 제거 후)
        if (skillResult.debuffEffect != null) {
          activeDebuffs = activeDebuffs
              .where((d) => d.effect.id != skillResult.debuffEffect!.effect.id)
              .toList()
            ..add(skillResult.debuffEffect!);
        }

        // 디버프 이벤트 생성
        newEvents.add(
          CombatEvent.playerDebuff(
            timestamp: timestamp,
            skillName: selectedSkill.name,
            targetName: monsterStats.name,
          ),
        );
      } else {
        // 일반 공격
        final attackResult = calculator.playerAttackMonster(
          attacker: playerStats,
          defender: monsterStats,
        );
        monsterStats = attackResult.updatedDefender;
        totalDamageDealt += attackResult.result.damage;

        // 일반 공격 이벤트 생성
        final result = attackResult.result;
        if (result.isEvaded) {
          newEvents.add(
            CombatEvent.monsterEvade(
              timestamp: timestamp,
              targetName: monsterStats.name,
            ),
          );
        } else {
          newEvents.add(
            CombatEvent.playerAttack(
              timestamp: timestamp,
              damage: result.damage,
              targetName: monsterStats.name,
              isCritical: result.isCritical,
              attackDelayMs: playerStats.attackDelayMs,
            ),
          );
        }
      }

      playerAccumulator -= playerStats.attackDelayMs;
      turnsElapsed++;
    }

    // 몬스터가 살아있으면 반격
    if (monsterStats.isAlive &&
        monsterAccumulator >= monsterStats.attackDelayMs) {
      // 디버프 효과 적용된 몬스터 스탯 계산
      var debuffedMonster = monsterStats;
      if (activeDebuffs.isNotEmpty) {
        double atkMod = 0;
        for (final debuff in activeDebuffs) {
          if (!debuff.isExpired(timestamp)) {
            atkMod += debuff.effect.atkModifier; // 음수 값
          }
        }
        // ATK 감소 적용 (최소 10% ATK 유지)
        final newAtk = (monsterStats.atk * (1 + atkMod)).round().clamp(
          monsterStats.atk ~/ 10,
          monsterStats.atk,
        );
        debuffedMonster = monsterStats.copyWith(atk: newAtk);
      }

      final attackResult = calculator.monsterAttackPlayer(
        attacker: debuffedMonster,
        defender: playerStats,
      );
      playerStats = attackResult.updatedDefender;
      totalDamageTaken += attackResult.result.damage;
      monsterAccumulator -= monsterStats.attackDelayMs;

      // 몬스터 공격 이벤트 생성
      final result = attackResult.result;
      if (result.isEvaded) {
        newEvents.add(
          CombatEvent.playerEvade(
            timestamp: timestamp,
            attackerName: monsterStats.name,
          ),
        );
      } else if (result.isBlocked) {
        newEvents.add(
          CombatEvent.playerBlock(
            timestamp: timestamp,
            reducedDamage: result.damage,
            attackerName: monsterStats.name,
          ),
        );
      } else if (result.isParried) {
        newEvents.add(
          CombatEvent.playerParry(
            timestamp: timestamp,
            reducedDamage: result.damage,
            attackerName: monsterStats.name,
          ),
        );
      } else {
        newEvents.add(
          CombatEvent.monsterAttack(
            timestamp: timestamp,
            damage: result.damage,
            attackerName: monsterStats.name,
            attackDelayMs: monsterStats.attackDelayMs,
          ),
        );
      }
    }

    // 전투 종료 체크
    final isActive = playerStats.isAlive && monsterStats.isAlive;

    // 기존 이벤트와 합쳐서 최대 10개 유지
    final combinedEvents = [...combat.recentEvents, ...newEvents];
    final recentEvents = combinedEvents.length > 10
        ? combinedEvents.sublist(combinedEvents.length - 10)
        : combinedEvents;

    return (
      combat: combat.copyWith(
        playerStats: playerStats,
        monsterStats: monsterStats,
        playerAttackAccumulatorMs: playerAccumulator,
        monsterAttackAccumulatorMs: monsterAccumulator,
        totalDamageDealt: totalDamageDealt,
        totalDamageTaken: totalDamageTaken,
        turnsElapsed: turnsElapsed,
        isActive: isActive,
        recentEvents: recentEvents,
        activeDoTs: activeDoTs,
        usedPotionTypes: usedPotionTypes,
        activeDebuffs: activeDebuffs,
      ),
      skillSystem: updatedSkillSystem,
      potionInventory: updatedPotionInventory,
    );
  }

  /// 플레이어 사망 처리 (Phase 4)
  ///
  /// 모든 장비 상실 및 사망 정보 기록
  GameState _processPlayerDeath(
    GameState state, {
    required String killerName,
    required DeathCause cause,
  }) {
    // 상실할 장비 개수 계산
    final lostCount = state.equipment.equippedItems.length;

    // 사망 직전 전투 이벤트 저장 (최대 10개)
    final lastCombatEvents =
        state.progress.currentCombat?.recentEvents ?? const [];

    // 빈 장비 생성 (기본 무기만 유지)
    final emptyEquipment = Equipment(
      items: [
        EquipmentItem.defaultWeapon(),
        EquipmentItem.empty(EquipmentSlot.shield),
        EquipmentItem.empty(EquipmentSlot.helm),
        EquipmentItem.empty(EquipmentSlot.hauberk),
        EquipmentItem.empty(EquipmentSlot.brassairts),
        EquipmentItem.empty(EquipmentSlot.vambraces),
        EquipmentItem.empty(EquipmentSlot.gauntlets),
        EquipmentItem.empty(EquipmentSlot.gambeson),
        EquipmentItem.empty(EquipmentSlot.cuisses),
        EquipmentItem.empty(EquipmentSlot.greaves),
        EquipmentItem.empty(EquipmentSlot.sollerets),
      ],
      bestIndex: 0,
    );

    // 사망 정보 생성 (전투 로그 포함)
    final deathInfo = DeathInfo(
      cause: cause,
      killerName: killerName,
      lostEquipmentCount: lostCount,
      goldAtDeath: state.inventory.gold,
      levelAtDeath: state.traits.level,
      timestamp: state.skillSystem.elapsedMs,
      lastCombatEvents: lastCombatEvents,
    );

    // 전투 상태 초기화 및 사망 횟수 증가
    final progress = state.progress.copyWith(
      currentCombat: null,
      deathCount: state.progress.deathCount + 1,
    );

    return state.copyWith(
      equipment: emptyEquipment,
      progress: progress,
      deathInfo: deathInfo,
    );
  }
}
