// You can write more code here

import RewindableSprite, { TimeMode } from "../components/RewindableSprite";
import Enemy from "./Enemy";

/* START OF COMPILED CODE */

/* START-USER-IMPORTS */
/* END-USER-IMPORTS */

export default class Bullet extends RewindableSprite {
  constructor(
    scene: Phaser.Scene,
    x?: number,
    y?: number,
    texture?: string,
    frame?: number | string
  ) {
    super(scene, x ?? 638, y ?? 395, texture || "bullet", frame);

    /* START-USER-CTR-CODE */
    // Initialize bullet properties
    this.initializeBullet();
    /* END-USER-CTR-CODE */
  }

  /* START-USER-CODE */

  // Bullet properties
  private targetX: number = 0;
  private targetY: number = 0;
  private targetEnemy: Enemy | null = null;
  private speed: number = 500; // pixels per second
  private damage: number = 1; // damage dealt to enemies
  private maxLifetime: number = 3000; // 3 seconds max lifetime
  private creationTime: number = 0;
  private velocityX: number = 0;
  private velocityY: number = 0;
  private hasHitTarget: boolean = false;
  private isDead: boolean = false; // Track dead state for rewind
  private deathTime: number = 0; // Timestamp when bullet died

  // Performance optimization: use squared distance for collision detection
  private collisionRadius: number = 12; // Collision radius in pixels
  private collisionRadiusSquared: number =
    this.collisionRadius * this.collisionRadius;

  /**
   * Override getCustomStateData to include bullet-specific state
   */
  protected getCustomStateData(): Record<string, any> {
    return {
      isDead: this.isDead,
      hasHitTarget: this.hasHitTarget,
      targetX: this.targetX,
      targetY: this.targetY,
      velocityX: this.velocityX,
      velocityY: this.velocityY,
      creationTime: this.creationTime,
    };
  }

  /**
   * Override applyCustomStateData to restore bullet-specific state
   */
  protected applyCustomStateData(customData: Record<string, any>): void {
    if (customData.isDead !== undefined) {
      this.isDead = customData.isDead;
    }

    if (customData.hasHitTarget !== undefined) {
      this.hasHitTarget = customData.hasHitTarget;
    }

    if (customData.targetX !== undefined) {
      this.targetX = customData.targetX;
    }

    if (customData.targetY !== undefined) {
      this.targetY = customData.targetY;
    }

    if (customData.velocityX !== undefined) {
      this.velocityX = customData.velocityX;
    }

    if (customData.velocityY !== undefined) {
      this.velocityY = customData.velocityY;
    }

    if (customData.creationTime !== undefined) {
      this.creationTime = customData.creationTime;
    }

    // Update visibility and behavior based on restored dead state
    this.updateVisibilityBasedOnDeadState();
  }

  /**
   * Initialize bullet properties
   */
  private initializeBullet(): void {
    this.creationTime = Date.now();

    // Set initial scale or other properties
    this.setScale(1);
    this.setOrigin(0.5, 0.5);
  }

  /**
   * Set the bullet's target position and enemy
   * @param x Target X coordinate
   * @param y Target Y coordinate
   * @param enemy Target enemy (optional)
   */
  public setTarget(x: number, y: number, enemy?: Enemy): void {
    this.targetX = x;
    this.targetY = y;
    this.targetEnemy = enemy || null;

    // Calculate velocity vector towards target
    const deltaX = this.targetX - this.x;
    const deltaY = this.targetY - this.y;
    const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);

