import * as THREE from 'three';
import type { Game } from './Game';
import { Enemy } from './Enemy';
import { Ghost } from './Ghost';
import { Slime } from './Slime';
import { Spider } from './Spider';
import { Archer } from './Archer';
import { Shaman } from './Shaman';
import { Turtle } from './Turtle';
import { playSound } from './SoundManager';

const MAX_HEALTH = 13000;
const HIT_FLASH_TIME = 0.14;
const MODEL_SCALE = 1.8;
const FLOAT_HEIGHT = 1.2;
const BOB_AMPLITUDE = 0.25;
const DRIFT_SPEED = 2.5;

const TELEPORT_COOLDOWN = 6;
const TELEPORT_COOLDOWN_2 = 4;
const TELEPORT_CHARGE = 1.0;
const TELEPORT_CHARGE_2 = 0.6;
const TELEPORT_RADIUS = 3.2;
const TELEPORT_DAMAGE = 15;
const TELEPORT_RING_PULSE = 6;

const VOLLEY_COOLDOWN = 4;
const VOLLEY_COOLDOWN_2 = 3;
const VOLLEY_COUNT = 5;
const VOLLEY_COUNT_2 = 7;
const VOLLEY_SPREAD = 0.4;
const BOLT_DAMAGE = 18;

const SUMMON_COOLDOWN = 8;
const SUMMON_COOLDOWN_2 = 6;
const SUMMON_COUNT = 3;
const SUMMON_COUNT_2 = 5;
const SUMMON_TELEGRAPH = 0.8;

