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

// 하위 호환성(backward compatibility)을 위한 re-export
export 'package:asciineverdie/src/shared/animation/canvas/monster_frames.dart'
    show getMonsterIdleFrames;

/// 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);
      }
    }

    // 텍스트 이펙트 레이어
    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));
    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);
          }
        }
      }
    }

    return AsciiLayer(cells: cells, zIndex: 0, opacity: 0.5);
  }

  /// 캐릭터 레이어 생성 (z=2)
  AsciiLayer _createCharacterLayer(
    BattlePhase phase,
    int subFrame,
    AttackerType attacker,
  ) {
    CharacterFrame charFrame;
    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;
    final charX = _getCharacterX(phase, isPlayerAttacking);

    final cells = _spriteToCells(charFrame.lines);
    final charY = frameHeight - cells.length - 1;

    return AsciiLayer(cells: cells, zIndex: 2, offsetX: charX, offsetY: charY);
  }

  /// 몬스터 레이어 생성 (z=1)
  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;
    final monsterRightEdge = _getMonsterRightEdge(phase, isMonsterAttacking);
    final monsterX = monsterRightEdge - monsterWidth;
    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;

    const opponentWidth = 6;
    final opponentRightEdge = _getMonsterRightEdge(phase, isOpponentAttacking);
    final opponentX = opponentRightEdge - opponentWidth;

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

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

  /// 이펙트 레이어 생성 (z=3)
  AsciiLayer? _createEffectLayer(
    BattlePhase phase,
    int subFrame,
    AttackerType attacker,
  ) {
    final isPlayerAttacking =
        attacker == AttackerType.player || attacker == AttackerType.both;
    final isMonsterAttacking =
        attacker == AttackerType.monster || attacker == AttackerType.both;

    const charWidth = 6;
    final charX = _getCharacterX(phase, isPlayerAttacking);
    final monsterRightEdge = _getMonsterRightEdge(phase, isMonsterAttacking);
    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 {
      final effect = getWeaponEffect(weaponCategory);
      effectLines = _getEffectLines(effect, phase, subFrame);
      effectX = (charX + charWidth + monsterX) ~/ 2;
    }

    if (effectLines.isEmpty) return null;

    final List<List<AsciiCell>> cells;
    if (attacker == AttackerType.player && weaponRarity != null) {
      cells = _spriteToCellsWithColor(
        effectLines,
        weaponRarity!.effectCellColor,
      );
    } else {
      cells = _spriteToCells(effectLines);
    }

    final effectHeight = effectLines.length;
    final effectY = frameHeight - effectHeight - 1;

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

  // ============================================================================
  // 텍스트 이펙트 레이어
  // ============================================================================

  AsciiLayer _createCriticalTextLayer(int subFrame) {
    final textLines = subFrame % 2 == 0 ? critTextFrames[0] : critTextFrames[1];
    final cells = _textLinesToCells(textLines, AsciiCellColor.positive);
    final textWidth = textLines.isNotEmpty ? textLines[0].length : 0;
    final offsetX = (frameWidth - textWidth) ~/ 2;
    return AsciiLayer(cells: cells, zIndex: 4, offsetX: offsetX, offsetY: 0);
  }

  AsciiLayer _createEvadeTextLayer(int subFrame) {
    return _createTextLayer(
      frames: evadeTextFrames,
      subFrame: subFrame,
      color: AsciiCellColor.positive,
      offsetX: 15,
    );
  }

  AsciiLayer _createMissTextLayer(int subFrame) {
    return _createTextLayer(
      frames: missTextFrames,
      subFrame: subFrame,
      color: AsciiCellColor.negative,
      offsetX: 35,
    );
  }

  AsciiLayer _createDebuffTextLayer(int subFrame) {
    return _createTextLayer(
      frames: debuffTextFrames,
      subFrame: subFrame,
      color: AsciiCellColor.negative,
      offsetX: 35,
    );
  }

  AsciiLayer _createDotTextLayer(int subFrame) {
    return _createTextLayer(
      frames: dotTextFrames,
      subFrame: subFrame,
      color: AsciiCellColor.negative,
      offsetX: 35,
    );
  }

  AsciiLayer _createBlockTextLayer(int subFrame) {
    return _createTextLayer(
      frames: blockTextFrames,
      subFrame: subFrame,
      color: AsciiCellColor.positive,
      offsetX: 15,
    );
  }

  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 = _textLinesToCells(textLines, color);
    return AsciiLayer(cells: cells, zIndex: 4, offsetX: offsetX, offsetY: 0);
  }

  /// 텍스트 라인을 AsciiCell 배열로 변환
  List<List<AsciiCell>> _textLinesToCells(
    List<String> lines,
    AsciiCellColor color,
  ) {
    return lines.map((String line) {
      return line.split('').map((String char) {
        if (char == ' ') return AsciiCell.empty;
        return AsciiCell(char: char, color: color);
      }).toList();
    }).toList();
  }

  // ============================================================================
  // 위치 계산 헬퍼
  // ============================================================================

  /// 캐릭터 X 위치 계산
  int _getCharacterX(BattlePhase phase, bool isPlayerAttacking) {
    return 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,
    };
  }

  /// 몬스터 오른쪽 가장자리 위치 계산
  int _getMonsterRightEdge(BattlePhase phase, bool isMonsterAttacking) {
    return 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,
    };
  }

  // ============================================================================
  // 이펙트 헬퍼
  // ============================================================================

  /// 몬스터 공격 이펙트 (방향)
  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>[],
    };
  }

  /// 멀티라인 이펙트 프레임 반환
  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];
  }

  // ============================================================================
  // 스프라이트 변환 유틸리티
  // ============================================================================

  /// 문자열 좌우 반전 (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'\' => '/',
      '(' => ')',
      ')' => '(',
      '[' => ']',
      ']' => '[',
      '{' => '}',
      '}' => '{',
      '<' => '>',
      '>' => '<',
      '\u2518' => '\u2514',
      '\u2514' => '\u2518',
      '\u2510' => '\u250c',
      '\u250c' => '\u2510',
      _ => char,
    };
  }

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

  /// 문자열 스프라이트를 지정된 색상으로 변환
  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();
  }
}
