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 Guard extends Enemy {
  // Original SPDPATROL is 512. 1 Tile is 65536 units.
  // 512 / 65536 = ~0.0078 tiles per tic.
  static const double speedPerTic = 0.0078;

  @override
  EnemyType get type => EnemyType.guard;

  Guard({
    required super.x,
    required super.y,
    required super.angle,
    required super.mapId,
    Difficulty difficulty = Difficulty.medium,
  }) : super(
         spriteIndex: EnemyType.guard.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;
    double distance = position.distanceTo(playerPosition);

    // 1. Perception (SightPlayer)
    if (checkWakeUp(
      elapsedMs: elapsedMs,
      playerPosition: playerPosition,
      isWalkable: isWalkable,
      areaAt: areaAt,
      isAreaConnectedToPlayer: isAreaConnectedToPlayer,
    )) {
      onPlaySound(alertSound);
    }

    // 2. Discrete AI Logic (Decisions happen every 10 tics)
    bool ticReady = processTics(elapsedDeltaMs, moveSpeed: 0);

    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(20);
        } else if (currentFrame == 2) {
          setTics(20);
        } else {
          state = EntityState.patrolling;
          currentFrame = 0;
          setTics(20);
        }
      }
    } else if (state == EntityState.pain) {
      if (ticReady) {
        state = isAlerted ? EntityState.patrolling : EntityState.idle;
        setTics(10);
      }
    } else if (state != EntityState.dead) {
      // 3. Continuous Movement Logic (Happens every frame)
      // Calculate movement based on current tics passed this frame for smoothness
      double ticsThisFrame = elapsedDeltaMs / 14.28;
      double currentMoveSpeed = speedPerTic * ticsThisFrame;

      if (isAlerted) {
        resetPatrolPathState();
        newAngle = position.angleTo(playerPosition);

        // AI Decision: Should I stop to shoot? (Only check on tic boundaries)
        if (ticReady && hasLineOfSight(playerPosition, isWalkable)) {
          double chance = (distance < 2.0) ? 1.0 : (160.0 / distance) / 256.0;
          if (math.Random().nextDouble() < chance) {
            state = EntityState.attacking;
            currentFrame = 0;
            lastActionTime = elapsedMs;
            setTics(20);
            return (movement: const Coordinate2D(0, 0), newAngle: newAngle);
          }
        }

        // Pursuit movement
        movement = getValidMovement(
          intendedMovement: Coordinate2D(
            math.cos(newAngle) * currentMoveSpeed,
            math.sin(newAngle) * currentMoveSpeed,
          ),
          playerPosition: playerPosition,
          isWalkable: isWalkable,
          tryOpenDoor: tryOpenDoor,
        );
      } else if (state == EntityState.patrolling) {
        movement = getPatrolPathMovement(
          moveSpeed: currentMoveSpeed,
          playerPosition: playerPosition,
          isWalkable: isWalkable,
          tryOpenDoor: tryOpenDoor,
        );
      }

      if (ticReady) {
        currentFrame = (currentFrame + 1) % 4;
        setTics(10);
      }
    }

    _updateAnimation(elapsedMs, newAngle, playerPosition);
    return (movement: movement, newAngle: newAngle);
  }

  void _updateAnimation(
    int elapsedMs,
    double newAngle,
    Coordinate2D playerPosition,
  ) {
    double diff = newAngle - position.angleTo(playerPosition);
    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 || isAlerted)
          ? currentFrame
          : null,
    );
  }
}
