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

const MAX_HEALTH = 10500;
const MAX_ARMOR = 2500;
const ARMOR_REGEN_TIME = 10;
const ARMOR_REDUCTION = 0.7;
const HIT_FLASH_TIME = 0.14;
const MODEL_SCALE = 2.0;
const MOVE_SPEED = 3.5;

const BURST_COOLDOWN = 7;
const BURST_COOLDOWN_2 = 5;
const BURST_MARK_COUNT = 8;
const BURST_MARK_COUNT_2 = 12;
const BURST_RING2_RADIUS = 13;
const BURST_MARK_RADIUS = 2.8;
const BURST_TELEGRAPH = 1.6;
const BURST_STAGGER = 0.18;
const BURST_DAMAGE = 22;
const BURST_RADIUS = 9;

const RAIN_COOLDOWN = 5;
const RAIN_COOLDOWN_2 = 4;
const RAIN_COUNT = 7;
const RAIN_COUNT_2 = 10;

const STOMP_COOLDOWN = 10;
const STOMP_COOLDOWN_2 = 8;
const STOMP_RADIUS = 11;
const STOMP_DAMAGE = 15;

const ENRAGE_RATIO = 0.35;
const ICICLE_DAMAGE = 30;
const ICICLE_TELEGRAPH = 0.9;
const ICICLE_IMPACT_RADIUS = 1.8;

export class FrostGiant extends Enemy {
  private model: THREE.Group;
  private game: Game | null = null;
  private waveTime = 0;
  private enraged = false;
  private hitFlash = 0;

  private skinMat!: THREE.MeshToonMaterial;
  private darkMat!: THREE.MeshToonMaterial;
  private eyeMat!: THREE.MeshBasicMaterial;
  private armorShards: THREE.Mesh[] = [];
  private armorShardMats: THREE.MeshToonMaterial[] = [];
  private armorBroken = false;
  private armorHp = MAX_ARMOR;
  private armorRegenTimer = 0;
  private armorGrow = 0;
  private burstTimer = 3;
  private rainTimer = 2;
  private stompTimer = 6;

  private burstMarks: {
    group: THREE.Group;
    ringMat: THREE.MeshBasicMaterial;
    discMat: THREE.MeshBasicMaterial;
    index: number;
    exploded: boolean;
    explAt: number;
  }[] = [];
  private burstActive = false;
  private burstExplodeStart = 0;
  private burstStartTime = 0;
  private burstFlashes: { mesh: THREE.Mesh; mat: THREE.MeshBasicMaterial; life: number }[] = [];
  private icicles: FallingIcicle[] = [];
  private shockRing!: THREE.Mesh;
  private shockRingMat!: THREE.MeshBasicMaterial;
  private shockRingLife = 0;

