import * as THREE from 'three';
import type { Game } from './Game';
import { playSound } from './SoundManager';

export abstract class Enemy {
  body: THREE.Group;
  dead = false;
  deathTimer = 0;
  knockback = 0;
  rootTime = 0;
  health = 100;
  maxHealth = 100;
  isBoss = false;
  displayName = '';
  speed = 6;
  xp = 10;
  contactDps = 25;
  contactRadius = 2;
  guaranteedDrop = false;
  spawnAtCenter = false;
  immovable = false;
  grace = 0;
  mixer: THREE.AnimationMixer | null = null;
  protected clips: Map<string, THREE.AnimationAction> = new Map();
  protected currentAnim = '';
  protected stopWalkSound: (() => void) | null = null;
  protected fadeOutModel: THREE.Group | null = null;
  protected fadeMaterials: THREE.Material[] = [];
  private dying = false;
  private dieTime = 0;

  constructor() {
    this.body = new THREE.Group();
  }

  abstract playAnim(name: string, loop?: boolean, speed?: number): void;
  abstract updateAnimation(dt: number): void;
  abstract update(dt: number, game: Game): void;

  canBeSlashHit(): boolean {
    return true;
  }

  protected onDeath(game: Game) {
    // Subclasses override for model-specific death behavior
  }

  protected registerFadeMesh(mesh: THREE.Mesh) {
    const mat = mesh.material as THREE.Material & {
      transparent: boolean;
      opacity: number;
    };
    mat.transparent = true;
    this.fadeMaterials.push(mat);
  }

  protected beginFadeDeath() {
    this.dying = true;
    this.dieTime = 0;
  }

  protected updateFadeDeath(
    dt: number,
    opts?: { duration?: number; shrink?: number; sink?: number }
  ): boolean {
    if (!this.dying) return false;
    const duration = opts?.duration ?? 0.9;
    this.dieTime += dt;
    const t = Math.min(this.dieTime / duration, 1);
    const scale = Math.max(0.05, 1 - t * (opts?.shrink ?? 0.95));
    if (this.fadeOutModel) {
      this.fadeOutModel.scale.set(scale, scale, scale);
    } else {
      this.body.scale.set(scale, scale, scale);
    }
    const sink = opts?.sink ?? 0;
    if (sink > 0) {
      this.body.position.y = Math.max(-1, this.body.position.y - sink * dt);
    }
    const opacity = 1 - t;
    for (const mat of this.fadeMaterials) {
      (mat as THREE.Material & { opacity: number }).opacity = opacity;
    }
    return true;
  }

  takeDamage(
    damage: number,
    game: Game,
    withKnockback = true,
    sourcePos?: THREE.Vector3
  ) {
    if (this.dead || this.grace > 0) return;
    this.health -= damage;
    if (withKnockback) {
      this.knockback = 0.5;
    }

    playSound('damage', 0.7);
    game.onDamageDealt(damage, this);

    if (this.health <= 0) {
      this.die(game);
    }
  }

  private die(game: Game) {
    if (this.dead) return;
    this.dead = true;
    this.onDeath(game);
    if (this.stopWalkSound) {
      this.stopWalkSound();
      this.stopWalkSound = null;
    }
    game.removeEnemy(this);
  }

  dispose() {
    if (this.stopWalkSound) {
      this.stopWalkSound();
      this.stopWalkSound = null;
    }
  }
}
