/**
 * Tank.js
 * Base class for all tanks (player and enemy).
 * Handles position, direction, movement, rendering, and collision box.
 */

const {
  TILE_SIZE,
  MAP_OFFSET_X,
  MAP_OFFSET_Y,
  MAP_WIDTH,
  MAP_HEIGHT,
  DIRECTION,
  DIR_VECTORS,
} = require('../base/GameGlobal');
const { drawTankSkin, DESIGN_HALF_SIZE } = require('./TankSkinRenderer');

class Tank {
  /**
   * @param {object} config
   * @param {number} config.x - Pixel X position (center).
   * @param {number} config.y - Pixel Y position (center).
   * @param {number} config.speed - Movement speed (pixels per frame at 60fps).
   * @param {number} config.hp - Hit points.
   * @param {string} config.color - Fill color.
   * @param {number} config.size - Tank size in pixels.
   * @param {number} [config.direction] - Initial direction.
   */
  constructor(config) {
    this.x = config.x;
    this.y = config.y;
    this.speed = config.speed || 2;
    this.hp = config.hp || 1;
    this.maxHp = config.hp || 1;
    this.color = config.color || '#FFFFFF';
    this.size = config.size || TILE_SIZE * 0.85;
    this.direction = config.direction !== undefined ? config.direction : DIRECTION.UP;
    this.alive = true;
    this.visible = true;

    // Collision size (can differ from visual size for large tanks like Boss)
    this.colliderSize = config.colliderSize || this.size;
    // Half-size for collision calculations
    this.halfSize = this.size / 2;
    this.colliderHalfSize = this.colliderSize / 2;
  }

  /**
   * Move the tank in a direction.
   * @param {number} dir - DIRECTION enum value.
   * @param {number} dt - Delta time in seconds.
   * @param {MapManager} mapManager - For collision checking.
   * @returns {boolean} Whether the tank actually moved.
   */
  move(dir, dt, mapManager) {
    if (!this.alive) return false;

    const prevDir = this.direction;
    this.direction = dir;

    // When changing direction, snap to nearest grid alignment first
    // and do NOT advance forward this frame — classic Battle City behavior.
    if (prevDir !== dir) {
      this._snapToGrid(prevDir);
      return false;
    }

    const vec = DIR_VECTORS[dir];
    const moveAmount = this.speed * dt * 60; // normalize to 60fps

    let newX = this.x + vec.dx * moveAmount;
    let newY = this.y + vec.dy * moveAmount;

    // Clamp to map boundaries instead of rejecting movement entirely.
    // This allows the tank to slide along the edge smoothly.
    const minX = MAP_OFFSET_X + this.colliderHalfSize;
    const minY = MAP_OFFSET_Y + this.colliderHalfSize;
    const maxX = MAP_OFFSET_X + MAP_WIDTH - this.colliderHalfSize;
    const maxY = MAP_OFFSET_Y + MAP_HEIGHT - this.colliderHalfSize;

    newX = Math.max(minX, Math.min(newX, maxX));
    newY = Math.max(minY, Math.min(newY, maxY));

    // If position didn't change after clamping, we're stuck at the boundary
    if (newX === this.x && newY === this.y) {
      return false;
    }

    // Calculate bounding box at clamped position
    const left = newX - this.colliderHalfSize;
    const top = newY - this.colliderHalfSize;

    // Terrain collision check
    if (mapManager && mapManager.rectCollidesWithTerrain(left, top, this.colliderSize, this.colliderSize)) {
      // Try to align to grid for smoother movement along walls
      return this._tryAlignedMove(dir, dt, mapManager);
    }

    this.x = newX;
    this.y = newY;
    return true;
  }