  constructor() {
    super();
    this.health = MAX_HEALTH;
    this.maxHealth = MAX_HEALTH;
    this.isBoss = true;
    this.displayName = 'Frostriese';
    this.speed = MOVE_SPEED;
    this.xp = 280;
    this.contactDps = 35;
    this.contactRadius = 3.2;
    this.guaranteedDrop = true;

    this.model = new THREE.Group();
    this.model.scale.setScalar(MODEL_SCALE);
    this.fadeOutModel = this.model;
    this.body.add(this.model);

    this.skinMat = new THREE.MeshToonMaterial({ color: 0x77bbdd });
    this.darkMat = new THREE.MeshToonMaterial({ color: 0x4477aa });
    this.eyeMat = new THREE.MeshBasicMaterial({ color: 0x88ddff });
    const crystalMat = new THREE.MeshToonMaterial({ color: 0xccf0ff, emissive: 0x223344 });
    const metalMat = new THREE.MeshToonMaterial({ color: 0x5599bb });

    // Beine
    for (const side of [-1, 1]) {
      const leg = new THREE.Mesh(new THREE.CylinderGeometry(0.35, 0.45, 1.2, 10), this.darkMat);
      leg.position.set(side * 0.55, -0.6, 0);
      leg.castShadow = true;
      this.registerFadeMesh(leg);
      this.model.add(leg);
      const foot = new THREE.Mesh(new THREE.BoxGeometry(0.6, 0.25, 0.9), this.skinMat);
      foot.position.set(side * 0.55, -1.25, 0.1);
      this.registerFadeMesh(foot);
      this.model.add(foot);
    }

    // Torso
    const torso = new THREE.Mesh(new THREE.BoxGeometry(2.0, 2.2, 1.4), this.skinMat);
    torso.position.y = 0.9;
    torso.castShadow = true;
    this.registerFadeMesh(torso);
    this.model.add(torso);

    // Bauchpanzer
    const belly = new THREE.Mesh(new THREE.BoxGeometry(1.4, 0.9, 0.1), metalMat);
    belly.position.set(0, 0.55, 0.72);
    this.registerFadeMesh(belly);
    this.model.add(belly);

    // Schultern
    for (const side of [-1, 1]) {
      const shoulder = new THREE.Mesh(new THREE.SphereGeometry(0.7, 12, 10), this.skinMat);
      shoulder.position.set(side * 1.3, 1.7, 0);
      shoulder.castShadow = true;
      this.registerFadeMesh(shoulder);
      this.model.add(shoulder);

      // Arme
      const arm = new THREE.Mesh(new THREE.CylinderGeometry(0.3, 0.42, 1.4, 8), this.darkMat);
      arm.position.set(side * 1.6, 0.6, 0);
      this.registerFadeMesh(arm);
      this.model.add(arm);

      // Faust
      const fist = new THREE.Mesh(new THREE.SphereGeometry(0.45, 10, 8), this.skinMat);
      fist.position.set(side * 1.7, 0.0, 0.2);
      this.registerFadeMesh(fist);
      this.model.add(fist);
    }

    // Kopf
    const head = new THREE.Mesh(new THREE.BoxGeometry(1.0, 0.9, 0.95), this.skinMat);
    head.position.y = 2.5;
    head.castShadow = true;
    this.registerFadeMesh(head);
    this.model.add(head);

    // Frostbart (+Z)
    const beard = new THREE.Mesh(new THREE.ConeGeometry(0.45, 0.7, 8), this.darkMat);
    beard.position.set(0, 1.95, 0.5);
    beard.rotation.x = Math.PI;
    this.registerFadeMesh(beard);
    this.model.add(beard);

    // Augen (+Z)
    for (const side of [-1, 1]) {
      const eye = new THREE.Mesh(new THREE.SphereGeometry(0.13, 8, 6), this.eyeMat);
      eye.position.set(side * 0.3, 2.62, 0.5);
      this.model.add(eye);
    }

    // Eiskristall-Panzer (verdeckt den Rumpf, solange die Rüstung hält)
    const shardPositions: [number, number, number, number][] = [
      [0, 1.5, 0.75, 0],
      [-0.7, 1.3, 0.7, 0.4],
      [0.7, 1.3, 0.7, -0.4],
      [0, 2.1, 0.7, 0],
      [-1.4, 1.7, 0, 0.8],
      [1.4, 1.7, 0, -0.8],
      [-0.6, 0.7, 0.75, 0.3],
      [0.6, 0.7, 0.75, -0.3],
      [-0.3, 2.6, 0.5, 0.5],
      [0.3, 2.6, 0.5, -0.5],
    ];
    for (const [x, y, z, rotY] of shardPositions) {
      const mat = crystalMat.clone();
      const shard = new THREE.Mesh(new THREE.ConeGeometry(0.35, 0.9, 6), mat);
      shard.position.set(x, y, z);
      shard.rotation.set(-0.15, rotY, 0);
      this.registerFadeMesh(shard);
      this.model.add(shard);
      this.armorShards.push(shard);
      this.armorShardMats.push(mat);
    }

    // Schockwellen-Ring
    this.shockRingMat = new THREE.MeshBasicMaterial({
      color: 0x88ccff,
      transparent: true,
      opacity: 0,
      side: THREE.DoubleSide,
      depthWrite: false,
    });
    this.shockRing = new THREE.Mesh(new THREE.RingGeometry(0.9, 1.0, 48), this.shockRingMat);
    this.shockRing.rotation.x = -Math.PI / 2;
    this.shockRing.position.y = 0.06;
    this.shockRing.visible = false;
    this.body.add(this.shockRing);
  }

  playAnim() {
    // Prozedurale Optik
  }

  updateAnimation(dt: number) {
    this.updateFadeDeath(dt, { duration: 1.2, sink: 0.5 });
  }

