import 'dart:math' as math;

import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
import 'package:wolf_3d_dart/wolf_3d_entities.dart';

class HansGrosse extends Enemy {
  static const double speed = 0.04;
  static const int _baseSprite = 291;

  @override
  EnemyType get type =>
      throw UnimplementedError("Hans Grosse uses manual animation logic.");

  @override
  SoundEffect get alertSound => SoundEffect.bossActive;

  @override
  SoundEffect get attackSound => SoundEffect.enemyFire;

  @override
  SoundEffect get deathSound => SoundEffect.hansGrosseDeath;

  @override
  int get scoreValue => 5000;

  HansGrosse({
    required super.x,
    required super.y,
    required super.angle,
    required super.mapId,
    required Difficulty difficulty,
  }) : super(spriteIndex: _baseSprite, state: EntityState.idle) {
    health = switch (difficulty.level) {
      0 => 850,
      1 => 950,
      2 => 1050,
      _ => 1200,
    };
    damage = 20;
  }

  static HansGrosse? trySpawn(
    int objId,
    double x,
    double y,
    Difficulty difficulty, {
    bool isSharewareMode = false,
  }) {
    if (objId == MapObject.bossHansGrosse) {
      return HansGrosse(
        x: x,
        y: y,
        angle: MapObject.getAngle(objId),
        mapId: objId,
        difficulty: difficulty,
      );
    }
    return null;
  }

  @override
  void takeDamage(int amount, int currentTime) {
    if (state == EntityState.dead) return;

    health -= amount;
    lastActionTime = currentTime;

    if (health <= 0) {
      state = EntityState.dead;
      isDying = true;
      currentFrame = 0;
      setTics(5); // Start death sequence
    }
  }

  @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 (isAlerted && state != EntityState.dead) {
      newAngle = position.angleTo(playerPosition);
    }

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

    double distance = position.distanceTo(playerPosition);

    switch (state) {
      case EntityState.idle:
      case EntityState.ambushing:
        spriteIndex = _baseSprite;
        break;

      case EntityState.patrolling:
        if (!isAlerted || distance > 1.5) {
          double currentMoveAngle = isAlerted ? newAngle : angle;
          movement = getValidMovement(
            intendedMovement: Coordinate2D(
              math.cos(currentMoveAngle) * speed,
              math.sin(currentMoveAngle) * speed,
            ),
            playerPosition: playerPosition,
            isWalkable: isWalkable,
            tryOpenDoor: tryOpenDoor,
          );
        }

        spriteIndex = (_baseSprite + 1) + currentFrame;

        if (processTics(elapsedDeltaMs, moveSpeed: speed)) {
          currentFrame = (currentFrame + 1) % 4;
          setTics(15);

          if (isAlerted &&
              distance < 12.0 &&
              hasLineOfSight(playerPosition, isWalkable)) {
            state = EntityState.attacking;
            currentFrame = 0;
            setTics(10); // Aiming
          }
        }
        break;

      case EntityState.attacking:
        if (processTics(elapsedDeltaMs, moveSpeed: 0)) {
          currentFrame++;
          if (currentFrame == 1) {
            spriteIndex = _baseSprite + 5; // Aim
            setTics(10);
          } else if (currentFrame == 2) {
            spriteIndex = _baseSprite + 6; // Fire
            onPlaySound(attackSound);
            onDamagePlayer(damage);
            setTics(4);
          } else if (currentFrame == 3) {
            spriteIndex = _baseSprite + 7; // Recoil
            setTics(4);
          } else {
            // High chance to keep firing
            if (distance < 12.0 && hasLineOfSight(playerPosition, isWalkable)) {
              if (math.Random().nextDouble() > 0.2) {
                currentFrame = 1;
                setTics(10);
                return (movement: movement, newAngle: newAngle);
              }
            }
            state = EntityState.patrolling;
            currentFrame = 0;
            setTics(15);
          }
        }
        break;

      case EntityState.dead:
        if (isDying) {
          if (processTics(elapsedDeltaMs, moveSpeed: 0)) {
            currentFrame++;
            if (currentFrame < 4) {
              spriteIndex = (_baseSprite + 8) + currentFrame;
              setTics(5);
            } else {
              spriteIndex = _baseSprite + 11;
              isDying = false;
            }
          }
        } else {
          spriteIndex = _baseSprite + 11;
        }
        break;

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