/**
 * CollisionManager.js
 * Handles all collision detection between game entities each frame:
 * bullet↔terrain, bullet↔tank, bullet↔bullet, bullet↔base, tank↔tank.
 */

const {
  TILE_SIZE,
  MAP_OFFSET_X,
  MAP_OFFSET_Y,
  MAP_WIDTH,
  MAP_HEIGHT,
  TERRAIN,
  GRID_ROWS,
  GRID_COLS,
} = require('../base/GameGlobal');

class CollisionManager {
  /**
   * @param {object} deps
   * @param {import('../managers/MapManager')} deps.mapManager
   * @param {Function} deps.onExplosion - Callback(x, y, isBig) to spawn explosion.
   * @param {import('../base/EventBus')} deps.eventBus
   */
  constructor(deps) {
    this._map = deps.mapManager;
    this._onExplosion = deps.onExplosion;
    this._eventBus = deps.eventBus;
  }

  /**
   * Run all collision checks for one frame.
   * @param {object} entities
   * @param {import('../entities/PlayerTank')} entities.player
   * @param {Array<import('../entities/Tank')>} entities.enemies
   * @param {Array<import('../entities/Bullet')>} entities.bullets
   */
  update(entities) {
    const { player, enemies, bullets } = entities;
    const aliveBullets = bullets.filter((b) => b.alive);
    const aliveEnemies = enemies.filter((e) => e.alive);

    // 1. Bullet ↔ Terrain / Base
    this._checkBulletTerrain(aliveBullets);

    // 2. Bullet ↔ Tank
    this._checkBulletTank(aliveBullets, player, aliveEnemies);

    // 3. Bullet ↔ Bullet (player vs enemy)
    this._checkBulletBullet(aliveBullets);

    // 4. Tank ↔ Tank (player vs enemies)
    // Note: In classic tank game, tanks block each other but don't destroy on contact.
    // Player death on contact is optional - we implement it per requirements.
    this._checkTankTank(player, aliveEnemies);
  }

  /**
   * Check bullets against terrain tiles.
   * @private
   */
  _checkBulletTerrain(bullets) {
    for (const bullet of bullets) {
      if (!bullet.alive) continue;

      const { row, col } = this._map.pixelToGrid(bullet.x, bullet.y);

      // Out of map bounds
      if (row < 0 || row >= GRID_ROWS || col < 0 || col >= GRID_COLS) {
        bullet.destroy();
        this._onExplosion(bullet.x, bullet.y, false);
        continue;
      }

      const terrain = this._map.getTerrain(row, col);

      if (terrain === TERRAIN.BRICK) {
        // Destroy brick
        this._map.setTerrain(row, col, TERRAIN.EMPTY);

        // Lv3 bullets destroy adjacent bricks too
        if (bullet.canBreakSteel) {
          this._destroyAdjacentBricks(row, col, bullet.direction);
        }

        bullet.destroy();
        this._onExplosion(bullet.x, bullet.y, false);
      } else if (terrain === TERRAIN.BASE_WALL) {
        // Player bullets are immune to base wall
        if (bullet.owner === 'player') {
          bullet.destroy();
          continue;
        }

        // Base wall has HP - use bulletHitTerrain for HP tracking
        const result = this._map.bulletHitTerrain(row, col, bullet.canBreakSteel);

        // Lv3 bullets also damage adjacent base walls
        if (bullet.canBreakSteel) {
          this._destroyAdjacentBricks(row, col, bullet.direction);
        }

        bullet.destroy();
        this._onExplosion(bullet.x, bullet.y, false);
      } else if (terrain === TERRAIN.STEEL) {
        if (bullet.canBreakSteel) {
          this._map.setTerrain(row, col, TERRAIN.EMPTY);
          bullet.destroy();
          this._onExplosion(bullet.x, bullet.y, false);
        } else {
          // Bullet blocked by steel
          bullet.destroy();
          this._onExplosion(bullet.x, bullet.y, false);
        }
      } else if (terrain === TERRAIN.BASE) {
        // Player bullets are immune to base
        if (bullet.owner === 'player') {
          bullet.destroy();
          continue;
        }

        // Base hit by enemy bullet!
        this._map._baseDestroyed = true;
        bullet.destroy();
        this._onExplosion(bullet.x, bullet.y, true);
        this._eventBus.emit('base:destroyed');
      }
      // RIVER and FOREST: bullets pass through
    }
  }