  /**
   * Snap the tank center to the nearest grid-cell center on the axis
   * of the OLD direction. This prevents the tank from "drifting" when
   * turning and ensures clean grid-aligned movement.
   * @param {number} oldDir - The direction the tank was facing before turning.
   * @private
   */
  _snapToGrid(oldDir) {
    const halfTile = TILE_SIZE / 2;
    const minX = MAP_OFFSET_X + this.colliderHalfSize;
    const minY = MAP_OFFSET_Y + this.colliderHalfSize;
    const maxX = MAP_OFFSET_X + MAP_WIDTH - this.colliderHalfSize;
    const maxY = MAP_OFFSET_Y + MAP_HEIGHT - this.colliderHalfSize;

    if (oldDir === DIRECTION.UP || oldDir === DIRECTION.DOWN) {
      // Was moving vertically → snap Y to nearest grid-cell center
      const rowExact = (this.y - MAP_OFFSET_Y - halfTile) / TILE_SIZE;
      const nearestRow = Math.round(rowExact);
      let alignedY = MAP_OFFSET_Y + nearestRow * TILE_SIZE + halfTile;
      // Clamp to map bounds so snapping doesn't push tank outside
      alignedY = Math.max(minY, Math.min(alignedY, maxY));
      if (Math.abs(alignedY - this.y) < TILE_SIZE * 0.5) {
        this.y = alignedY;
      }
    } else {
      // Was moving horizontally → snap X to nearest grid-cell center
      const colExact = (this.x - MAP_OFFSET_X - halfTile) / TILE_SIZE;
      const nearestCol = Math.round(colExact);
      let alignedX = MAP_OFFSET_X + nearestCol * TILE_SIZE + halfTile;
      // Clamp to map bounds so snapping doesn't push tank outside
      alignedX = Math.max(minX, Math.min(alignedX, maxX));
      if (Math.abs(alignedX - this.x) < TILE_SIZE * 0.5) {
        this.x = alignedX;
      }
    }
  }