const ENRAGE_RATIO = 0.4;

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

  private robeMat!: THREE.MeshToonMaterial;
  private darkMat!: THREE.MeshToonMaterial;
  private glowMat!: THREE.MeshBasicMaterial;
  private eyeMat!: THREE.MeshBasicMaterial;
  private shadow!: THREE.Mesh;
  private shadowMat!: THREE.MeshBasicMaterial;

  private teleportTimer = 3;
  private volleyTimer = 1.5;
  private summonTimer = 5;
  private teleportState: 'idle' | 'charge' = 'idle';
  private teleportCharge = 0;
  private teleportTarget = new THREE.Vector3();
  private teleportRing!: THREE.Group;
  private teleportRingMesh!: THREE.Mesh;
  private teleportRingDisc!: THREE.Mesh;
  private teleportRingMat!: THREE.MeshBasicMaterial;
  private teleportDiscMat!: THREE.MeshBasicMaterial;
  private teleportRingInScene = false;

  private spawnRings: { group: THREE.Group; timer: number; spawn: () => void }[] = [];
  private arrivalFlashes: { mesh: THREE.Mesh; mat: THREE.MeshBasicMaterial; life: number }[] = [];

  constructor() {
    super();
    this.health = MAX_HEALTH;
    this.maxHealth = MAX_HEALTH;
    this.isBoss = true;
    this.displayName = 'Erz-Nekromant';
    this.speed = 0;
    this.xp = 320;
    this.contactDps = 15;
    this.contactRadius = 1.8;
    this.guaranteedDrop = true;

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

    this.robeMat = new THREE.MeshToonMaterial({ color: 0x6a3a9a, emissive: 0x1a0a2a });
    this.darkMat = new THREE.MeshToonMaterial({ color: 0x2a1445 });
    this.glowMat = new THREE.MeshBasicMaterial({ color: 0xbb66ff });
    this.eyeMat = new THREE.MeshBasicMaterial({ color: 0xdd88ff });
    const boneMat = new THREE.MeshToonMaterial({ color: 0xf0e6cf });
    const faceMat = new THREE.MeshBasicMaterial({ color: 0x0a0614 });
    let boneFadeRegistered = false;
    const registerBone = (mesh: THREE.Mesh) => {
      // Knochen-Material einmal fürs Fade-Death registrieren (wird geteilt)
      if (!boneFadeRegistered) {
        this.registerFadeMesh(mesh);
        boneFadeRegistered = true;
      }
    };

    // --- Robe (Blickrichtung +Z, schwebt: unten offen und zerfetzt) ---
    const robeProfile = [
      new THREE.Vector2(0.75, -1.15), // Saum
      new THREE.Vector2(0.62, -0.75),
      new THREE.Vector2(0.45, -0.2),
      new THREE.Vector2(0.38, 0.25),
      new THREE.Vector2(0.5, 0.55), // Schulteransatz
      new THREE.Vector2(0.32, 0.78), // Hals
    ];
    const robe = new THREE.Mesh(new THREE.LatheGeometry(robeProfile, 14), this.robeMat);
    robe.castShadow = true;
    this.registerFadeMesh(robe);
    this.model.add(robe);

    // Innenschatten im Saum (von unten sichtbar)
    const hemInner = new THREE.Mesh(new THREE.CircleGeometry(0.7, 14), this.darkMat);
    hemInner.rotation.x = Math.PI / 2;
    hemInner.position.y = -1.08;
    this.registerFadeMesh(hemInner);
    this.model.add(hemInner);

    // Zerfetzte Saumfetzen
    for (let i = 0; i < 8; i++) {
      const a = (i / 8) * Math.PI * 2;
      const tatter = new THREE.Mesh(
        new THREE.ConeGeometry(0.08, 0.4, 4),
        i % 2 === 0 ? this.robeMat : this.darkMat
      );
      tatter.rotation.x = Math.PI;
      tatter.position.set(Math.sin(a) * 0.68, -1.28, Math.cos(a) * 0.68);
      this.registerFadeMesh(tatter);
      this.model.add(tatter);
    }

    // Gürtelkordel + Quaste
    const belt = new THREE.Mesh(new THREE.TorusGeometry(0.42, 0.035, 6, 16), this.darkMat);
    belt.rotation.x = Math.PI / 2;
    belt.position.y = 0.12;
    this.registerFadeMesh(belt);
    this.model.add(belt);
    const tasselCord = new THREE.Mesh(new THREE.CylinderGeometry(0.02, 0.02, 0.28, 5), this.darkMat);
    tasselCord.position.set(-0.3, -0.05, 0.36);
    this.registerFadeMesh(tasselCord);
    this.model.add(tasselCord);
    const tassel = new THREE.Mesh(new THREE.ConeGeometry(0.05, 0.14, 5), boneMat);
    tassel.rotation.x = Math.PI;
    tassel.position.set(-0.3, -0.24, 0.38);
    registerBone(tassel);
    this.model.add(tassel);

    // Oberkörper
    const body = new THREE.Mesh(new THREE.SphereGeometry(0.55, 16, 12), this.robeMat);
    body.position.y = 0.6;
    body.scale.set(1, 1.2, 1);
    body.castShadow = true;
    this.registerFadeMesh(body);
    this.model.add(body);

    // Kragen
    const collar = new THREE.Mesh(new THREE.TorusGeometry(0.22, 0.045, 6, 14), this.darkMat);
    collar.rotation.x = Math.PI / 2 - 0.2;
    collar.position.set(0, 0.85, 0.08);
    this.registerFadeMesh(collar);
    this.model.add(collar);

    // Schulterpanzer mit Spitzen
    for (const side of [-1, 1]) {
      const pauldron = new THREE.Mesh(new THREE.SphereGeometry(0.26, 12, 8), this.darkMat);
      pauldron.scale.set(1.25, 0.65, 1.25);
      pauldron.position.set(side * 0.45, 0.78, 0);
      this.registerFadeMesh(pauldron);
      this.model.add(pauldron);
      const spike = new THREE.Mesh(new THREE.ConeGeometry(0.06, 0.22, 5), this.darkMat);
      spike.position.set(side * 0.55, 0.95, 0);
      spike.rotation.z = side * -0.5;
      this.registerFadeMesh(spike);
      this.model.add(spike);
    }

    // Kapuze mit Krempe und schwarzem Gesichts-Leerraum
    const hood = new THREE.Mesh(new THREE.ConeGeometry(0.5, 0.95, 12, 1, true), this.darkMat);
    hood.position.set(0, 1.18, 0.02);
    hood.rotation.x = 0.22;
    hood.castShadow = true;
    this.registerFadeMesh(hood);
    this.model.add(hood);
    const hoodRim = new THREE.Mesh(new THREE.TorusGeometry(0.34, 0.05, 8, 16), this.darkMat);
    hoodRim.rotation.x = -0.5;
    hoodRim.position.set(0, 1.05, 0.34);
    this.registerFadeMesh(hoodRim);
    this.model.add(hoodRim);
    const face = new THREE.Mesh(new THREE.SphereGeometry(0.3, 12, 10), faceMat);
    face.scale.set(1, 1.1, 0.8);
    face.position.set(0, 1.04, 0.26);
    this.registerFadeMesh(face);
    this.model.add(face);

    // Leuchtende Augen in der Kapuze (+Z)
    for (const side of [-1, 1]) {
      const eye = new THREE.Mesh(new THREE.SphereGeometry(0.075, 8, 6), this.eyeMat);
      eye.position.set(side * 0.12, 1.08, 0.48);
      this.model.add(eye);
    }

    // Amulett + Runen auf der Robe (glühen, färben sich im Enrage)
    const amulet = new THREE.Mesh(new THREE.SphereGeometry(0.08, 8, 6), this.glowMat);
    amulet.position.set(0, 0.6, 0.5);
    this.model.add(amulet);
    const runeSpots: [number, number, number][] = [
      [0, -0.15, 0.5],
      [-0.2, -0.5, 0.5],
      [0.2, -0.5, 0.5],
    ];
    for (const [x, y, z] of runeSpots) {
      const rune = new THREE.Mesh(new THREE.OctahedronGeometry(0.06), this.glowMat);
      rune.scale.set(1, 1.7, 0.5);
      rune.position.set(x, y, z);
      this.model.add(rune);
    }

    // Rechter Arm: Ärmel zum Stab
    const sleeveR = new THREE.Mesh(new THREE.CylinderGeometry(0.09, 0.16, 0.55, 8), this.robeMat);
    sleeveR.position.set(0.65, 0.84, 0.14);
    sleeveR.rotation.z = 1.2;
    this.registerFadeMesh(sleeveR);
    this.model.add(sleeveR);
    const handR = new THREE.Mesh(new THREE.SphereGeometry(0.1, 8, 6), boneMat);
    handR.position.set(0.85, 0.9, 0.23);
    registerBone(handR);
    this.model.add(handR);

    // Linker Arm: Ärmel nach vorn (Beschwörungs-Geste) + Energiekugel
    const sleeveL = new THREE.Mesh(new THREE.CylinderGeometry(0.09, 0.18, 0.6, 8), this.robeMat);
    sleeveL.position.set(-0.55, 0.75, 0.32);
    sleeveL.rotation.x = -1.25;
    sleeveL.rotation.z = -0.35;
    this.registerFadeMesh(sleeveL);
    this.model.add(sleeveL);
    const handL = new THREE.Mesh(new THREE.SphereGeometry(0.1, 8, 6), boneMat);
    handL.position.set(-0.68, 0.72, 0.62);
    registerBone(handL);
    this.model.add(handL);
    const castOrb = new THREE.Mesh(new THREE.SphereGeometry(0.09, 8, 6), this.glowMat);
    castOrb.position.set(-0.7, 0.92, 0.68);
    this.model.add(castOrb);

    // Stab mit Manschetten, Totenkopf und schwebendem Orb
    const staff = new THREE.Mesh(new THREE.CylinderGeometry(0.045, 0.045, 2.4, 8), this.darkMat);
    staff.position.set(0.8, 1.2, 0.28);
    staff.rotation.z = 0.18;
    staff.rotation.x = 0.15;
    this.registerFadeMesh(staff);
    this.model.add(staff);
    for (const [x, y, z] of [[0.88, 0.76, 0.21], [0.96, 0.31, 0.15]] as const) {
      const collarM = new THREE.Mesh(new THREE.CylinderGeometry(0.06, 0.06, 0.07, 8), this.darkMat);
      collarM.position.set(x, y, z);
      this.registerFadeMesh(collarM);
      this.model.add(collarM);
    }
    const skull = new THREE.Mesh(new THREE.SphereGeometry(0.15, 10, 8), boneMat);
    skull.position.set(0.58, 2.42, 0.47);
    registerBone(skull);
    this.model.add(skull);
    for (const side of [-1, 1]) {
      const socket = new THREE.Mesh(new THREE.SphereGeometry(0.03, 6, 4), this.darkMat);
      socket.position.set(0.58 + side * 0.05, 2.44, 0.6);
      this.model.add(socket);
    }

    const orbMat = new THREE.MeshBasicMaterial({
      color: 0xbb66ff,
      transparent: true,
      opacity: 0.85,
      blending: THREE.AdditiveBlending,
      depthWrite: false,
    });
    const orb = new THREE.Mesh(new THREE.SphereGeometry(0.14, 10, 8), orbMat);
    orb.position.set(0.58, 2.66, 0.47);
    this.model.add(orb);
    this.orbMat = orbMat;
    this.orb = orb;

    // Boden-Schatten
    this.shadowMat = new THREE.MeshBasicMaterial({
      color: 0x000000,
      transparent: true,
      opacity: 0.3,
    });
    this.shadow = new THREE.Mesh(new THREE.CircleGeometry(0.9, 20), this.shadowMat);
    this.shadow.rotation.x = -Math.PI / 2;
    this.shadow.renderOrder = 998;
    this.body.add(this.shadow);

    // Teleport-Telegraf: Ring + Scheibe am Zielpunkt
    this.teleportRingMat = new THREE.MeshBasicMaterial({
      color: 0xbb66ff,
      transparent: true,
      opacity: 0,
      side: THREE.DoubleSide,
      depthWrite: false,
    });
    this.teleportDiscMat = new THREE.MeshBasicMaterial({
      color: 0x8833cc,
      transparent: true,
      opacity: 0,
      depthWrite: false,
    });
    const ringMesh = new THREE.Mesh(
      new THREE.RingGeometry(1.6, 2.0, 32),
      this.teleportRingMat
    );
    ringMesh.rotation.x = -Math.PI / 2;
    ringMesh.position.y = 0.05;
    const disc = new THREE.Mesh(new THREE.CircleGeometry(1.8, 32), this.teleportDiscMat);
    disc.rotation.x = -Math.PI / 2;
    disc.position.y = 0.03;
    this.teleportRingMesh = ringMesh;
    this.teleportRingDisc = disc;
    this.teleportRing = new THREE.Group();
    this.teleportRing.add(ringMesh, disc);
    this.teleportRing.visible = false;
  }

  private orb!: THREE.Mesh;
  private orbMat!: THREE.MeshBasicMaterial;

  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;

    // Schweben
    this.body.position.y = FLOAT_HEIGHT + Math.sin(this.waveTime * 2.2) * BOB_AMPLITUDE;
    this.shadow.position.y = 0.06 - this.body.position.y;
    const sway = Math.sin(this.waveTime * 1.5) * 0.06;
    this.model.rotation.z = sway;
    this.model.rotation.x = Math.sin(this.waveTime * 2.2) * 0.04;

    // Hit-Feedback
    if (this.hitFlash > 0) {
      this.hitFlash -= dt;
      this.robeMat.color.set(0xffffff);
      this.darkMat.color.set(0xffffff);
      this.model.scale.setScalar(MODEL_SCALE * (1 + (this.hitFlash / HIT_FLASH_TIME) * 0.05));
    } else {
      this.robeMat.color.set(this.enraged ? 0x8a2233 : 0x6a3a9a);
      this.darkMat.color.set(this.enraged ? 0x551520 : 0x2a1445);
      this.model.scale.setScalar(MODEL_SCALE);
    }

    // Orb pulsiert
    const orbPulse = 1 + Math.abs(Math.sin(this.waveTime * 5)) * 0.25;
    this.orb.scale.setScalar(orbPulse);
    this.orbMat.opacity = 0.6 + Math.abs(Math.sin(this.waveTime * 5)) * 0.3;

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

    // Zum Spieler driften (langsam)
    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(DRIFT_SPEED);
      if (this.knockback > 0) {
        moveDir = dir.multiplyScalar(-DRIFT_SPEED);
        this.knockback -= dt;
      }
      this.body.position.x += moveDir.x * dt;
      this.body.position.z += moveDir.z * dt;
      game.clampToArena(this.body.position);
    }

    // Teleport
    if (this.teleportState === 'idle') {
      this.teleportTimer -= dt;
      if (this.teleportTimer <= 0) {
        this.teleportTimer = this.enraged ? TELEPORT_COOLDOWN_2 : TELEPORT_COOLDOWN;
        this.startTeleport(game);
      }
    } else {
      this.updateTeleportCharge(dt, game);
    }

    // Salven
    this.volleyTimer -= dt;
    if (this.volleyTimer <= 0) {
      this.volleyTimer = this.enraged ? VOLLEY_COOLDOWN_2 : VOLLEY_COOLDOWN;
      this.fireVolley(game);
    }

    // Beschwörung
    this.summonTimer -= dt;
    if (this.summonTimer <= 0) {
      this.summonTimer = this.enraged ? SUMMON_COOLDOWN_2 : SUMMON_COOLDOWN;
      this.startSummon(game);
    }

    // Beschwörungs-Telegraphen
    for (let i = this.spawnRings.length - 1; i >= 0; i--) {
      const r = this.spawnRings[i];
      r.timer -= dt;
      if (r.timer <= 0) {
        r.spawn();
        game.scene.remove(r.group);
        r.group.traverse((obj) => {
          if (obj instanceof THREE.Mesh) {
            obj.geometry.dispose();
            (obj.material as THREE.Material).dispose();
          }
        });
        this.spawnRings.splice(i, 1);
      } else {
        const t = 1 - r.timer / SUMMON_TELEGRAPH;
        r.group.scale.setScalar(0.5 + t * 1.2);
      }
    }

    // Ankunfts-Blitz-Flächen
    for (let i = this.arrivalFlashes.length - 1; i >= 0; i--) {
      const f = this.arrivalFlashes[i];
      f.life -= dt;
      const t = 1 - Math.max(0, f.life) / 0.3;
      f.mesh.scale.setScalar(0.5 + t * 5);
      f.mat.opacity = 0.7 * (1 - t);
      if (f.life <= 0) {
        game.scene.remove(f.mesh);
        f.mesh.geometry.dispose();
        f.mat.dispose();
        this.arrivalFlashes.splice(i, 1);
      }
    }
  }

  // --- Teleport ---

  private startTeleport(game: Game) {
    this.teleportState = 'charge';
    this.teleportCharge = this.enraged ? TELEPORT_CHARGE_2 : TELEPORT_CHARGE;
    const playerPos = game.player.body.position;
    // Ziel = aktuelle Spielerposition (leicht zufällig versetzt)
    this.teleportTarget.set(
      playerPos.x + (Math.random() - 0.5) * 2,
      0,
      playerPos.z + (Math.random() - 0.5) * 2
    );
    game.clampToArena(this.teleportTarget);
    if (!this.teleportRingInScene) {
      game.scene.add(this.teleportRing);
      this.teleportRingInScene = true;
    }
    this.teleportRing.position.set(this.teleportTarget.x, 0, this.teleportTarget.z);
    this.teleportRing.visible = true;
    playSound('spawn', 0.7);
  }

  private updateTeleportCharge(dt: number, game: Game) {
    this.teleportCharge -= dt;
    const pulse = Math.abs(Math.sin(this.waveTime * TELEPORT_RING_PULSE));
    this.teleportRingMat.opacity = 0.3 + 0.5 * pulse;
    this.teleportDiscMat.opacity = 0.1 + 0.08 * pulse;

    if (this.teleportCharge <= 0) {
      this.teleportState = 'idle';
      this.teleportRing.visible = false;
      this.teleportRingMat.opacity = 0;
      this.teleportDiscMat.opacity = 0;

      // Blitz
      this.body.position.x = this.teleportTarget.x;
      this.body.position.z = this.teleportTarget.z;
      playSound('teleport', 0.8);

      // Ankunftsexplosion + Schaden, wenn der Spieler unter dem Zielpunkt steht
      const flashMat = new THREE.MeshBasicMaterial({
        color: 0xbb66ff,
        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(this.teleportTarget.x, 0.07, this.teleportTarget.z);
      game.scene.add(flash);
      this.arrivalFlashes.push({ mesh: flash, mat: flashMat, life: 0.3 });

      if (game.player.health > 0 && game.player.body.position.y <= 0.8) {
        const dx = game.player.body.position.x - this.teleportTarget.x;
        const dz = game.player.body.position.z - this.teleportTarget.z;
        if (Math.hypot(dx, dz) < TELEPORT_RADIUS) {
          game.player.damage(TELEPORT_DAMAGE, this.teleportTarget);
        }
      }

      // Salve vom neuen Standort
      this.fireVolley(game);
    }
  }

  // --- Projektilsalve ---

  private fireVolley(game: Game) {
    const from = new THREE.Vector3(this.body.position.x, FLOAT_HEIGHT + 0.6, this.body.position.z);
    const target = game.player.body.position.clone();
    const count = this.enraged ? VOLLEY_COUNT_2 : VOLLEY_COUNT;
    const baseDir = new THREE.Vector3().subVectors(target, from);
    baseDir.y = 0;
    baseDir.normalize();
    const perp = new THREE.Vector3(-baseDir.z, 0, baseDir.x);
    for (let i = 0; i < count; i++) {
      const off = (i / (count - 1) - 0.5) * 2 * VOLLEY_SPREAD;
      const dir = new THREE.Vector3()
        .addScaledVector(baseDir, 1)
        .addScaledVector(perp, off)
        .normalize();
      const t = from.clone().add(dir.multiplyScalar(25));
      t.y = 0.5;
      game.spawnEnemyProjectile(from, t, BOLT_DAMAGE);
    }
    playSound('spawn', 0.5);
  }

  // --- Beschwörung ---

  private startSummon(game: Game) {
    const count = this.enraged ? SUMMON_COUNT_2 : SUMMON_COUNT;
    const types = [() => new Ghost(), () => new Slime(), () => new Spider(),
      () => new Archer(), () => new Shaman(), () => new Turtle()];
    for (let i = 0; i < count; i++) {
      const a = Math.random() * Math.PI * 2;
      const r = 5 + Math.random() * 8;
      const [x, z] = this.clampToArena(game,
        this.body.position.x + Math.cos(a) * r,
        this.body.position.z + Math.sin(a) * r
      );

      // Wachsender Boden-Glow als Telegraf
      const mat = new THREE.MeshBasicMaterial({
        color: 0xbb66ff,
        transparent: true,
        opacity: 0.35,
        depthWrite: false,
      });
      const ring = new THREE.Mesh(new THREE.CircleGeometry(1.6, 24), mat);
      ring.rotation.x = -Math.PI / 2;
      ring.position.set(x, 0.05, z);
      ring.scale.setScalar(0.5);
      const group = new THREE.Group();
      group.add(ring);
      game.scene.add(group);

      const type = types[Math.floor(Math.random() * types.length)];
      this.spawnRings.push({
        group,
        timer: SUMMON_TELEGRAPH,
        spawn: () => {
          game.spawnEnemyInstance(type(), x, z, 0.1);
          playSound('spawn', 0.5);
        },
      });
    }
    playSound('spawn', 0.6);
  }

  private clampToArena(game: Game, x: number, z: number): [number, number] {
    const v = new THREE.Vector3(x, 0, z);
    game.clampToArena(v);
    return [v.x, v.z];
  }

  override takeDamage(
    damage: number,
    game: Game,
    withKnockback = true,
    sourcePos?: THREE.Vector3
  ) {
    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() {
    if (this.game) {
      for (const r of this.spawnRings) {
        this.game.scene.remove(r.group);
        r.group.traverse((obj) => {
          if (obj instanceof THREE.Mesh) {
            obj.geometry.dispose();
            (obj.material as THREE.Material).dispose();
          }
        });
      }
      for (const f of this.arrivalFlashes) {
        this.game.scene.remove(f.mesh);
        f.mesh.geometry.dispose();
        f.mat.dispose();
      }
    }
    this.spawnRings = [];
    this.arrivalFlashes = [];
    this.teleportState = 'idle';
    this.teleportRing.visible = false;
    if (this.teleportRingInScene && this.game) {
      this.game.scene.remove(this.teleportRing);
    }
    this.teleportRingInScene = false;
    this.robeMat.color.set(0x6a3a9a);
    this.darkMat.color.set(0x2a1445);
    this.eyeMat.color.set(0xdd88ff);
    this.glowMat.color.set(0xbb66ff);
    this.model.scale.setScalar(MODEL_SCALE);
  }
}
