import 'package:asciineverdie/src/core/animation/ascii_animation_data.dart';
import 'package:asciineverdie/src/core/animation/background_data.dart';
import 'package:asciineverdie/src/core/animation/background_layer.dart';
import 'package:asciineverdie/src/core/animation/canvas/ascii_cell.dart';
import 'package:asciineverdie/src/core/animation/canvas/ascii_layer.dart';
import 'package:asciineverdie/src/core/animation/character_frames.dart';
import 'package:asciineverdie/src/core/animation/monster_size.dart';
import 'package:asciineverdie/src/core/animation/race_character_frames.dart';
import 'package:asciineverdie/src/core/animation/weapon_category.dart';
import 'package:asciineverdie/src/core/animation/weapon_effects.dart';
import 'package:asciineverdie/src/core/animation/canvas/rarity_color_mapper.dart';
import 'package:asciineverdie/src/core/model/item_stats.dart';

/// Canvas용 전투 프레임 합성기
///
/// 기존 BattleComposer의 로직을 레이어 기반으로 변환.
/// 출력: `List<AsciiLayer>` (z-order 정렬됨)
///
/// PvP 모드: [opponentRaceId]가 설정되면 몬스터 대신 상대 캐릭터(좌우 반전) 표시
class CanvasBattleComposer {
  const CanvasBattleComposer({
    required this.weaponCategory,
    required this.hasShield,
    required this.monsterCategory,
    required this.monsterSize,
    this.raceId,
    this.weaponRarity,
    this.opponentRaceId,
    this.opponentHasShield = false,
  });

  final WeaponCategory weaponCategory;
  final bool hasShield;
  final MonsterCategory monsterCategory;
  final MonsterSize monsterSize;

  /// 종족 ID (Phase 4: 종족별 캐릭터 애니메이션)
  final String? raceId;

  /// 무기 희귀도 (Phase 9: 무기 등급별 이펙트 색상)
  final ItemRarity? weaponRarity;

  /// 상대 종족 ID (PvP 모드: 설정 시 몬스터 대신 캐릭터 표시)
  final String? opponentRaceId;

  /// 상대 방패 장착 여부 (PvP 모드)
  final bool opponentHasShield;

  /// PvP 모드 여부
  bool get isPvP => opponentRaceId != null;

  /// 프레임 상수
  static const int frameWidth = 60;
  static const int frameHeight = 8;
  static const int monsterWidth = 18;

  /// 레이어 기반 프레임 생성
  List<AsciiLayer> composeLayers(
    BattlePhase phase,
    int subFrame,
    String? monsterBaseName,
    EnvironmentType environment,
    int globalTick, {
    AttackerType attacker = AttackerType.none,
    bool isCritical = false,
    bool isEvade = false,
    bool isMiss = false,
    bool isDebuff = false,
    bool isDot = false,
    bool isBlock = false,
    bool isParry = false,
    bool hideMonster = false,
  }) {
    final layers = <AsciiLayer>[
      _createBackgroundLayer(environment, globalTick),
      _createCharacterLayer(phase, subFrame, attacker),
      // PvP 모드: 몬스터 대신 상대 캐릭터(좌우 반전) 표시
      // hideMonster: 몬스터 사망 애니메이션 중에는 렌더링 안함
      if (!hideMonster)
        isPvP
            ? _createOpponentCharacterLayer(phase, subFrame, attacker)
            : _createMonsterLayer(phase, subFrame, attacker),
    ];

    // 이펙트 레이어 (준비/공격/히트 페이즈에서, 공격자 있을 때)
    if ((phase == BattlePhase.prepare ||
            phase == BattlePhase.attack ||
            phase == BattlePhase.hit) &&
        attacker != AttackerType.none) {
      final effectLayer = _createEffectLayer(phase, subFrame, attacker);
      if (effectLayer != null) {
        layers.add(effectLayer);
      }
    }

    // 텍스트 이펙트 레이어 (Phase 10~11)
    // 크리티컬 텍스트 (상단 중앙)
    if (isCritical &&
        (phase == BattlePhase.attack || phase == BattlePhase.hit)) {
      layers.add(_createCriticalTextLayer(subFrame));
    }
    // 회피 텍스트 (캐릭터 위)
    if (isEvade) {
      layers.add(_createEvadeTextLayer(subFrame));
    }
    // 미스 텍스트 (몬스터 위)
    if (isMiss) {
      layers.add(_createMissTextLayer(subFrame));
    }
    // 디버프 텍스트 (몬스터 위)
    if (isDebuff) {
      layers.add(_createDebuffTextLayer(subFrame));
    }
    // DOT 텍스트 (몬스터 위)
    if (isDot) {
      layers.add(_createDotTextLayer(subFrame));
    }
    // 블록 텍스트 (캐릭터 위)
    if (isBlock) {
      layers.add(_createBlockTextLayer(subFrame));
    }
    // 패리 텍스트 (캐릭터 위)
    if (isParry) {
      layers.add(_createParryTextLayer(subFrame));
    }

    // z-order 정렬
    layers.sort((a, b) => a.zIndex.compareTo(b.zIndex));

    return layers;
  }

  /// 배경 레이어 생성 (z=0)
  AsciiLayer _createBackgroundLayer(
    EnvironmentType environment,
    int globalTick,
  ) {
    final cells = List.generate(
      frameHeight,
      (_) => List.filled(frameWidth, AsciiCell.empty),
    );

    final bgLayers = getBackgroundLayers(environment);
    for (final layer in bgLayers) {
      // 스크롤 오프셋 계산
      final offset = (globalTick * layer.scrollSpeed).toInt();

      for (var i = 0; i < layer.lines.length; i++) {
        final y = layer.yStart + i;
        if (y >= frameHeight) break;

        final pattern = layer.lines[i];
        if (pattern.isEmpty) continue;

        for (var x = 0; x < frameWidth; x++) {
          final patternIdx = (x + offset) % pattern.length;
          final char = pattern[patternIdx];
          if (char != ' ') {
            cells[y][x] = AsciiCell.fromChar(char);
          }
        }
      }
    }

    // 배경 레이어는 50% 투명으로 캐릭터 부각
    return AsciiLayer(cells: cells, zIndex: 0, opacity: 0.5);
  }

