import 'dart:developer';
import 'dart:math' as math;

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

/// Orchestrates the state and interaction of all doors within a level.
///
/// This manager maintains a map of [Door] objects and ensures that when a
/// door's state changes (e.g., starts closing), the appropriate global
/// game events (like sound effects) are triggered.
class DoorManager {
  /// A lookup table for doors, keyed by packed grid coordinates.
  final Map<int, Door> doors = {};

  /// Callback used to trigger sound effects without tight coupling
  /// to a specific audio engine implementation.
  final void Function(SoundEffect effect) onPlaySound;

  static int _key(int x, int y) => ((y & 0xFFFF) << 16) | (x & 0xFFFF);

  DoorManager({required this.onPlaySound});

  /// Scans the [wallGrid] for tile IDs >= 90 and initializes [Door] instances.
  void initDoors(SpriteMap wallGrid) {
    doors.clear();
    for (int y = 0; y < wallGrid.length; y++) {
      for (int x = 0; x < wallGrid[y].length; x++) {
        int id = wallGrid[y][x];
        if (id >= 90) {
          doors[_key(x, y)] = Door(x: x, y: y, mapId: id);
        }
      }
    }
  }

  /// Updates all managed doors and plays the "close" sound if a door
  /// naturally begins its closing cycle.
  void update(Duration elapsed) {
    for (final door in doors.values) {
      final newState = door.update(elapsed.inMilliseconds);

      if (newState == DoorState.closing) {
        onPlaySound(SoundEffect.closeDoor);
      }
    }
  }

  /// Handles player-initiated interaction with doors based on facing direction.
  ///
  /// Returns the door tile coordinates when a door actually transitions/opening
  /// starts, otherwise returns `null`.
  ({int x, int y})? handleInteraction(
    double playerX,
    double playerY,
    double playerAngle,
  ) {
    int targetX = (playerX + math.cos(playerAngle)).toInt();
    int targetY = (playerY + math.sin(playerAngle)).toInt();

    final int key = _key(targetX, targetY);
    if (doors.containsKey(key) && doors[key]!.interact()) {
      onPlaySound(SoundEffect.openDoor);
      log('[DEBUG] Player opened door at ($targetX, $targetY)');
      return (x: targetX, y: targetY);
    }

    return null;
  }

  /// Attempted by AI entities to open a door blocking their path.
  void tryOpenDoor(int x, int y) {
    final int key = _key(x, y);
    // AI only interacts if the door is currently fully closed (offset == 0).
    if (doors.containsKey(key) && doors[key]!.offset == 0.0) {
      if (doors[key]!.interact()) {
        onPlaySound(SoundEffect.openDoor);
      }
    }
  }

  /// Returns the current open offset for the door at [x],[y].
  /// Returns 0.0 when there is no active/opening door at that tile.
  double doorOffsetAt(int x, int y) {
    return doors[_key(x, y)]?.offset ?? 0.0;
  }

  /// Returns true if the door at [x], [y] is sufficiently open for
  /// an entity (player or enemy) to walk through.
  bool isDoorOpenEnough(int x, int y) {
    final int key = _key(x, y);
    if (doors.containsKey(key)) {
      // 0.7 (70% open) is the standard collision threshold.
      return doors[key]!.offset > 0.7;
    }
    return false;
  }
}