  update(dt: number, game: Game) {
    this.game = game;
    this.waveTime += dt;

    // Hit-Feedback
    if (this.hitFlash > 0) {
      this.hitFlash -= dt;
      this.skinMat.color.set(0xffffff);
      this.darkMat.color.set(0xffffff);
      this.model.scale.setScalar(MODEL_SCALE * (1 + (this.hitFlash / HIT_FLASH_TIME) * 0.05));
    } else {
      const dark = this.armorBroken;
      this.skinMat.color.set(dark ? 0x4477aa : 0x77bbdd);
      this.darkMat.color.set(dark ? 0x2a4a6a : 0x4477aa);
      this.model.scale.setScalar(MODEL_SCALE);
    }

    // Enrage unter 35% HP
    if (!this.enraged && this.health < MAX_HEALTH * ENRAGE_RATIO) {
      this.enraged = true;
      this.eyeMat.color.set(0xff4422);
      playSound('damage', 0.9);
      game.triggerCameraShake(0.35, 0.5);
    }

    // Rüstungs-Regeneration
    if (this.armorBroken) {
      this.armorRegenTimer -= dt;
      const t = 1 - Math.max(0, this.armorRegenTimer) / ARMOR_REGEN_TIME;
      // Kristalle wachsen langsam nach, kurz vor Abschluss deutlich sichtbar
      this.armorGrow = Math.min(1, t * 1.6);
      const showScale = this.armorGrow;
      for (const shard of this.armorShards) shard.visible = showScale > 0.02;
      for (let i = 0; i < this.armorShards.length; i++) {
        this.armorShards[i].scale.setScalar(showScale);
        this.armorShardMats[i].opacity = Math.min(1, showScale * 2);
      }
      if (this.armorRegenTimer <= 0) {
        this.armorBroken = false;
        this.armorHp = MAX_ARMOR;
        this.armorRegenTimer = 0;
        for (const shard of this.armorShards) shard.scale.setScalar(1);
        for (const mat of this.armorShardMats) mat.opacity = 1;
        playSound('spawn', 0.7);
      }
    }

    // Zum Spieler laufen
    const playerPos = game.player.body.position;
    const toPlayer = new THREE.Vector3().subVectors(playerPos, this.body.position);
    toPlayer.y = 0;
    const dist = toPlayer.length();
    if (dist > 0.1) {
      const dir = toPlayer.normalize();
      this.body.lookAt(
        this.body.position.x + dir.x,
        this.body.position.y,
        this.body.position.z + dir.z
      );
      let moveDir = dir.multiplyScalar(MOVE_SPEED);
      if (this.knockback > 0) {
        moveDir = dir.multiplyScalar(-MOVE_SPEED);
        this.knockback -= dt;
      }
      this.body.position.x += moveDir.x * dt;
      this.body.position.z += moveDir.z * dt;
      game.clampToArena(this.body.position);
    }

    // Angriffe
    this.burstTimer -= dt;
    if (this.burstTimer <= 0 && !this.burstActive) {
      this.burstTimer = this.enraged ? BURST_COOLDOWN_2 : BURST_COOLDOWN;
      this.startBurst(game);
    }
    this.rainTimer -= dt;
    if (this.rainTimer <= 0) {
      this.rainTimer = this.enraged ? RAIN_COOLDOWN_2 : RAIN_COOLDOWN;
      this.icicleRain(game);
    }
    this.stompTimer -= dt;
    if (this.stompTimer <= 0) {
      this.stompTimer = this.enraged ? STOMP_COOLDOWN_2 : STOMP_COOLDOWN;
      this.stomp(game);
    }

    this.updateBurst(dt, game);
    this.updateBurstFlashes(dt);

    // Schockwellen-Ring expandieren
    if (this.shockRing.visible) {
      const s = this.shockRing.scale.x + dt * 26;
      this.shockRing.scale.setScalar(s);
      this.shockRingMat.opacity = Math.max(0, 0.5 - (s / STOMP_RADIUS) * 0.5);
      if (s >= STOMP_RADIUS) this.shockRing.visible = false;
    }

    // Eiszapfen
    for (let i = this.icicles.length - 1; i >= 0; i--) {
      if (!this.icicles[i].update(dt, game)) {
        this.icicles[i].dispose();
        this.icicles.splice(i, 1);
      }
    }
  }

  // --- Eispanzer ---

  private breakArmor(game: Game) {
    this.armorBroken = true;
    this.armorRegenTimer = ARMOR_REGEN_TIME;
    this.armorGrow = 0;
    for (const shard of this.armorShards) shard.visible = false;
    for (const mat of this.armorShardMats) mat.opacity = 0;
    game.spawnDamageText('PANZER WEG!', this.body.position, '#88ccff');
    game.triggerCameraShake(0.4, 0.5);
    playSound('explosion', 0.9);
  }