  /// 캐릭터 레이어 생성 (z=1)
  ///
  /// Phase 4: 종족별 캐릭터 프레임 지원
  /// Phase 7: 공격자별 위치 분리 (플레이어가 공격자일 때만 이동)
  AsciiLayer _createCharacterLayer(
    BattlePhase phase,
    int subFrame,
    AttackerType attacker,
  ) {
    CharacterFrame charFrame;

    // 종족 ID가 있으면 종족별 프레임 사용
    if (raceId != null && raceId!.isNotEmpty) {
      final raceData = RaceCharacterFrames.get(raceId!);
      if (raceData != null) {
        final frames = raceData.getFrames(phase);
        charFrame = frames[subFrame % frames.length];
      } else {
        // 종족 데이터 없으면 기본 프레임 사용
        charFrame = getCharacterFrame(phase, subFrame);
      }
    } else {
      charFrame = getCharacterFrame(phase, subFrame);
    }

    if (hasShield) {
      charFrame = charFrame.withShield();
    }

    // 플레이어가 공격자인지 확인
    final isPlayerAttacking =
        attacker == AttackerType.player || attacker == AttackerType.both;

    // 페이즈별 X 위치 (Phase 7: 공격자별 위치 분리)
    // 플레이어가 공격자일 때만 이동, 아니면 제자리(12)
    // hit 페이즈에서도 플레이어 공격 중이면 위치 유지 (Bug fix)
    final charX = switch (phase) {
      BattlePhase.idle => 12,
      BattlePhase.prepare => isPlayerAttacking ? 15 : 12,
      BattlePhase.attack => isPlayerAttacking ? 18 : 12,
      BattlePhase.hit => isPlayerAttacking ? 18 : 12,
      BattlePhase.recover => isPlayerAttacking ? 15 : 12,
    };

    final cells = _spriteToCells(charFrame.lines);
    // 바닥 레이어(Y=7) 위에 서있도록
    final charY = frameHeight - cells.length - 1;

    return AsciiLayer(
      cells: cells,
      zIndex: 2, // 몬스터(z=1) 위에 캐릭터 표시
      offsetX: charX,
      offsetY: charY,
    );
  }

  /// 몬스터 레이어 생성 (z=1, 캐릭터보다 뒤)
  ///
  /// Phase 7: 공격자별 위치 분리 (몬스터가 공격자일 때만 이동)
  AsciiLayer _createMonsterLayer(
    BattlePhase phase,
    int subFrame,
    AttackerType attacker,
  ) {
    final monsterFrames = _getAnimatedMonsterFrames(
      monsterCategory,
      monsterSize,
      phase,
    );
    final monsterFrame = monsterFrames[subFrame % monsterFrames.length];

    // 몬스터 스프라이트를 오른쪽 정렬하여 셀로 변환
    final cells = _spriteToRightAlignedCells(monsterFrame, monsterWidth);

    // 몬스터가 공격자인지 확인
    final isMonsterAttacking =
        attacker == AttackerType.monster || attacker == AttackerType.both;

    // 페이즈별 X 위치 (Phase 7: 공격자별 위치 분리)
    // 몬스터가 공격자일 때만 이동, 아니면 제자리(48)
    // attack 페이즈에서도 몬스터 공격 중이면 위치 유지 (Bug fix)
    final monsterRightEdge = switch (phase) {
      BattlePhase.idle => 48,
      BattlePhase.prepare => isMonsterAttacking ? 45 : 48,
      BattlePhase.attack => isMonsterAttacking ? 42 : 48,
      BattlePhase.hit => isMonsterAttacking ? 42 : 48,
      BattlePhase.recover => isMonsterAttacking ? 45 : 48,
    };
    final monsterX = monsterRightEdge - monsterWidth;

    // 바닥 레이어(Y=7) 위에 서있도록
    final monsterY = frameHeight - cells.length - 1;

    return AsciiLayer(
      cells: cells,
      zIndex: 1,
      offsetX: monsterX,
      offsetY: monsterY,
    );
  }

  /// 상대 캐릭터 레이어 생성 (PvP 모드, z=1)
  ///
  /// 몬스터 대신 상대 캐릭터를 좌우 반전하여 표시
  AsciiLayer _createOpponentCharacterLayer(
    BattlePhase phase,
    int subFrame,
    AttackerType attacker,
  ) {
    // 상대 종족별 프레임 조회
    CharacterFrame opponentFrame;
    if (opponentRaceId != null && opponentRaceId!.isNotEmpty) {
      final raceData = RaceCharacterFrames.get(opponentRaceId!);
      if (raceData != null) {
        final frames = raceData.getFrames(phase);
        opponentFrame = frames[subFrame % frames.length];
      } else {
        opponentFrame = getCharacterFrame(phase, subFrame);
      }
    } else {
      opponentFrame = getCharacterFrame(phase, subFrame);
    }

    if (opponentHasShield) {
      opponentFrame = opponentFrame.withShield();
    }

    // 좌우 반전
    final mirroredLines = _mirrorLines(opponentFrame.lines);

    // 상대가 공격자인지 확인 (몬스터 역할)
    final isOpponentAttacking =
        attacker == AttackerType.monster || attacker == AttackerType.both;

    // 페이즈별 X 위치 (몬스터와 동일하지만 캐릭터 너비 기준)
    const opponentWidth = 6;
    final opponentRightEdge = switch (phase) {
      BattlePhase.idle => 48,
      BattlePhase.prepare => isOpponentAttacking ? 45 : 48,
      BattlePhase.attack => isOpponentAttacking ? 42 : 48,
      BattlePhase.hit => isOpponentAttacking ? 42 : 48,
      BattlePhase.recover => isOpponentAttacking ? 45 : 48,
    };
    final opponentX = opponentRightEdge - opponentWidth;

    final cells = _spriteToCells(mirroredLines);
    final opponentY = frameHeight - cells.length - 1;

    return AsciiLayer(
      cells: cells,
      zIndex: 1,
      offsetX: opponentX,
      offsetY: opponentY,
    );
  }

