import {
  CAMERA_FOLLOW_LERP,
  CAMERA_LERP_REFERENCE_MS,
  SHAKE_MAGNITUDE,
  SHAKE_DURATION_MS,
} from '../shared/constants';

/**
 * Camera controller with soft follow and screen shake.
 *
 * The camera tracks a target position (the hero's screen-space position)
 * and applies a smooth lerp to follow. Screen shake is applied as an
 * additive offset that decays over time.
 */
export class Camera {
  /** Current camera position (center of view in world-screen space) */
  x = 0;
  y = 0;

  /** Target position to follow */
  private targetX = 0;
  private targetY = 0;

  /** Shake state */
  private shakeTimeRemaining = 0;
  private shakeMagnitude = SHAKE_MAGNITUDE;
  private shakeOffsetX = 0;
  private shakeOffsetY = 0;

  /** Lerp factor (0..1) */
  private lerpFactor: number;

  constructor(lerpFactor = CAMERA_FOLLOW_LERP) {
    this.lerpFactor = lerpFactor;
  }

  /** Set the target position for the camera to follow */
  setTarget(x: number, y: number): void {
    this.targetX = x;
    this.targetY = y;
  }

  /** Snap the camera instantly to the target (no lerp) */
  snapToTarget(): void {
    this.x = this.targetX;
    this.y = this.targetY;
  }

  /** Trigger a screen shake effect (e.g., on hit) */
  shake(magnitude = SHAKE_MAGNITUDE, durationMs = SHAKE_DURATION_MS): void {
    this.shakeMagnitude = magnitude;
    this.shakeTimeRemaining = durationMs;
  }

  /** Update camera position. Call once per frame with delta time in ms. */
  update(dtMs: number): void {
    // Exponential smoothing; same factor as legacy per-frame lerp at ~60 Hz reference.
    const k =
      1 -
      Math.pow(1 - this.lerpFactor, dtMs / CAMERA_LERP_REFERENCE_MS);
    this.x += (this.targetX - this.x) * k;
    this.y += (this.targetY - this.y) * k;

    // Update screen shake
    if (this.shakeTimeRemaining > 0) {
      this.shakeTimeRemaining -= dtMs;
      const intensity = Math.max(0, this.shakeTimeRemaining / SHAKE_DURATION_MS);
      const mag = this.shakeMagnitude * intensity;
      this.shakeOffsetX = (Math.random() * 2 - 1) * mag;
      this.shakeOffsetY = (Math.random() * 2 - 1) * mag;
    } else {
      this.shakeOffsetX = 0;
      this.shakeOffsetY = 0;
    }
  }

  /** Get the final camera X including shake offset */
  get finalX(): number {
    return this.x + this.shakeOffsetX;
  }

  /** Get the final camera Y including shake offset */
  get finalY(): number {
    return this.y + this.shakeOffsetY;
  }

  /**
   * Apply camera transform to a PixiJS container.
   * The container is shifted so the camera target appears at screen center.
   */
  applyTo(container: { x: number; y: number }, screenWidth: number, screenHeight: number): void {
    container.x = screenWidth / 2 - this.finalX;
    container.y = screenHeight / 2 - this.finalY;
  }
}