  /**
   * Try to move with grid alignment (helps navigate around corners).
   * When blocked, find the nearest gap in the perpendicular axis and slide
   * the tank towards it so the player can smoothly pass through openings
   * between bricks — classic Battle City "snap-to-gap" behaviour.
   * @private
   */
  _tryAlignedMove(dir, dt, mapManager) {
    const moveAmount = this.speed * dt * 60;
    const vec = DIR_VECTORS[dir];
    const halfTile = TILE_SIZE / 2;
    const colliderHS = this.colliderHalfSize;
    const colliderS = this.colliderSize;

    if (dir === DIRECTION.UP || dir === DIRECTION.DOWN) {
      // Moving vertically but blocked — try to slide horizontally into a gap

      // Check two candidate column alignments (left-snap and right-snap)
      const colExact = (this.x - MAP_OFFSET_X - halfTile) / TILE_SIZE;
      const colLeft = Math.floor(colExact);
      const colRight = Math.ceil(colExact);

      const candidates = [];
      for (const col of [colLeft, colRight]) {
        const alignedX = MAP_OFFSET_X + col * TILE_SIZE + halfTile;
        const diffX = alignedX - this.x;
        // Only consider if the offset is within a comfortable snap threshold
        if (Math.abs(diffX) < TILE_SIZE * 0.55) {
          // Check whether moving in the desired direction would be clear at this aligned X
          const testX = alignedX;
          const testY = this.y + vec.dy * moveAmount;
          const left = testX - colliderHS;
          const top = testY - colliderHS;
          if (
            left >= MAP_OFFSET_X &&
            top >= MAP_OFFSET_Y &&
            left + colliderS <= MAP_OFFSET_X + MAP_WIDTH &&
            top + colliderS <= MAP_OFFSET_Y + MAP_HEIGHT &&
            !mapManager.rectCollidesWithTerrain(left, top, colliderS, colliderS)
          ) {
            candidates.push({ alignedX, diffX: Math.abs(diffX) });
          }
        }
      }

      if (candidates.length > 0) {
        // Pick the closest gap
        candidates.sort((a, b) => a.diffX - b.diffX);
        const best = candidates[0];
        const diffX = best.alignedX - this.x;
        const slideAmount = Math.min(Math.abs(diffX), moveAmount);
        this.x += Math.sign(diffX) * slideAmount;
        // Clamp to map bounds after sliding
        this.x = Math.max(MAP_OFFSET_X + colliderHS, Math.min(this.x, MAP_OFFSET_X + MAP_WIDTH - colliderHS));
        return false;
      }

      // No gap found — just do a basic grid-align slide
      const gridCol = Math.round(colExact);
      const alignedX = MAP_OFFSET_X + gridCol * TILE_SIZE + halfTile;
      const diffX = alignedX - this.x;
      if (Math.abs(diffX) < TILE_SIZE * 0.4) {
        const slideAmount = Math.min(Math.abs(diffX), moveAmount);
        this.x += Math.sign(diffX) * slideAmount;
        // Clamp to map bounds after sliding
        this.x = Math.max(MAP_OFFSET_X + colliderHS, Math.min(this.x, MAP_OFFSET_X + MAP_WIDTH - colliderHS));
      }
    } else {
      // Moving horizontally but blocked — try to slide vertically into a gap

      const rowExact = (this.y - MAP_OFFSET_Y - halfTile) / TILE_SIZE;
      const rowUp = Math.floor(rowExact);
      const rowDown = Math.ceil(rowExact);

      const candidates = [];
      for (const row of [rowUp, rowDown]) {
        const alignedY = MAP_OFFSET_Y + row * TILE_SIZE + halfTile;
        const diffY = alignedY - this.y;
        if (Math.abs(diffY) < TILE_SIZE * 0.55) {
          const testX = this.x + vec.dx * moveAmount;
          const testY = alignedY;
          const left = testX - colliderHS;
          const top = testY - colliderHS;
          if (
            left >= MAP_OFFSET_X &&
            top >= MAP_OFFSET_Y &&
            left + colliderS <= MAP_OFFSET_X + MAP_WIDTH &&
            top + colliderS <= MAP_OFFSET_Y + MAP_HEIGHT &&
            !mapManager.rectCollidesWithTerrain(left, top, colliderS, colliderS)
          ) {
            candidates.push({ alignedY, diffY: Math.abs(diffY) });
          }
        }
      }

      if (candidates.length > 0) {
        candidates.sort((a, b) => a.diffY - b.diffY);
        const best = candidates[0];
        const diffY = best.alignedY - this.y;
        const slideAmount = Math.min(Math.abs(diffY), moveAmount);
        this.y += Math.sign(diffY) * slideAmount;
        // Clamp to map bounds after sliding
        this.y = Math.max(MAP_OFFSET_Y + colliderHS, Math.min(this.y, MAP_OFFSET_Y + MAP_HEIGHT - colliderHS));
        return false;
      }

      // No gap found — basic grid-align slide
      const gridRow = Math.round(rowExact);
      const alignedY = MAP_OFFSET_Y + gridRow * TILE_SIZE + halfTile;
      const diffY = alignedY - this.y;
      if (Math.abs(diffY) < TILE_SIZE * 0.4) {
        const slideAmount = Math.min(Math.abs(diffY), moveAmount);
        this.y += Math.sign(diffY) * slideAmount;
        // Clamp to map bounds after sliding
        this.y = Math.max(MAP_OFFSET_Y + colliderHS, Math.min(this.y, MAP_OFFSET_Y + MAP_HEIGHT - colliderHS));
      }
    }

    return false;
  }

  /**
   * Take damage.
   * @param {number} [amount=1]
   * @returns {boolean} Whether the tank was destroyed.
   */
  takeDamage(amount = 1) {
    if (!this.alive) return false;
    this.hp -= amount;
    if (this.hp <= 0) {
      this.hp = 0;
      this.alive = false;
      return true; // destroyed
    }
    return false;
  }