  /// 문자열 좌우 반전 (PvP 모드용)
  List<String> _mirrorLines(List<String> lines) {
    return lines.map((line) {
      final chars = line.split('');
      final mirrored = chars.reversed.map(_mirrorChar).toList();
      return mirrored.join();
    }).toList();
  }

  /// 개별 문자 미러링
  String _mirrorChar(String char) {
    return switch (char) {
      '/' => r'\',
      r'\' => '/',
      '(' => ')',
      ')' => '(',
      '[' => ']',
      ']' => '[',
      '{' => '}',
      '}' => '{',
      '<' => '>',
      '>' => '<',
      '┘' => '└',
      '└' => '┘',
      '┐' => '┌',
      '┌' => '┐',
      _ => char,
    };
  }

  /// 이펙트 레이어 생성 (z=3, 캐릭터/몬스터 위에 표시)
  ///
  /// Phase 8: 공격자에 따라 이펙트 위치/모양 분리
  /// - 플레이어 공격: 몬스터 왼쪽에 무기 이펙트 (→ 방향)
  /// - 몬스터 공격: 캐릭터 오른쪽에 공격 이펙트 (← 방향)
  AsciiLayer? _createEffectLayer(
    BattlePhase phase,
    int subFrame,
    AttackerType attacker,
  ) {
    // 공격자별 위치 계산을 위한 플래그 (동일 로직 재사용)
    final isPlayerAttacking =
        attacker == AttackerType.player || attacker == AttackerType.both;
    final isMonsterAttacking =
        attacker == AttackerType.monster || attacker == AttackerType.both;

    // 캐릭터 위치 계산 (characterLayer와 동일 로직)
    const charWidth = 6;
    final charX = switch (phase) {
      BattlePhase.idle => 12,
      BattlePhase.prepare => isPlayerAttacking ? 15 : 12,
      BattlePhase.attack => isPlayerAttacking ? 18 : 12,
      BattlePhase.hit => isPlayerAttacking ? 18 : 12,
      BattlePhase.recover => isPlayerAttacking ? 15 : 12,
    };

    // 몬스터 위치 계산 (monsterLayer와 동일 로직)
    final monsterRightEdge = switch (phase) {
      BattlePhase.idle => 48,
      BattlePhase.prepare => isMonsterAttacking ? 45 : 48,
      BattlePhase.attack => isMonsterAttacking ? 42 : 48,
      BattlePhase.hit => isMonsterAttacking ? 42 : 48,
      BattlePhase.recover => isMonsterAttacking ? 45 : 48,
    };
    final monsterX = monsterRightEdge - monsterWidth;

    // 공격자에 따라 다른 이펙트 사용
    final List<String> effectLines;
    final int effectX;

    if (attacker == AttackerType.player) {
      // 플레이어 공격: 무기 이펙트 → 몬스터 왼쪽 바로 옆
      final effect = getWeaponEffect(weaponCategory);
      effectLines = _getEffectLines(effect, phase, subFrame);
      effectX = monsterX - 2;
    } else if (attacker == AttackerType.monster) {
      // 몬스터 공격: 왼쪽 방향 이펙트 → 캐릭터 오른쪽 바로 옆
      effectLines = _getMonsterAttackEffect(phase, subFrame);
      effectX = charX + charWidth;
    } else {
      // 동시 공격(both): 무기 이펙트 두 캐릭터 중앙
      final effect = getWeaponEffect(weaponCategory);
      effectLines = _getEffectLines(effect, phase, subFrame);
      effectX = (charX + charWidth + monsterX) ~/ 2;
    }

    if (effectLines.isEmpty) return null;

    // Phase 9: 플레이어 공격 시 무기 등급 색상 적용
    final List<List<AsciiCell>> cells;
    if (attacker == AttackerType.player && weaponRarity != null) {
      // 무기 등급에 따른 이펙트 색상
      cells = _spriteToCellsWithColor(
        effectLines,
        weaponRarity!.effectCellColor,
      );
    } else {
      // 기본 색상 (자동 색상 결정)
      cells = _spriteToCells(effectLines);
    }

    // 이펙트 높이에 따른 동적 Y 위치 (캔버스 하단 기준)
    final effectHeight = effectLines.length;
    final effectY = frameHeight - effectHeight - 1;

    return AsciiLayer(
      cells: cells,
      zIndex: 3,
      offsetX: effectX,
      offsetY: effectY,
    );
  }

  /// 크리티컬 텍스트 레이어 생성 (z=4, 최상위)
  ///
  /// 크리티컬 히트 시 "*CRIT!*" 텍스트를 화면 상단 중앙에 표시.
  /// 노란색(positive) 색상으로 강조.
  AsciiLayer _createCriticalTextLayer(int subFrame) {
    // 프레임별 텍스트 애니메이션 (반짝임 효과)
    final textLines = subFrame % 2 == 0
        ? _critTextFrames[0]
        : _critTextFrames[1];

    final cells = textLines.map((String line) {
      return line.split('').map((String char) {
        if (char == ' ') return AsciiCell.empty;
        return AsciiCell(char: char, color: AsciiCellColor.positive);
      }).toList();
    }).toList();

    // 화면 상단 중앙 배치 (Y=0~1, X=중앙)
    final textWidth = textLines.isNotEmpty ? textLines[0].length : 0;
    final offsetX = (frameWidth - textWidth) ~/ 2;

    return AsciiLayer(
      cells: cells,
      zIndex: 4, // 이펙트(z=3) 위에 표시
      offsetX: offsetX,
      offsetY: 0,
    );
  }