  // --- Eisberst-Ring ---

  private startBurst(game: Game) {
    this.burstActive = true;
    this.burstExplodeStart = BURST_TELEGRAPH;
    this.burstStartTime = this.waveTime;
    this.burstMarks = [];
    const count = this.enraged ? BURST_MARK_COUNT_2 : BURST_MARK_COUNT;
    const offset = Math.random() * Math.PI * 2;
    const rings = this.enraged
      ? [{ radius: BURST_RADIUS, count: BURST_MARK_COUNT }]
      : [{ radius: BURST_RADIUS, count }];
    if (this.enraged) {
      rings.push({ radius: BURST_RING2_RADIUS, count: BURST_MARK_COUNT - 2 });
    }
    const cx = this.body.position.x;
    const cz = this.body.position.z;
    let index = 0;
    for (const ring of rings) {
      for (let i = 0; i < ring.count; i++) {
        const a = offset + (i / ring.count) * Math.PI * 2;
        const ringMat = new THREE.MeshBasicMaterial({
          color: 0x66ccff,
          transparent: true,
          opacity: 0,
          side: THREE.DoubleSide,
          depthWrite: false,
        });
        const discMat = new THREE.MeshBasicMaterial({
          color: 0x4488ff,
          transparent: true,
          opacity: 0,
          depthWrite: false,
        });
        const ringMesh = new THREE.Mesh(
          new THREE.RingGeometry(BURST_MARK_RADIUS - 0.15, BURST_MARK_RADIUS + 0.15, 32),
          ringMat
        );
        ringMesh.rotation.x = -Math.PI / 2;
        ringMesh.position.y = 0.05;
        const disc = new THREE.Mesh(
          new THREE.CircleGeometry(BURST_MARK_RADIUS, 32),
          discMat
        );
        disc.rotation.x = -Math.PI / 2;
        disc.position.y = 0.03;
        const group = new THREE.Group();
        group.add(ringMesh, disc);
        group.position.set(
          cx + Math.cos(a) * ring.radius,
          0,
          cz + Math.sin(a) * ring.radius
        );
        game.scene.add(group);
        this.burstMarks.push({ group, ringMat, discMat, index, exploded: false, explAt: 0 });
        index++;
      }
    }
    playSound('spawn', 0.7);
  }

  private updateBurst(dt: number, game: Game) {
    if (!this.burstActive) return;
    if (this.burstExplodeStart > 0) {
      // Telegraf: Marken pulsieren
      this.burstExplodeStart -= dt;
      const pulse = Math.abs(Math.sin(this.waveTime * 9));
      for (const mark of this.burstMarks) {
        mark.ringMat.opacity = 0.3 + 0.5 * pulse;
        mark.discMat.opacity = 0.12 + 0.08 * pulse;
      }
      if (this.burstExplodeStart <= 0) {
        // Explosionsfenster: nacheinander
        this.burstExplodeStart = -1;
      }
      return;
    }

    let allDone = true;
    for (const mark of this.burstMarks) {
      if (!mark.exploded) {
        allDone = false;
        const t = (this.waveTime - this.burstStartTime) - (mark.index * BURST_STAGGER);
        if (t >= 0) {
          mark.exploded = true;
          this.explodeMark(mark, game);
        }
      }
    }
    if (allDone) {
      this.burstActive = false;
      for (const mark of this.burstMarks) {
        game.scene.remove(mark.group);
        mark.group.traverse((obj) => {
          if (obj instanceof THREE.Mesh) {
            obj.geometry.dispose();
            (obj.material as THREE.Material).dispose();
          }
        });
      }
      this.burstMarks = [];
    }
  }

  private explodeMark(mark: { group: THREE.Group }, game: Game) {
    // Expandierender Frost-Ring
    const flashMat = new THREE.MeshBasicMaterial({
      color: 0x99ddff,
      transparent: true,
      opacity: 0.7,
      side: THREE.DoubleSide,
      depthWrite: false,
    });
    const flash = new THREE.Mesh(new THREE.RingGeometry(0.9, 1.1, 32), flashMat);
    flash.rotation.x = -Math.PI / 2;
    flash.position.set(mark.group.position.x, 0.07, mark.group.position.z);
    game.scene.add(flash);
    this.burstFlashes.push({ mesh: flash, mat: flashMat, life: 0.35 });

    // Schaden (überspringbar: nur am Boden)
    if (game.player.health > 0 && game.player.body.position.y <= 0.8) {
      const dx = game.player.body.position.x - mark.group.position.x;
      const dz = game.player.body.position.z - mark.group.position.z;
      if (Math.hypot(dx, dz) < BURST_MARK_RADIUS + 0.3) {
        game.player.damage(BURST_DAMAGE, mark.group.position);
      }
    }
    playSound('explosion', 0.4);
  }

