import 'dart:math' as math;

import 'package:wolf_3d_dart/src/entities/entities/enemies/enemy.dart';
import 'package:wolf_3d_dart/src/entities/entities/enemies/enemy_type.dart';
import 'package:wolf_3d_dart/src/entities/entity.dart';
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';

class Mutant extends Enemy {
  static const double speed = 0.04;

  @override
  EnemyType get type => EnemyType.mutant;

  Mutant({
    required super.x,
    required super.y,
    required super.angle,
    required super.mapId,
    Difficulty difficulty = Difficulty.medium,
  }) : super(
         spriteIndex: EnemyType.mutant.animations.idle.start,
         state: EntityState.idle,
       ) {
    health = type.hitPointsFor(difficulty);
  }

  @override
  ({Coordinate2D movement, double newAngle}) update({
    required int elapsedMs,
    required int elapsedDeltaMs,
    required Coordinate2D playerPosition,
    required double playerAngle,
    required bool isPlayerRunning,
    required bool Function(int x, int y) isWalkable,
    required int Function(int x, int y) areaAt,
    required bool Function(int area) isAreaConnectedToPlayer,
    required void Function(int damage) onDamagePlayer,
    required void Function(int x, int y) tryOpenDoor,
    required void Function(SoundEffect effect) onPlaySound,
  }) {
    Coordinate2D movement = const Coordinate2D(0, 0);
    double newAngle = angle;

    if (checkWakeUp(
      elapsedMs: elapsedMs,
      playerPosition: playerPosition,
      isWalkable: isWalkable,
      areaAt: areaAt,
      isAreaConnectedToPlayer: isAreaConnectedToPlayer,
    )) {
      onPlaySound(alertSound);
    }

    double distance = position.distanceTo(playerPosition);
    double angleToPlayer = position.angleTo(playerPosition);

    if (isAlerted && state != EntityState.dead) newAngle = angleToPlayer;

    double diff = newAngle - angleToPlayer;
    while (diff <= -math.pi) {
      diff += 2 * math.pi;
    }
    while (diff > math.pi) {
      diff -= 2 * math.pi;
    }

    EnemyAnimation currentAnim = switch (state) {
      EntityState.patrolling || EntityState.ambushing => EnemyAnimation.walking,
      EntityState.attacking => EnemyAnimation.attacking,
      EntityState.pain => EnemyAnimation.pain,
      EntityState.dead => isDying ? EnemyAnimation.dying : EnemyAnimation.dead,
      _ => EnemyAnimation.idle,
    };

    spriteIndex = type.getSpriteFromAnimation(
      animation: currentAnim,
      elapsedMs: elapsedMs,
      lastActionTime: lastActionTime,
      angleDiff: diff,
      walkFrameOverride: state == EntityState.patrolling ? currentFrame : null,
    );

    if (state == EntityState.patrolling) {
      if (!isAlerted) {
        movement = getPatrolPathMovement(
          moveSpeed: speed,
          playerPosition: playerPosition,
          isWalkable: isWalkable,
          tryOpenDoor: tryOpenDoor,
        );
      } else {
        resetPatrolPathState();
        if (distance > 0.8) {
          movement = getValidMovement(
            intendedMovement: Coordinate2D(
              math.cos(angleToPlayer) * speed,
              math.sin(angleToPlayer) * speed,
            ),
            playerPosition: playerPosition,
            isWalkable: isWalkable,
            tryOpenDoor: tryOpenDoor,
          );
        }
      }

      if (processTics(elapsedDeltaMs, moveSpeed: speed)) {
        currentFrame = (currentFrame + 1) % 4;
        setTics(6); // Very fast walk

        if (isAlerted &&
            distance < 10.0 &&
            hasLineOfSight(playerPosition, isWalkable)) {
          state = EntityState.attacking;
          currentFrame = 0;
          lastActionTime = elapsedMs;
          setTics(10); // Aim
        }
      }
    }

    if (state == EntityState.attacking) {
      if (processTics(elapsedDeltaMs, moveSpeed: 0)) {
        currentFrame++;
        if (currentFrame == 1) {
          onPlaySound(attackSound);
          if (tryRollRangedHit(
            distance: distance,
            playerPosition: playerPosition,
            playerAngle: playerAngle,
            isPlayerRunning: isPlayerRunning,
          )) {
            onDamagePlayer(rollRangedDamage(distance));
          }
          setTics(4);
        } else if (currentFrame == 2) {
          setTics(4);
        } else {
          state = EntityState.patrolling;
          currentFrame = 0;
          setTics(6);
        }
      }
    }

    if (state == EntityState.pain) {
      if (processTics(elapsedDeltaMs, moveSpeed: 0)) {
        state = EntityState.patrolling;
        currentFrame = 0;
        setTics(5);
      }
    }

    return (movement: movement, newAngle: newAngle);
  }
}