  /// 회피 텍스트 레이어 생성 (캐릭터 위, positive)
  AsciiLayer _createEvadeTextLayer(int subFrame) {
    return _createTextLayer(
      frames: _evadeTextFrames,
      subFrame: subFrame,
      color: AsciiCellColor.positive,
      offsetX: 15, // 캐릭터 위
    );
  }

  /// 미스 텍스트 레이어 생성 (몬스터 위, negative)
  AsciiLayer _createMissTextLayer(int subFrame) {
    return _createTextLayer(
      frames: _missTextFrames,
      subFrame: subFrame,
      color: AsciiCellColor.negative,
      offsetX: 35, // 몬스터 위
    );
  }

  /// 디버프 텍스트 레이어 생성 (몬스터 위, negative)
  AsciiLayer _createDebuffTextLayer(int subFrame) {
    return _createTextLayer(
      frames: _debuffTextFrames,
      subFrame: subFrame,
      color: AsciiCellColor.negative,
      offsetX: 35, // 몬스터 위
    );
  }

  /// DOT 텍스트 레이어 생성 (몬스터 위, negative)
  AsciiLayer _createDotTextLayer(int subFrame) {
    return _createTextLayer(
      frames: _dotTextFrames,
      subFrame: subFrame,
      color: AsciiCellColor.negative,
      offsetX: 35, // 몬스터 위
    );
  }

  /// 블록 텍스트 레이어 생성 (캐릭터 위, positive)
  AsciiLayer _createBlockTextLayer(int subFrame) {
    return _createTextLayer(
      frames: _blockTextFrames,
      subFrame: subFrame,
      color: AsciiCellColor.positive,
      offsetX: 15, // 캐릭터 위
    );
  }

  /// 패리 텍스트 레이어 생성 (캐릭터 위, positive)
  AsciiLayer _createParryTextLayer(int subFrame) {
    return _createTextLayer(
      frames: _parryTextFrames,
      subFrame: subFrame,
      color: AsciiCellColor.positive,
      offsetX: 15, // 캐릭터 위
    );
  }

  /// 공통 텍스트 레이어 생성 헬퍼
  AsciiLayer _createTextLayer({
    required List<List<String>> frames,
    required int subFrame,
    required AsciiCellColor color,
    required int offsetX,
  }) {
    final textLines = frames[subFrame % 2];

    final cells = textLines.map((String line) {
      return line.split('').map((String char) {
        if (char == ' ') return AsciiCell.empty;
        return AsciiCell(char: char, color: color);
      }).toList();
    }).toList();

    return AsciiLayer(cells: cells, zIndex: 4, offsetX: offsetX, offsetY: 0);
  }

  /// 몬스터 공격 이펙트 (← 방향)
  List<String> _getMonsterAttackEffect(BattlePhase phase, int subFrame) {
    return switch (phase) {
      BattlePhase.prepare =>
        _monsterPrepareFrames[subFrame % _monsterPrepareFrames.length],
      BattlePhase.attack =>
        _monsterAttackFrames[subFrame % _monsterAttackFrames.length],
      BattlePhase.hit => _monsterHitFrames[subFrame % _monsterHitFrames.length],
      _ => <String>[],
    };
  }

  /// 문자열 스프라이트를 AsciiCell 2D 배열로 변환
  List<List<AsciiCell>> _spriteToCells(List<String> lines) {
    return lines.map((line) {
      return line.split('').map(AsciiCell.fromChar).toList();
    }).toList();
  }

  /// 문자열 스프라이트를 지정된 색상으로 AsciiCell 2D 배열로 변환 (Phase 9)
  ///
  /// 이펙트 문자(공백 아닌 문자)에 지정 색상 적용
  List<List<AsciiCell>> _spriteToCellsWithColor(
    List<String> lines,
    AsciiCellColor effectColor,
  ) {
    return lines.map((line) {
      return line.split('').map((char) {
        if (char == ' ' || char.isEmpty) return AsciiCell.empty;
        return AsciiCell(char: char, color: effectColor);
      }).toList();
    }).toList();
  }

  /// 문자열 스프라이트를 오른쪽 정렬된 AsciiCell 2D 배열로 변환
  List<List<AsciiCell>> _spriteToRightAlignedCells(
    List<String> lines,
    int width,
  ) {
    // 전체 스프라이트에서 최대 너비 계산
    int maxWidth = 0;
    for (final line in lines) {
      final trimmed = line.trimRight().length;
      if (trimmed > maxWidth) maxWidth = trimmed;
    }

    // 왼쪽 패딩 계산
    final leftPadding = width - maxWidth;

    return lines.map((line) {
      final trimmed = line.trimRight();
      final cells = <AsciiCell>[];

      // 왼쪽 패딩
      for (var i = 0; i < leftPadding; i++) {
        cells.add(AsciiCell.empty);
      }

      // 실제 문자
      for (var i = 0; i < trimmed.length; i++) {
        cells.add(AsciiCell.fromChar(trimmed[i]));
      }

      // 오른쪽 채우기 (필요시)
      while (cells.length < width) {
        cells.add(AsciiCell.empty);
      }

      return cells;
    }).toList();
  }

  /// 멀티라인 이펙트 프레임 반환
  List<String> _getEffectLines(
    WeaponEffect effect,
    BattlePhase phase,
    int subFrame,
  ) {
    final frames = switch (phase) {
      BattlePhase.idle => <List<String>>[],
      BattlePhase.prepare => effect.prepareFrames,
      BattlePhase.attack => effect.attackFrames,
      BattlePhase.hit => effect.hitFrames,
      BattlePhase.recover => <List<String>>[],
    };
    if (frames.isEmpty) return [];
    return frames[subFrame % frames.length];
  }
}

// ============================================================================
// 몬스터 애니메이션 프레임 (기존 BattleComposer에서 이식)
// ============================================================================