  private updateBurstFlashes(dt: number) {
    for (let i = this.burstFlashes.length - 1; i >= 0; i--) {
      const f = this.burstFlashes[i];
      f.life -= dt;
      const t = 1 - Math.max(0, f.life) / 0.35;
      f.mesh.scale.setScalar(0.5 + t * 6);
      f.mat.opacity = 0.7 * (1 - t);
      if (f.life <= 0) {
        this.game?.scene.remove(f.mesh);
        f.mesh.geometry.dispose();
        f.mat.dispose();
        this.burstFlashes.splice(i, 1);
      }
    }
  }

  // --- Eiszapfen-Regen ---

  private icicleRain(game: Game) {
    const playerPos = game.player.body.position;
    const count = this.enraged ? RAIN_COUNT_2 : RAIN_COUNT;
    for (let i = 0; i < count; i++) {
      const a = Math.random() * Math.PI * 2;
      const r = 2 + Math.random() * 6;
      const icicle = new FallingIcicle(new THREE.Vector3(
        playerPos.x + Math.cos(a) * r,
        0,
        playerPos.z + Math.sin(a) * r
      ));
      game.scene.add(icicle.body);
      this.icicles.push(icicle);
    }
    playSound('spawn', 0.5);
  }

  // --- Stampf ---

  private stomp(game: Game) {
    this.shockRing.visible = true;
    this.shockRing.scale.setScalar(0.2);
    this.shockRingMat.opacity = 0.5;
    playSound('explosion', 0.7);

    const playerPos = game.player.body.position;
    const dist = Math.hypot(
      playerPos.x - this.body.position.x,
      playerPos.z - this.body.position.z
    );
    // Überspringbar: nur Treffer am Boden
    if (dist < STOMP_RADIUS && playerPos.y <= 0.6) {
      game.player.damage(STOMP_DAMAGE, this.body.position);
    }
    game.triggerCameraShake(0.3, 0.3);
  }

  override takeDamage(
    damage: number,
    game: Game,
    withKnockback = true,
    sourcePos?: THREE.Vector3
  ) {
    if (this.dead) return;
    if (this.armorBroken === false && this.armorShards.length > 0) {
      const absorbed = Math.min(MAX_ARMOR, damage * ARMOR_REDUCTION);
      damage *= 1 - ARMOR_REDUCTION;
      game.spawnDamageText('Eispanzer!', this.body.position, '#99ccff');
      // Rüstung abbauen: Kristalle werden mit sinkender Armor-HP blasser
      const pct = 1 - this.armorHp / MAX_ARMOR;
      for (let i = 0; i < this.armorShards.length; i++) {
        const shard = this.armorShards[i];
        const threshold = (i + 1) / this.armorShards.length;
        shard.visible = pct < threshold;
        if (!shard.visible) {
          this.armorShardMats[i].opacity = 0;
        }
      }
      this.armorHp -= absorbed;
      if (this.armorHp <= 0) {
        this.armorHp = 0;
        this.breakArmor(game);
      }
    }
    super.takeDamage(damage, game, withKnockback, sourcePos);
    this.hitFlash = HIT_FLASH_TIME;
  }

  protected onDeath(_game: Game) {
    this.cleanupEffects();
    this.beginFadeDeath();
  }

  override dispose() {
    super.dispose();
    this.cleanupEffects();
  }

  private cleanupEffects() {
    this.burstActive = false;
    if (this.game) {
      for (const mark of this.burstMarks) {
        this.game.scene.remove(mark.group);
        mark.group.traverse((obj) => {
          if (obj instanceof THREE.Mesh) {
            obj.geometry.dispose();
            (obj.material as THREE.Material).dispose();
          }
        });
      }
      for (const f of this.burstFlashes) {
        this.game.scene.remove(f.mesh);
        f.mesh.geometry.dispose();
        f.mat.dispose();
      }
    }
    this.burstMarks = [];
    this.burstFlashes = [];
    for (const icicle of this.icicles) icicle.dispose();
    this.icicles = [];
    this.shockRing.visible = false;
    this.skinMat.color.set(0x77bbdd);
    this.darkMat.color.set(0x4477aa);
    this.eyeMat.color.set(0x88ddff);
    this.model.scale.setScalar(MODEL_SCALE);
    for (const shard of this.armorShards) shard.visible = true;
    for (const mat of this.armorShardMats) mat.opacity = 1;
  }
}