    if (distance > 0) {
      // Normalize and apply speed
      this.velocityX = (deltaX / distance) * this.speed;
      this.velocityY = (deltaY / distance) * this.speed;

      // Rotate bullet to face target
      this.rotation = Math.atan2(deltaY, deltaX);
    }
  }

  /**
   * Set bullet speed
   * @param speed Speed in pixels per second
   */
  public setSpeed(speed: number): void {
    this.speed = speed;

    // Recalculate velocity if we have a target
    if (this.targetX !== 0 || this.targetY !== 0) {
      this.setTarget(this.targetX, this.targetY, this.targetEnemy || undefined);
    }
  }

  /**
   * Set bullet damage
   * @param damage Damage dealt to enemies
   */
  public setDamage(damage: number): void {
    this.damage = damage;
  }

  /**
   * Set collision radius for the bullet
   * @param radius Collision radius in pixels
   */
  public setCollisionRadius(radius: number): void {
    this.collisionRadius = radius;
    this.collisionRadiusSquared = radius * radius;
  }

  /**
   * Override updateForward to handle bullet movement and collision
   */
  protected updateForward(time: number, delta: number): void {
    // Skip update if bullet is dead or destroyed
    if (this.isDead || !this.active || !this.scene) {
      return;
    }

    // Check if bullet has exceeded its lifetime
    if (Date.now() - this.creationTime > this.maxLifetime) {
      console.debug("Bullet exceeded lifetime, marking as dead");
      this.markAsDead();
      return;
    }

    // Move bullet
    const deltaSeconds = delta / 1000;
    this.x += this.velocityX * deltaSeconds;
    this.y += this.velocityY * deltaSeconds;

    // Check for collision with enemies
    this.checkCollisionWithEnemies();

    // Check if bullet is off screen (with some buffer)
    if (this.isOffScreen()) {
      console.debug("Bullet went off screen, marking as dead");
      this.markAsDead();
      return;
    }
  }

  /**
   * Check collision with all enemies in the scene
   * Performance optimized for many enemies
   */
  private checkCollisionWithEnemies(): void {
    // Skip collision if bullet is already dead
    if (this.isDead) return;

    const enemies = this.getAllEnemies();

    for (const enemy of enemies) {
      if (this.checkCollisionWithEnemy(enemy)) {
        this.hitEnemy(enemy);
        return; // Exit after first hit
      }
    }
  }

  /**
   * Check collision with a specific enemy using squared distance
   * @param enemy Enemy to check collision with
   */
  private checkCollisionWithEnemy(enemy: Enemy): boolean {
    // Skip if bullet is dead or enemy can't be targeted
    if (this.isDead || !enemy.canBeTargeted()) {
      return false;
    }

    // Use squared distance for performance
    const dx = this.x - enemy.x;
    const dy = this.y - enemy.y;
    const distanceSquared = dx * dx + dy * dy;

    return distanceSquared <= this.collisionRadiusSquared;
  }

  /**
   * Handle hitting an enemy
   * @param enemy Enemy that was hit
   */
  private hitEnemy(enemy: Enemy): void {
    if (this.isDead) return; // Prevent multiple hits

    // Deal damage to enemy (1 HP by default, can be configured by tower)
    enemy.takeDamage(this.damage);

    console.debug(
      `Bullet hit enemy at (${enemy.x}, ${
        enemy.y
      }), enemy HP: ${enemy.getHp()}/${enemy.getMaxHp()}`
    );

    // Mark this bullet as dead instead of destroying it
    this.markAsDead();
  }

  /**
   * Get all enemies in the scene efficiently
   */
  private getAllEnemies(): Enemy[] {
    const children = this.scene.children.getAll();
    const enemies: Enemy[] = [];

    // Use for loop for better performance than filter
    for (let i = 0; i < children.length; i++) {
      const child = children[i];
      if (child instanceof Enemy) {
        enemies.push(child);
      }
    }

    return enemies;
  }

  /**
   * Check if bullet is off screen
   */
  private isOffScreen(): boolean {
    // Safety check for destroyed bullets
    if (!this.scene || !this.scene.cameras) {
      return true; // Treat as off-screen if scene is not available
    }

    const camera = this.scene.cameras.main;
    const buffer = 50; // Buffer zone outside visible area

    return (
      this.x < -buffer ||
      this.x > camera.width + buffer ||
      this.y < -buffer ||
      this.y > camera.height + buffer
    );
  }

  /**
   * Get the target enemy
   */
  public getTargetEnemy(): Enemy | null {
    return this.targetEnemy;
  }

  /**
   * Check if bullet has hit its target
   */
  public hasHit(): boolean {
    return this.hasHitTarget;
  }

  /**
   * Get bullet velocity
   */
  public getVelocity(): { x: number; y: number } {
    return { x: this.velocityX, y: this.velocityY };
  }

  /**
   * Mark bullet as dead instead of destroying it
   */
  private markAsDead(): void {
    if (!this.isDead) {
      this.isDead = true;
      this.deathTime = Date.now(); // Record when bullet died
      this.hasHitTarget = true;
      this.updateVisibilityBasedOnDeadState();
      console.debug("Bullet marked as dead");
    }
  }

  /**
   * Check if bullet is dead
   */
  public isDead_(): boolean {
    return this.isDead;
  }

  /**
   * Get the time when bullet died (0 if not dead)
   */
  public getDeathTime(): number {
    return this.deathTime;
  }

  /**
   * Check if bullet can move and collide (not dead)
   */
  public canMove(): boolean {
    return !this.isDead && this.active && this.visible;
  }

  /**
   * Update visibility and behavior based on dead state
   */
  private updateVisibilityBasedOnDeadState(): void {
    if (this.isDead) {
      // Make bullet invisible and stop all behavior
      this.setVisible(false);
    } else {
      // Make bullet visible and resume behavior
      this.setVisible(true);
    }
  }

  /**
   * Override setTimeMode to handle bullet behavior when returning from rewind
   */
  public setTimeMode(mode: TimeMode): void {
    const previousMode = this.getTimeMode();

    if (mode === TimeMode.FORWARD && previousMode === TimeMode.REWIND) {
      // When returning from rewind mode, dead state is automatically restored by applyCustomStateData
      // We just need to ensure visibility is correctly set
      this.updateVisibilityBasedOnDeadState();
    }

    // Call parent method
    super.setTimeMode(mode);
  }

  /**
   * Override rewindTime to handle destruction when rewound to beginning
   */
  public rewindTime(amount: number): void {
    // Call parent method first
    super.rewindTime(amount);

    // Check if we've been rewound to the very beginning
    if (
      this.getTimeMode() === TimeMode.REWIND &&
      this.getCurrentTimeOffset() === 0
    ) {
      // If we're at the beginning of our existence, destroy this bullet
      console.debug("Bullet rewound to beginning - destroying");
      this.destroy();
    }
  }

  // Write your code here.

  /* END-USER-CODE */
}

/* END OF COMPILED CODE */

// You can write more code here