/// 몬스터 애니메이션 프레임 반환 (페이즈별 다른 동작)
List<List<String>> _getAnimatedMonsterFrames(
  MonsterCategory category,
  MonsterSize size,
  BattlePhase phase,
) {
  // 피격 상태
  if (phase == BattlePhase.hit) {
    return _getMonsterHitFrames(category, size);
  }
  // 경계 상태 (prepare, attack)
  if (phase == BattlePhase.prepare || phase == BattlePhase.attack) {
    return _getMonsterAlertFrames(category, size);
  }
  // 일반 상태 (idle, recover)
  return _getMonsterIdleFrames(category, size);
}

List<List<String>> _getMonsterIdleFrames(
  MonsterCategory category,
  MonsterSize size,
) {
  return switch (size) {
    MonsterSize.tiny => _tinyIdleFrames(category),
    MonsterSize.small => _smallIdleFrames(category),
    MonsterSize.medium => _mediumIdleFrames(category),
    // large, huge, giant, titanic 모두 8줄 (large 프레임 사용)
    MonsterSize.large ||
    MonsterSize.huge ||
    MonsterSize.giant ||
    MonsterSize.titanic => _largeIdleFrames(category),
  };
}

List<List<String>> _getMonsterHitFrames(
  MonsterCategory category,
  MonsterSize size,
) {
  return switch (size) {
    MonsterSize.tiny => _tinyHitFrames(category),
    MonsterSize.small => _smallHitFrames(category),
    MonsterSize.medium => _mediumHitFrames(category),
    MonsterSize.large ||
    MonsterSize.huge ||
    MonsterSize.giant ||
    MonsterSize.titanic => _largeHitFrames(category),
  };
}

List<List<String>> _getMonsterAlertFrames(
  MonsterCategory category,
  MonsterSize size,
) {
  return switch (size) {
    MonsterSize.tiny => _tinyAlertFrames(category),
    MonsterSize.small => _smallAlertFrames(category),
    MonsterSize.medium => _mediumAlertFrames(category),
    MonsterSize.large ||
    MonsterSize.huge ||
    MonsterSize.giant ||
    MonsterSize.titanic => _largeAlertFrames(category),
  };
}

// ============================================================================
// Tiny 몬스터 (2줄)
// ============================================================================