// Eiszapfen: fällt mit wachsendem Schatten-Telegrafen vom Himmel
class FallingIcicle {
  body: THREE.Group;
  private shadow: THREE.Mesh;
  private icicle: THREE.Mesh;
  private phase: 'telegraph' | 'fall' | 'dust' = 'telegraph';
  private timer = ICICLE_TELEGRAPH;
  private dust: THREE.Mesh[] = [];
  private dustDirs: THREE.Vector3[] = [];
  private dustMat: THREE.MeshBasicMaterial;

  constructor(pos: THREE.Vector3) {
    this.body = new THREE.Group();
    this.body.position.set(pos.x, 0, pos.z);

    const shadowMat = new THREE.MeshBasicMaterial({
      color: 0x000000,
      transparent: true,
      opacity: 0.3,
      depthWrite: false,
    });
    this.shadow = new THREE.Mesh(new THREE.CircleGeometry(1.6, 24), shadowMat);
    this.shadow.rotation.x = -Math.PI / 2;
    this.shadow.position.y = 0.06;
    this.shadow.scale.setScalar(0.4);
    this.body.add(this.shadow);

    const iceMat = new THREE.MeshToonMaterial({ color: 0xbbeeff, emissive: 0x334455 });
    this.icicle = new THREE.Mesh(new THREE.ConeGeometry(0.45, 1.6, 6), iceMat);
    this.icicle.position.y = 16;
    this.icicle.rotation.x = Math.PI;
    this.body.add(this.icicle);

    this.dustMat = new THREE.MeshBasicMaterial({
      color: 0x99bbdd,
      transparent: true,
      opacity: 0,
      depthWrite: false,
    });
  }

  update(dt: number, game: Game): boolean {
    this.timer -= dt;

    if (this.phase === 'telegraph') {
      const t = 1 - this.timer / ICICLE_TELEGRAPH;
      this.shadow.scale.setScalar(0.4 + t * 0.6);
      (this.shadow.material as THREE.MeshBasicMaterial).opacity = 0.2 + t * 0.2;
      if (this.timer <= 0) {
        this.phase = 'fall';
        this.timer = 2;
      }
    } else if (this.phase === 'fall') {
      this.icicle.position.y -= 30 * dt;
      this.icicle.rotation.z += dt * 2;
      if (this.icicle.position.y <= 0.15) {
        this.icicle.position.y = 0.15;
        this.icicle.visible = false;
        this.shadow.visible = false;

        if (game.player.health > 0) {
          const dx = game.player.body.position.x - this.body.position.x;
          const dz = game.player.body.position.z - this.body.position.z;
          if (Math.hypot(dx, dz) < ICICLE_IMPACT_RADIUS) {
            game.player.damage(ICICLE_DAMAGE, this.body.position);
          }
        }

        for (let i = 0; i < 5; i++) {
          const a = (i / 5) * Math.PI * 2;
          const dir = new THREE.Vector3(Math.cos(a), 0, Math.sin(a));
          const puff = new THREE.Mesh(new THREE.SphereGeometry(0.25, 6, 5), this.dustMat);
          this.body.add(puff);
          this.dust.push(puff);
          this.dustDirs.push(dir);
        }
        this.phase = 'dust';
        this.timer = 0.4;
      }
    } else {
      const t = 1 - this.timer / 0.4;
      for (let i = 0; i < this.dust.length; i++) {
        this.dust[i].position.copy(this.dustDirs[i]).multiplyScalar(0.5 + t * 1.8);
        this.dust[i].position.y = 0.2 + t * 0.8;
      }
      this.dustMat.opacity = 0.5 * (1 - t);
      if (this.timer <= 0) return false;
    }
    return true;
  }

  dispose() {
    this.body.removeFromParent();
    this.body.traverse((obj) => {
      if (obj instanceof THREE.Mesh) {
        obj.geometry.dispose();
        (obj.material as THREE.Material).dispose();
      }
    });
  }
}