  /**
   * Destroy adjacent bricks for Lv3 bullet splash.
   * @private
   */
  _destroyAdjacentBricks(row, col, direction) {
    const { DIRECTION } = require('../base/GameGlobal');
    const offsets =
      direction === DIRECTION.UP || direction === DIRECTION.DOWN
        ? [[0, -1], [0, 1]] // horizontal neighbors
        : [[-1, 0], [1, 0]]; // vertical neighbors

    for (const [dr, dc] of offsets) {
      const nr = row + dr;
      const nc = col + dc;
      const t = this._map.getTerrain(nr, nc);
      if (t === TERRAIN.BRICK) {
        this._map.setTerrain(nr, nc, TERRAIN.EMPTY);
      } else if (t === TERRAIN.BASE_WALL) {
        // Base wall has HP - use bulletHitTerrain for HP tracking
        this._map.bulletHitTerrain(nr, nc, false);
      }
    }
  }

  /**
   * Check bullets against tanks.
   * @private
   */
  _checkBulletTank(bullets, player, enemies) {
    for (const bullet of bullets) {
      if (!bullet.alive) continue;

      const bb = bullet.getBounds();

      if (bullet.owner === 'player') {
        // Player bullet hits enemy
        for (const enemy of enemies) {
          if (!enemy.alive) continue;
          const eb = enemy.getBounds();
          if (this._rectsOverlap(bb, eb)) {
            const destroyed = enemy.takeDamage(1);
            bullet.destroy();
            if (destroyed) {
              this._onExplosion(enemy.x, enemy.y, true);
              this._eventBus.emit('enemy:destroyed', { enemy });
            } else {
              this._onExplosion(bullet.x, bullet.y, false);
              this._eventBus.emit('enemy:hit', { enemy });
              if (GameGlobal.audioManager) GameGlobal.audioManager.playSFX('hit');
            }
            break;
          }
        }
      } else {
        // Enemy bullet hits player
        if (player && player.alive) {
          const pb = player.getBounds();
          if (this._rectsOverlap(bb, pb)) {
            const destroyed = player.takeDamage(1);
            bullet.destroy();
            if (destroyed) {
              this._onExplosion(player.x, player.y, true);
              this._eventBus.emit('player:destroyed');
            } else {
              this._onExplosion(bullet.x, bullet.y, false);
            }
          }
        }
      }
    }
  }

  /**
   * Check bullet-bullet collisions (player vs enemy bullets cancel out).
   * @private
   */
  _checkBulletBullet(bullets) {
    for (let i = 0; i < bullets.length; i++) {
      if (!bullets[i].alive) continue;
      for (let j = i + 1; j < bullets.length; j++) {
        if (!bullets[j].alive) continue;

        // Only cancel if different owners
        if (bullets[i].owner === bullets[j].owner) continue;

        const a = bullets[i].getBounds();
        const b = bullets[j].getBounds();

        if (this._rectsOverlap(a, b)) {
          const mx = (bullets[i].x + bullets[j].x) / 2;
          const my = (bullets[i].y + bullets[j].y) / 2;
          bullets[i].destroy();
          bullets[j].destroy();
          this._onExplosion(mx, my, false);
        }
      }
    }
  }

  /**
   * Check tank-tank collisions.
   * Classic Battle City behavior: tanks block each other on contact,
   * they are pushed apart so they don't overlap. No damage is dealt.
   * @private
   */
  _checkTankTank(player, enemies) {
    if (!player || !player.alive) return;

    for (const enemy of enemies) {
      if (!enemy.alive) continue;
      if (player.collidesWith(enemy)) {
        // Push tanks apart — resolve overlap along the axis with smallest penetration
        this._separateTanks(player, enemy);
      }
    }

    // Also prevent enemies from overlapping each other
    for (let i = 0; i < enemies.length; i++) {
      if (!enemies[i].alive) continue;
      for (let j = i + 1; j < enemies.length; j++) {
        if (!enemies[j].alive) continue;
        if (enemies[i].collidesWith(enemies[j])) {
          this._separateTanks(enemies[i], enemies[j]);
        }
      }
    }
  }