  /**
   * Render the tank.
   * @param {CanvasRenderingContext2D} ctx
   */
  render(ctx) {
    if (!this.alive || !this.visible) return;

    ctx.save();
    ctx.translate(this.x, this.y);

    // Rotate based on direction
    const angles = {
      [DIRECTION.UP]: 0,
      [DIRECTION.DOWN]: Math.PI,
      [DIRECTION.LEFT]: -Math.PI / 2,
      [DIRECTION.RIGHT]: Math.PI / 2,
    };
    ctx.rotate(angles[this.direction]);

    // ★ Unified skin path — any tank with a skin id uses the SAME drawing
    //    code as the SkinScene preview. Scale to match the actual tank size.
    //    Clip laterally to the collision box so wide tracks / decorations
    //    don't make the tank look wider than non-skinned tanks. Leave the
    //    top/bottom un-clipped so the barrel can extend naturally (same as
    //    legacy rendering).
    if (this._skinId) {
      const t = (typeof performance !== 'undefined' ? performance.now() : Date.now()) / 1000;
      const k = this.halfSize / DESIGN_HALF_SIZE;
      // Clip: lateral bounds = collision box; vertical = generous to allow barrel
      const barrelExtra = this.size * 0.55; // same as legacy barrelH
      ctx.beginPath();
      ctx.rect(-this.halfSize, -this.halfSize - barrelExtra, this.size, this.size + barrelExtra * 2);
      ctx.clip();
      ctx.save();
      ctx.scale(k, k);
      drawTankSkin(ctx, this._skinId, this._skinColors, t);
      ctx.restore();
      // Gold border for local player identification
      if (this._isLocal) {
        ctx.strokeStyle = '#FFD700';
        ctx.lineWidth = 2;
        ctx.strokeRect(-this.halfSize, -this.halfSize, this.size, this.size);
        // Also outline the barrel area
        ctx.strokeRect(-this.size * 0.15 / 2, -this.halfSize - barrelExtra, this.size * 0.15, barrelExtra);
      }
      ctx.restore();
      return;
    }

    // ── Legacy fallback for tanks without a skin id (enemy AI, etc.) ──
    const hs = this.halfSize;
    let bodyColor = this.color;
    let turretColor = this._darkenColor(this.color, 0.3);
    let trackColor = this._darkenColor(this.color, 0.4);

    if (this._skinColors) {
      bodyColor = this._skinColors.body || bodyColor;
      turretColor = this._skinColors.turret || turretColor;
      trackColor = this._skinColors.track || trackColor;
    }

    // Tank body
    ctx.fillStyle = bodyColor;
    ctx.fillRect(-hs, -hs, this.size, this.size);

    // Tank turret (barrel)
    const barrelW = this.size * 0.15;
    const barrelH = this.size * 0.5;
    ctx.fillStyle = turretColor;
    ctx.fillRect(-barrelW / 2, -hs - barrelH, barrelW, barrelH);

    // Tank body detail (center square)
    const innerSize = this.size * 0.4;
    ctx.fillStyle = this._darkenColor(bodyColor, 0.2);
    ctx.fillRect(-innerSize / 2, -innerSize / 2, innerSize, innerSize);

    // Tracks
    const trackW = this.size * 0.12;
    ctx.fillStyle = trackColor;
    ctx.fillRect(-hs, -hs, trackW, this.size);
    ctx.fillRect(hs - trackW, -hs, trackW, this.size);

    // Gold border for local player identification
    if (this._isLocal) {
      ctx.strokeStyle = '#FFD700';
      ctx.lineWidth = 2;
      ctx.strokeRect(-hs, -hs, this.size, this.size);
      // Barrel outline
      ctx.strokeRect(-barrelW / 2, -hs - barrelH, barrelW, barrelH);
    }

    ctx.restore();
  }

  /**
   * Get the axis-aligned bounding box.
   * @returns {{x: number, y: number, w: number, h: number}}
   */
  getBounds() {
    return {
      x: this.x - this.colliderHalfSize,
      y: this.y - this.colliderHalfSize,
      w: this.colliderSize,
      h: this.colliderSize,
    };
  }

  /**
   * Check collision with another tank.
   * @param {Tank} other
   * @returns {boolean}
   */
  collidesWith(other) {
    if (!this.alive || !other.alive) return false;
    const a = this.getBounds();
    const b = other.getBounds();
    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
    );
  }

  /**
   * Darken a hex color.
   * @param {string} hex
   * @param {number} factor - 0 to 1
   * @returns {string}
   */
  _darkenColor(hex, factor) {
    const r = parseInt(hex.slice(1, 3), 16);
    const g = parseInt(hex.slice(3, 5), 16);
    const b = parseInt(hex.slice(5, 7), 16);
    const dr = Math.floor(r * (1 - factor));
    const dg = Math.floor(g * (1 - factor));
    const db = Math.floor(b * (1 - factor));
    return `rgb(${dr},${dg},${db})`;
  }
}

module.exports = Tank;
