import 'dart:math' as math;

import 'package:wolf_3d_dart/wolf_3d_data_types.dart';

/// Represents a secret wall that slides when triggered by the player.
class Pushwall {
  int x;
  int y;
  int mapId;
  int dirX = 0;
  int dirY = 0;
  double offset = 0.0;
  int tilesMoved = 0;

  Pushwall(this.x, this.y, this.mapId);
}

/// Manages the detection and real-time movement of secret pushwalls.
///
/// Only one pushwall can be active (moving) at any given time.
class PushwallManager {
  /// Creates a pushwall manager with an optional sound dispatch callback.
  PushwallManager({this.onPlaySound});

  /// Optional callback used to emit audio cues when pushwalls activate.
  final void Function(SoundEffect effect)? onPlaySound;

  final Map<String, Pushwall> pushwalls = {};
  Pushwall? activePushwall;

  /// Populates the lookup table using the level's object grid.
  void initPushwalls(SpriteMap wallGrid, SpriteMap objectGrid) {
    pushwalls.clear();
    activePushwall = null;

    for (int y = 0; y < objectGrid.length; y++) {
      for (int x = 0; x < objectGrid[y].length; x++) {
        // Pushwalls are identified by a specific trigger ID in the object layer.
        if (objectGrid[y][x] == MapObject.pushwallTrigger) {
          pushwalls['$x,$y'] = Pushwall(x, y, wallGrid[y][x]);
        }
      }
    }
  }

  /// Logic for sliding the wall and updating the physical [wallGrid] for collisions.
  void update(Duration elapsed, SpriteMap wallGrid) {
    if (activePushwall == null) return;
    final pw = activePushwall!;

    // Movement speed matches the original DOS executable (roughly 0.54 tiles/sec).
    const double originalSpeed = 0.546875;
    pw.offset += (elapsed.inMilliseconds / 1000.0) * originalSpeed;

    // Handle the transition between grid tiles.
    if (pw.offset >= 1.0) {
      pw.offset -= 1.0;
      pw.tilesMoved++;

      int nextX = pw.x + pw.dirX;
      int nextY = pw.y + pw.dirY;

      // Update structural grid: the wall is now "solid" in the new tile.
      wallGrid[nextY][nextX] = pw.mapId;

      // The previous tile is now walkable.
      wallGrid[pw.y][pw.x] = 0;

      // Update lookup keys
      pushwalls.remove('${pw.x},${pw.y}');
      pw.x = nextX;
      pw.y = nextY;
      pushwalls['${pw.x},${pw.y}'] = pw;

      // Determine if movement is blocked by the world boundary or another solid tile.
      bool blocked = false;
      int checkX = pw.x + pw.dirX;
      int checkY = pw.y + pw.dirY;

      if (checkX < 0 ||
          checkX >= wallGrid[0].length ||
          checkY < 0 ||
          checkY >= wallGrid.length) {
        blocked = true;
      } else if (wallGrid[checkY][checkX] != 0) {
        blocked = true;
      }

      // Secret walls stop after moving 2 tiles or hitting an obstruction.
      if (pw.tilesMoved >= 2 || blocked) {
        activePushwall = null;
        pw.offset = 0.0;
      }
    }
  }

  /// Triggers a pushwall based on player proximity and facing direction.
  void handleInteraction(
    double playerX,
    double playerY,
    double playerAngle,
    SpriteMap wallGrid,
  ) {
    if (activePushwall != null) return;

    int targetX = (playerX + math.cos(playerAngle)).toInt();
    int targetY = (playerY + math.sin(playerAngle)).toInt();

    String key = '$targetX,$targetY';
    if (pushwalls.containsKey(key)) {
      final pw = pushwalls[key]!;

      // Determine push direction (X or Y) based on which axis the player is closer to.
      double dx = (targetX + 0.5) - playerX;
      double dy = (targetY + 0.5) - playerY;

      if (dx.abs() > dy.abs()) {
        pw.dirX = dx > 0 ? 1 : -1;
        pw.dirY = 0;
      } else {
        pw.dirX = 0;
        pw.dirY = dy > 0 ? 1 : -1;
      }

      // Only start the push if the space immediately behind the wall is empty.
      int checkX = targetX + pw.dirX;
      int checkY = targetY + pw.dirY;
      if (wallGrid[checkY][checkX] == 0) {
        activePushwall = pw;
        onPlaySound?.call(SoundEffect.pushWall);
      }
    }
  }
}