  /**
   * Check if a tank position is valid (within map bounds and not colliding with terrain).
   * @private
   */
  _isPositionValid(tank, x, y) {
    const hs = tank.colliderHalfSize;
    const left = x - hs;
    const top = y - hs;
    const right = x + hs;
    const bottom = y + hs;

    // Map boundary check
    if (
      left < MAP_OFFSET_X ||
      top < MAP_OFFSET_Y ||
      right > MAP_OFFSET_X + MAP_WIDTH ||
      bottom > MAP_OFFSET_Y + MAP_HEIGHT
    ) {
      return false;
    }

    // Terrain collision check
    if (this._map.rectCollidesWithTerrain(left, top, tank.colliderSize, tank.colliderSize)) {
      return false;
    }

    return true;
  }

  /**
   * Push two overlapping tanks apart along the axis of least penetration.
   * Validates new positions against map bounds and terrain before applying.
   * @private
   */
  _separateTanks(tankA, tankB) {
    const a = tankA.getBounds();
    const b = tankB.getBounds();

    // Calculate overlap on each axis
    const overlapX = Math.min(a.x + a.w, b.x + b.w) - Math.max(a.x, b.x);
    const overlapY = Math.min(a.y + a.h, b.y + b.h) - Math.max(a.y, b.y);

    if (overlapX <= 0 || overlapY <= 0) return; // no real overlap

    if (overlapX < overlapY) {
      // Separate along X axis
      const sign = tankA.x < tankB.x ? -1 : 1;
      const halfPush = overlapX / 2;

      const newAx = tankA.x + sign * halfPush;
      const newBx = tankB.x - sign * halfPush;

      const aValid = this._isPositionValid(tankA, newAx, tankA.y);
      const bValid = this._isPositionValid(tankB, newBx, tankB.y);

      if (aValid && bValid) {
        tankA.x = newAx;
        tankB.x = newBx;
      } else if (aValid && !bValid) {
        // B can't move, push A the full overlap
        const fullAx = tankA.x + sign * overlapX;
        if (this._isPositionValid(tankA, fullAx, tankA.y)) {
          tankA.x = fullAx;
        } else {
          tankA.x = newAx; // at least push half
        }
      } else if (!aValid && bValid) {
        // A can't move, push B the full overlap
        const fullBx = tankB.x - sign * overlapX;
        if (this._isPositionValid(tankB, fullBx, tankB.y)) {
          tankB.x = fullBx;
        } else {
          tankB.x = newBx; // at least push half
        }
      }
      // If neither is valid, don't move either (both stuck)
    } else {
      // Separate along Y axis
      const sign = tankA.y < tankB.y ? -1 : 1;
      const halfPush = overlapY / 2;

      const newAy = tankA.y + sign * halfPush;
      const newBy = tankB.y - sign * halfPush;

      const aValid = this._isPositionValid(tankA, tankA.x, newAy);
      const bValid = this._isPositionValid(tankB, tankB.x, newBy);

      if (aValid && bValid) {
        tankA.y = newAy;
        tankB.y = newBy;
      } else if (aValid && !bValid) {
        const fullAy = tankA.y + sign * overlapY;
        if (this._isPositionValid(tankA, tankA.x, fullAy)) {
          tankA.y = fullAy;
        } else {
          tankA.y = newAy;
        }
      } else if (!aValid && bValid) {
        const fullBy = tankB.y - sign * overlapY;
        if (this._isPositionValid(tankB, tankB.x, fullBy)) {
          tankB.y = fullBy;
        } else {
          tankB.y = newBy;
        }
      }
      // If neither is valid, don't move either (both stuck)
    }
  }

  /**
   * AABB overlap test.
   * @private
   */
  _rectsOverlap(a, b) {
    return (
      a.x < b.x + b.w &&
      a.x + a.w > b.x &&
      a.y < b.y + b.h &&
      a.y + a.h > b.y
    );
  }
}

module.exports = CollisionManager;