List<List<String>> _tinyIdleFrames(MonsterCategory category) {
  return switch (category) {
    MonsterCategory.bug => [
      [r'(o.o)', r' |_|'],
      [r'(o o)', r' |_|'],
    ],
    MonsterCategory.malware => [
      [r'<o_o>', r' \_/'],
      [r'<o-o>', r' /_\'],
    ],
    MonsterCategory.network => [
      [r' @', r'/|\'],
      [r' @', r'\|/'],
    ],
    MonsterCategory.system => [
      [r'[x_x]', r' /_\'],
      [r'[X_X]', r' \_/'],
    ],
    MonsterCategory.crypto => [
      [r'$(.)~', r' /_\'],
      [r'$(.)`', r' \_/'],
    ],
    MonsterCategory.ai => [
      [r'{o}', r'/_\'],
      [r'{O}', r'\_/'],
    ],
    MonsterCategory.boss => [
      [r'(|o|)', r' V V'],
      [r'(|O|)', r' v v'],
    ],
  };
}

List<List<String>> _tinyHitFrames(MonsterCategory category) {
  return [
    [r'(*!*)', r' X_X'],
    [r'(!*!)', r' x_x'],
  ];
}

List<List<String>> _tinyAlertFrames(MonsterCategory category) {
  return switch (category) {
    MonsterCategory.bug => [
      [r'(O!O)', r' |!|'],
      [r'(!O!)', r' |!|'],
    ],
    MonsterCategory.malware => [
      [r'<!_!>', r' \!/'],
      [r'<!-!>', r' /!\'],
    ],
    MonsterCategory.network => [
      [r' @!', r'/|\'],
      [r' !@', r'\|/'],
    ],
    MonsterCategory.system => [
      [r'[!_!]', r' /!\'],
      [r'[!_!]', r' \!/'],
    ],
    MonsterCategory.crypto => [
      [r'$(!)~', r' /!\'],
      [r'$(!)`', r' \!/'],
    ],
    MonsterCategory.ai => [
      [r'{!}', r'/!\'],
      [r'{!}', r'\!/'],
    ],
    MonsterCategory.boss => [
      [r'(|!|)', r' V!V'],
      [r'(|!|)', r' v!v'],
    ],
  };
}

// ============================================================================
// Small 몬스터 (4줄)
// ============================================================================

List<List<String>> _smallIdleFrames(MonsterCategory category) {
  return switch (category) {
    MonsterCategory.bug => [
      [r' /\_/\', r'( O.O )', r' > ^ <', r' /| |\'],
      [r' /\_/\', r'( O O )', r' > v <', r' \| |/'],
    ],
    MonsterCategory.malware => [
      [r'  /\/\', r' (O  O)', r' /    \', r' \/  \/'],
      [r'  \/\/\', r' (O  O)', r' \    /', r' /\  /\'],
    ],
    MonsterCategory.network => [
      [r'   O', r'  /|\', r'  / \', r' _| |_'],
      [r'   O', r'  \|/', r'  | |', r' _/ \_'],
    ],
    MonsterCategory.system => [
      [r'  _+_', r' (x_x)', r'  /|\', r' _/ \_'],
      [r'  _+_', r' (X_X)', r'  \|/', r' _| |_'],
    ],
    MonsterCategory.crypto => [
      [r'   __', r' <(oo)~', r'  /  \', r' <_  _>'],
      [r'   __', r'  (oo)>', r'  \  /', r' <_  _>'],
    ],
    MonsterCategory.ai => [
      [r'  ___', r' (   )', r' (   )', r'  \_/'],
      [r'   _', r'  / \', r' {   }', r'  \_/'],
    ],
    MonsterCategory.boss => [
      [r'  ^w^', r' (|o|)', r'  /|\', r'  V V'],
      [r'  ^W^', r' (|O|)', r'  \|/', r'  v v'],
    ],
  };
}

List<List<String>> _smallHitFrames(MonsterCategory category) {
  return [
    [r'  *!*', r' (>_<)', r'  \X/', r' _/_\_'],
    [r' !*!', r' (@_@)', r'  /X\', r' _\_/_'],
  ];
}

List<List<String>> _smallAlertFrames(MonsterCategory category) {
  return switch (category) {
    MonsterCategory.bug => [
      [r' /\_/\', r'( O!O )', r' > ! <', r' /| |\'],
      [r' /\_/\', r'( !O! )', r' > ! <', r' \| |/'],
    ],
    MonsterCategory.malware => [
      [r'  /\/\', r' (!  !)', r' /    \', r' \/  \/'],
      [r'  \/\/\', r' (!  !)', r' \    /', r' /\  /\'],
    ],
    MonsterCategory.network => [
      [r'   O!', r'  /|\', r'  / \', r' _| |_'],
      [r'   !O', r'  \|/', r'  | |', r' _/ \_'],
    ],
    MonsterCategory.system => [
      [r'  _!_', r' (!_!)', r'  /|\', r' _/ \_'],
      [r'  _!_', r' (!_!)', r'  \|/', r' _| |_'],
    ],
    MonsterCategory.crypto => [
      [r'   __', r' <(!!)~', r'  /  \', r' <_  _>'],
      [r'   __', r'  (!!)>', r'  \  /', r' <_  _>'],
    ],
    MonsterCategory.ai => [
      [r'  ___', r' ( ! )', r' ( ! )', r'  \_/'],
      [r'   _', r'  /!\', r' { ! }', r'  \_/'],
    ],
    MonsterCategory.boss => [
      [r'  ^!^', r' (|!|)', r'  /|\', r'  V V'],
      [r'  ^!^', r' (|!|)', r'  \|/', r'  v v'],
    ],
  };
}

// ============================================================================
// Medium 몬스터 (6줄)
// ============================================================================

List<List<String>> _mediumIdleFrames(MonsterCategory category) {
  return switch (category) {
    MonsterCategory.bug => [
      [
        r'  /\_/\',
        r' ( O.O )',
        r'  > ^ <',
        r' /|   |\',
        r' | |   | |',
        r'_|_|   |_|_',
      ],
      [
        r'  /\_/\',
        r' ( O O )',
        r'  > v <',
        r' \|   |/',
        r' | |   | |',
        r'_|_|   |_|_',
      ],
    ],
    MonsterCategory.malware => [
      [
        r'   /\/\',
        r'  /O  O\',
        r'  \    /',
        r'  /    \',
        r'  \/  \/',
        r' _/    \_',
      ],
      [
        r'   \/\/\',
        r'  \O  O/',
        r'  /    \',
        r'  \    /',
        r'  /\  /\',
        r' _\    /_',
      ],
    ],
    MonsterCategory.network => [
      [r'    O', r'   /|\', r'   / \', r'  |   |', r'  |   |', r' _|   |_'],
      [r'    O', r'   \|/', r'   | |', r'  |   |', r'  |   |', r' _/   \_'],
    ],
    MonsterCategory.system => [
      [r'   _+_', r'  (X_X)', r'   /|\', r'  / | \', r' |  |  |', r'_/  |  \_'],
      [r'   _x_', r'  (x_x)', r'   \|/', r'  \ | /', r' |  |  |', r'_\  |  /_'],
    ],
    MonsterCategory.crypto => [
      [
        r'    __',
        r'  <(OO)~',
        r'   /  \',
        r'  /    \',
        r' |      |',
        r'<__    __>',
      ],
      [
        r'    __',
        r'   (OO)>',
        r'   \  /',
        r'  \    /',
        r' |      |',
        r'<__    __>',
      ],
    ],
    MonsterCategory.ai => [
      [
        r'   ____',
        r'  /    \',
        r' (      )',
        r' (      )',
        r'  \    /',
        r'   \__/',
      ],
      [
        r'    __',
        r'   /  \',
        r'  /    \',
        r' {      }',
        r'  \    /',
        r'   \__/',
      ],
    ],
    MonsterCategory.boss => [
      [r'   ^W^', r'  (|O|)', r'   /|\', r'  / | \', r'  V   V', r' _/   \_'],
      [r'   ^w^', r'  (|o|)', r'   \|/', r'  \ | /', r'  v   v', r' _\   /_'],
    ],
  };
}

List<List<String>> _mediumHitFrames(MonsterCategory category) {
  return [
    [r'   *!*', r'  (>.<)', r'   \X/', r'  /   \', r' |     |', r'_/_   \_\'],
    [r'  !*!', r'  (@_@)', r'   /X\', r'  \   /', r' |     |', r'_\_   /_/'],
  ];
}

List<List<String>> _mediumAlertFrames(MonsterCategory category) {
  return switch (category) {
    MonsterCategory.bug => [
      [
        r'  /\_/\',
        r' ( O!O )',
        r'  > ! <',
        r' /|   |\',
        r' | |   | |',
        r'_|_|   |_|_',
      ],
      [
        r'  /\_/\',
        r' ( !O! )',
        r'  > ! <',
        r' \|   |/',
        r' | |   | |',
        r'_|_|   |_|_',
      ],
    ],
    MonsterCategory.malware => [
      [
        r'   /\/\',
        r'  /!  !\',
        r'  \    /',
        r'  /    \',
        r'  \/  \/',
        r' _/    \_',
      ],
      [
        r'   \/\/\',
        r'  \!  !/',
        r'  /    \',
        r'  \    /',
        r'  /\  /\',
        r' _\    /_',
      ],
    ],
    MonsterCategory.network => [
      [r'    O!', r'   /|\', r'   / \', r'  |   |', r'  |   |', r' _|   |_'],
      [r'    !O', r'   \|/', r'   | |', r'  |   |', r'  |   |', r' _/   \_'],
    ],
    MonsterCategory.system => [
      [r'   _!_', r'  (!_!)', r'   /|\', r'  / | \', r' |  |  |', r'_/  |  \_'],
      [r'   _!_', r'  (!_!)', r'   \|/', r'  \ | /', r' |  |  |', r'_\  |  /_'],
    ],
    MonsterCategory.crypto => [
      [
        r'    __',
        r'  <(!!)~',
        r'   /  \',
        r'  /    \',
        r' |      |',
        r'<__    __>',
      ],
      [
        r'    __',
        r'   (!!)>',
        r'   \  /',
        r'  \    /',
        r' |      |',
        r'<__    __>',
      ],
    ],
    MonsterCategory.ai => [
      [
        r'   ____',
        r'  / !  \',
        r' (   !  )',
        r' (  !   )',
        r'  \    /',
        r'   \__/',
      ],
      [
        r'    __',
        r'   / !\',
        r'  /  !  \',
        r' {   !   }',
        r'  \    /',
        r'   \__/',
      ],
    ],
    MonsterCategory.boss => [
      [r'   ^!^', r'  (|!|)', r'   /|\', r'  / | \', r'  V   V', r' _/   \_'],
      [r'   ^!^', r'  (|!|)', r'   \|/', r'  \ | /', r'  v   v', r' _\   /_'],
    ],
  };
}

// ============================================================================
// Large 몬스터 (8줄)
// ============================================================================

List<List<String>> _largeIdleFrames(MonsterCategory category) {
  return switch (category) {
    MonsterCategory.bug => [
      [
        r'   /\__/\',
        r'  ( O  O )',
        r'   > ^^ <',
        r'  /|    |\',
        r' | |    | |',
        r' | |    | |',
        r'_| |    | |_',
        r'|__|____|__|',
      ],
      [
        r'   /\__/\',
        r'  ( O  O )',
        r'   > vv <',
        r'  \|    |/',
        r' | |    | |',
        r' | |    | |',
        r'_| |    | |_',
        r'|__|____|__|',
      ],
    ],
    MonsterCategory.malware => [
      [
        r'    /\/\',
        r'   /O  O\',
        r'   \    /',
        r'   /    \',
        r'  /      \',
        r'  \/    \/',
        r' _/      \_',
        r'/__      __\\',
      ],
      [
        r'    \/\/\',
        r'   \O  O/',
        r'   /    \',
        r'   \    /',
        r'  \      /',
        r'  /\    /\',
        r' _\      /_',
        r'\__      __/',
      ],
    ],
    MonsterCategory.network => [
      [
        r'     O',
        r'    /|\',
        r'    / \',
        r'   |   |',
        r'   |   |',
        r'  |     |',
        r' _|     |_',
        r'|__     __|',
      ],
      [
        r'     O',
        r'    \|/',
        r'    | |',
        r'   |   |',
        r'   |   |',
        r'  |     |',
        r' _/     \_',
        r'/__     __\\',
      ],
    ],
    MonsterCategory.system => [
      [
        r'    _+_',
        r'   (X_X)',
        r'    /|\',
        r'   / | \',
        r'  |  |  |',
        r'  |  |  |',
        r' _/  |  \_',
        r'|____|____|',
      ],
      [
        r'    _x_',
        r'   (x_x)',
        r'    \|/',
        r'   \ | /',
        r'  |  |  |',
        r'  |  |  |',
        r' _\  |  /_',
        r'|____|____|',
      ],
    ],
    MonsterCategory.crypto => [
      [
        r'     ___',
        r'   <(O O)~',
        r'    /   \',
        r'   /     \',
        r'  |       |',
        r'  |       |',
        r' <__     __>',
        r'|___     ___|',
      ],
      [
        r'     ___',
        r'    (O O)>',
        r'    \   /',
        r'   \     /',
        r'  |       |',
        r'  |       |',
        r' <__     __>',
        r'|___     ___|',
      ],
    ],
    MonsterCategory.ai => [
      [
        r'    _____',
        r'   /     \',
        r'  (       )',
        r'  (       )',
        r'  (       )',
        r'   \     /',
        r'    \   /',
        r'     \_/',
      ],
      [
        r'     ___',
        r'    /   \',
        r'   /     \',
        r'  {       }',
        r'  {       }',
        r'   \     /',
        r'    \   /',
        r'     \_/',
      ],
    ],
    MonsterCategory.boss => [
      [
        r'    ^W^',
        r'   /|O|\',
        r'    /|\',
        r'   / | \',
        r'  |  |  |',
        r'  V  |  V',
        r' _/  |  \_',
        r'|_____|_____|',
      ],
      [
        r'    ^w^',
        r'   \|o|/',
        r'    \|/',
        r'   \ | /',
        r'  |  |  |',
        r'  v  |  v',
        r' _\  |  /_',
        r'|_____|_____|',
      ],
    ],
  };
}

List<List<String>> _largeHitFrames(MonsterCategory category) {
  return [
    [
      r'    *!*',
      r'   (>.<)',
      r'    \X/',
      r'   / | \',
      r'  |  |  |',
      r'  X  |  X',
      r' _/  |  \_',
      r'|_____|_____|',
    ],
    [
      r'   !*!',
      r'   (@_@)',
      r'    /X\',
      r'   \ | /',
      r'  |  |  |',
      r'  x  |  x',
      r' _\  |  /_',
      r'|_____|_____|',
    ],
  ];
}

List<List<String>> _largeAlertFrames(MonsterCategory category) {
  return switch (category) {
    MonsterCategory.bug => [
      [
        r'   /\__/\',
        r'  ( O!O  )',
        r'   > !! <',
        r'  /|    |\',
        r' | |    | |',
        r' | |    | |',
        r'_| |    | |_',
        r'|__|____|__|',
      ],
      [
        r'   /\__/\',
        r'  ( !O!  )',
        r'   > !! <',
        r'  \|    |/',
        r' | |    | |',
        r' | |    | |',
        r'_| |    | |_',
        r'|__|____|__|',
      ],
    ],
    MonsterCategory.malware => [
      [
        r'    /\/\',
        r'   /!  !\',
        r'   \    /',
        r'   /    \',
        r'  /      \',
        r'  \/    \/',
        r' _/      \_',
        r'/__      __\\',
      ],
      [
        r'    \/\/\',
        r'   \!  !/',
        r'   /    \',
        r'   \    /',
        r'  \      /',
        r'  /\    /\',
        r' _\      /_',
        r'\__      __/',
      ],
    ],
    MonsterCategory.network => [
      [
        r'     O!',
        r'    /|\',
        r'    / \',
        r'   |   |',
        r'   |   |',
        r'  |     |',
        r' _|     |_',
        r'|__     __|',
      ],
      [
        r'     !O',
        r'    \|/',
        r'    | |',
        r'   |   |',
        r'   |   |',
        r'  |     |',
        r' _/     \_',
        r'/__     __\\',
      ],
    ],
    MonsterCategory.system => [
      [
        r'    _!_',
        r'   (!_!)',
        r'    /|\',
        r'   / | \',
        r'  |  |  |',
        r'  |  |  |',
        r' _/  |  \_',
        r'|____|____|',
      ],
      [
        r'    _!_',
        r'   (!_!)',
        r'    \|/',
        r'   \ | /',
        r'  |  |  |',
        r'  |  |  |',
        r' _\  |  /_',
        r'|____|____|',
      ],
    ],
    MonsterCategory.crypto => [
      [
        r'     ___',
        r'   <(! !)~',
        r'    /   \',
        r'   /     \',
        r'  |       |',
        r'  |       |',
        r' <__     __>',
        r'|___     ___|',
      ],
      [
        r'     ___',
        r'    (! !)>',
        r'    \   /',
        r'   \     /',
        r'  |       |',
        r'  |       |',
        r' <__     __>',
        r'|___     ___|',
      ],
    ],
    MonsterCategory.ai => [
      [
        r'    _____',
        r'   /  !  \',
        r'  (   !   )',
        r'  (  !    )',
        r'  (   !   )',
        r'   \     /',
        r'    \   /',
        r'     \_/',
      ],
      [
        r'     ___',
        r'    / ! \',
        r'   /  !  \',
        r'  {   !   }',
        r'  {  !    }',
        r'   \     /',
        r'    \   /',
        r'     \_/',
      ],
    ],
    MonsterCategory.boss => [
      [
        r'    ^!^',
        r'   /|!|\',
        r'    /|\',
        r'   / | \',
        r'  |  |  |',
        r'  V  |  V',
        r' _/  |  \_',
        r'|_____|_____|',
      ],
      [
        r'     ^!^',
        r'    \|!|/',
        r'     \|/',
        r'    \ | /',
        r'   |  |  |',
        r'   v  |  v',
        r'  _\  |  /_',
        r'|_____|_____|',
      ],
    ],
  };
}

// ============================================================================
// 몬스터 공격 이펙트 (← 방향, Phase 8) - 5줄
// ============================================================================

/// 몬스터 공격 준비 프레임 (5줄)
const _monsterPrepareFrames = <List<String>>[
  [r'          ', r'          ', r'     <    ', r'          ', r'          '],
  [r'          ', r'      _   ', r'    <<    ', r'      -   ', r'          '],
];

/// 몬스터 공격 프레임 (5줄)
const _monsterAttackFrames = <List<String>>[
  [r'          ', r'     __   ', r'   <--    ', r'     --   ', r'          '],
  [r'          ', r'    ___   ', r'  <---    ', r'    ---   ', r'          '],
  [r'          ', r'   ____   ', r' <-----   ', r'   ----   ', r'          '],
];

/// 몬스터 히트 프레임 (5줄)
const _monsterHitFrames = <List<String>>[
  [r' *SLASH!* ', r'   ****   ', r' <-----   ', r'   ****   ', r'          '],
  [r'*ATTACK!* ', r'  ****    ', r'  <----   ', r'  ****    ', r'          '],
];

// ============================================================================
// 크리티컬 텍스트 프레임 (2줄, Phase 10)
// ============================================================================

/// 크리티컬 히트 텍스트 프레임 (반짝임 애니메이션)
const _critTextFrames = <List<String>>[
  [r'*CRITICAL!*', r' ========='],
  [r'=CRITICAL!=', r' *********'],
];

// ============================================================================
// 전투 텍스트 이펙트 프레임 (Phase 11)
// ============================================================================

/// 회피 텍스트 프레임 (플레이어 회피 성공)
const _evadeTextFrames = <List<String>>[
  [r'*EVADE!*', r'========'],
  [r'=EVADE!=', r'********'],
];

/// 미스 텍스트 프레임 (플레이어 공격 빗나감)
const _missTextFrames = <List<String>>[
  [r'*MISS!*', r'======='],
  [r'=MISS!=', r'*******'],
];

/// 디버프 텍스트 프레임 (적에게 디버프 적용)
const _debuffTextFrames = <List<String>>[
  [r'*DEBUFF!*', r'========='],
  [r'=DEBUFF!=', r'*********'],
];

/// DOT 텍스트 프레임 (지속 피해)
const _dotTextFrames = <List<String>>[
  [r'*DOT!*', r'======'],
  [r'=DOT!=', r'******'],
];

/// 블록 텍스트 프레임 (방패 방어)
const _blockTextFrames = <List<String>>[
  [r'*BLOCK!*', r'========'],
  [r'=BLOCK!=', r'********'],
];

/// 패리 텍스트 프레임 (무기 쳐내기)
const _parryTextFrames = <List<String>>[
  [r'*PARRY!*', r'========'],
  [r'=PARRY!=', r'********'],
];

/// 몬스터 Idle 프레임 가져오기 (외부에서 접근 가능)
///
/// 몬스터 사망 애니메이션에서 분해할 프레임을 가져올 때 사용
List<List<String>> getMonsterIdleFrames(
  MonsterCategory category,
  MonsterSize size,
) {
  return _getMonsterIdleFrames(category, size);
}
