import * as THREE from 'three';
import type { Game } from './Game';
import { Enemy } from './Enemy';
import { Spider, buildSpiderModel, getSpiderAnimations } from './Spider';
import { playSound } from './SoundManager';
import { WebPatch } from './WebPatch';

const MAX_HEALTH = 8500;
const WEB_COOLDOWN = 4.5;
const SCREAM_COOLDOWN = 6;
const SPIT_COOLDOWN = 5;
const WEB_RADIUS = 2.2;
const WEB_DURATION = 3;
const WEB_DIRECT_ROOT = 0.5;
const SPIDERLING_COUNT = 3;
const SPIDERLING_COUNT_2 = 5;
const SPIDERLING_MAX_ALIVE = 8;
const SPIT_COUNT = 3;
const SPIT_COUNT_2 = 5;
const SPIT_DAMAGE = 15;
const ENRAGE_RATIO = 0.4;
const HIT_FLASH_TIME = 0.14;
const WEB_BLOB_SPEED = 7;
const WEB_BLOB_PROXIMITY = 1.3;
const WEB_BLOB_DAMAGE = 10;

export class SpiderQueen extends Enemy {
  spawnAtCenter = true;
  private game: Game | null = null;
  private waveTime = 0;
  private webTimer = 2;
  private screamTimer = 4;
  private spitTimer = 5;
  private enraged = false;
  private effectsReady = false;
  private hitFlash = 0;

  private model!: THREE.Group;
  private tintMats: THREE.MeshToonMaterial[] = [];

  private webBlobs: WebBlob[] = [];
  private webs: WebPatch[] = [];

  constructor() {
    super();
    this.health = MAX_HEALTH;
    this.maxHealth = MAX_HEALTH;
    this.isBoss = true;
    this.displayName = 'Spinnenkönigin';
    this.immovable = true;
    this.speed = 0;
    this.xp = 240;
    this.contactDps = 0;
    this.contactRadius = 0;
    this.guaranteedDrop = true;

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

    // Königinnen-Modell: bestehende Spinne, größer + violett eingefärbt
    const spiderModel = buildSpiderModel(3.2, 0x9a5ac8);
    if (spiderModel) {
      this.model.add(spiderModel);
      spiderModel.traverse((child) => {
        if (child instanceof THREE.Mesh && child.material instanceof THREE.MeshToonMaterial) {
          this.tintMats.push(child.material);
        }
      });

      if (getSpiderAnimations().length > 0) {
        this.mixer = new THREE.AnimationMixer(spiderModel);
        for (const clip of getSpiderAnimations()) {
          this.clips.set(clip.name, this.mixer.clipAction(clip));
        }
        this.playAnim('stand');
      }
    } else {
      // Fallback (Modell nicht geladen): einfache große Spinne
      const skinMat = new THREE.MeshToonMaterial({ color: 0x7a3a8a });
      const darkMat = new THREE.MeshToonMaterial({ color: 0x4a2260 });
      this.tintMats.push(skinMat, darkMat);
      const abdomen = new THREE.Mesh(new THREE.SphereGeometry(1.6, 24, 18), skinMat);
      abdomen.scale.set(1, 1.05, 1.35);
      abdomen.position.set(0, 2.0, -1.7);
      abdomen.castShadow = true;
      this.registerFadeMesh(abdomen);
      this.model.add(abdomen);
      const cephalo = new THREE.Mesh(new THREE.SphereGeometry(1.0, 20, 16), skinMat);
      cephalo.scale.set(1, 0.85, 1.1);
      cephalo.position.set(0, 1.9, 0.6);
      cephalo.castShadow = true;
      this.registerFadeMesh(cephalo);
      this.model.add(cephalo);
      const head = new THREE.Mesh(new THREE.SphereGeometry(0.6, 16, 12), darkMat);
      head.scale.set(1, 0.8, 0.9);
      head.position.set(0, 2.0, 1.5);
      this.registerFadeMesh(head);
      this.model.add(head);
      const eyeMat = new THREE.MeshBasicMaterial({ color: 0xff3333 });
      for (const side of [-1, 1]) {
        const eye = new THREE.Mesh(new THREE.SphereGeometry(0.13, 8, 6), eyeMat);
        eye.position.set(side * 0.32, 2.15, 1.85);
        this.model.add(eye);
      }
      const legMat = new THREE.MeshToonMaterial({ color: 0x5a2a6a });
      this.tintMats.push(legMat);
      for (let i = 0; i < 8; i++) {
        const side = i < 4 ? -1 : 1;
        const back = i % 2 === 0;
        const leg = new THREE.Mesh(new THREE.CylinderGeometry(0.09, 0.13, 1.1, 6), legMat);
        leg.position.set(side * (back ? 1.0 : 0.9), 0.6, back ? -0.9 : 0.4);
        leg.rotation.z = side * 1.1;
        this.registerFadeMesh(leg);
        this.model.add(leg);
      }
    }
  }

  playAnim(name: string, loop = true, speed = 1) {
    if (!this.clips.has(name) || this.currentAnim === name) return;
    if (this.currentAnim) {
      const prev = this.clips.get(this.currentAnim);
      if (prev) prev.stop();
    }
    this.currentAnim = name;
    const action = this.clips.get(name)!;
    action.reset();
    action.setLoop(loop ? THREE.LoopRepeat : THREE.LoopOnce, loop ? Infinity : 1);
    action.clampWhenFinished = !loop;
    action.setEffectiveTimeScale(speed);
    action.play();
  }

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

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

    // Hit-Feedback: Materialien kurz weiß aufblitzen
    if (this.hitFlash > 0) {
      this.hitFlash -= dt;
      for (const mat of this.tintMats) mat.color.set(0xffffff);
      this.model.scale.setScalar(1 + (this.hitFlash / HIT_FLASH_TIME) * 0.05);
    } else {
      for (const mat of this.tintMats) mat.color.set(this.enraged ? 0xff6644 : 0x9a5ac8);
      this.model.scale.setScalar(1);
    }

    // Zum Spieler drehen
    const playerPos = game.player.body.position;
    const toPlayer = new THREE.Vector3().subVectors(playerPos, this.body.position);
    toPlayer.y = 0;
    if (toPlayer.length() > 0.1) {
      this.body.lookAt(
        this.body.position.x + toPlayer.x,
        this.body.position.y,
        this.body.position.z + toPlayer.z
      );
    }

    // Enrage unter 40% HP
    if (!this.enraged && this.health < MAX_HEALTH * ENRAGE_RATIO) {
      this.enraged = true;
      playSound('damage', 0.8);
      game.triggerCameraShake(0.35, 0.4);
    }

    // Angriffe
    this.webTimer -= dt;
    if (this.webTimer <= 0) {
      this.webTimer = this.enraged ? WEB_COOLDOWN * 0.65 : WEB_COOLDOWN;
      this.spitWeb(game);
    }

    this.screamTimer -= dt;
    if (this.screamTimer <= 0) {
      this.screamTimer = this.enraged ? SCREAM_COOLDOWN * 0.6 : SCREAM_COOLDOWN;
      this.scream(game);
    }

    this.spitTimer -= dt;
    if (this.spitTimer <= 0) {
      this.spitTimer = this.enraged ? SPIT_COOLDOWN * 0.7 : SPIT_COOLDOWN;
      this.spitAcid(game);
    }

    // Netz-Bälle aktualisieren
    for (let i = this.webBlobs.length - 1; i >= 0; i--) {
      const blob = this.webBlobs[i];
      if (!blob.update(dt, game)) {
        this.webBlobs.splice(i, 1);
        const patch = new WebPatch(
          blob.body.position,
          this.enraged ? WEB_RADIUS + 0.4 : WEB_RADIUS,
          this.enraged ? WEB_DURATION * 1.5 : WEB_DURATION
        );
        game.scene.add(patch.body);
        this.webs.push(patch);
        blob.dispose();
        playSound('explosion', 0.4);
      }
    }

    // Netze aktualisieren
    for (let i = this.webs.length - 1; i >= 0; i--) {
      const web = this.webs[i];
      if (!web.update(dt, game)) {
        this.webs.splice(i, 1);
        web.dispose();
      }
    }
  }

  private spitWeb(game: Game) {
    const from = new THREE.Vector3(this.body.position.x, 2.2, this.body.position.z);
    const target = game.player.body.position.clone();
    target.y = 0;

    const blob = new WebBlob(from, target);
    game.scene.add(blob.body);
    this.webBlobs.push(blob);
    playSound('spawn', 0.5);
  }

  private scream(game: Game) {
    // Nicht endlos viele Spinnlinge anhäufen
    let alive = 0;
    for (const e of game.enemies) {
      if (e instanceof Spiderling && !e.dead) alive++;
    }
    const count = Math.min(
      this.enraged ? SPIDERLING_COUNT_2 : SPIDERLING_COUNT,
      SPIDERLING_MAX_ALIVE - alive
    );
    if (count <= 0) return;

    for (let i = 0; i < count; i++) {
      const mini = new Spiderling();
      const a = Math.random() * Math.PI * 2;
      const r = 4 + Math.random() * 4;
      game.spawnEnemyInstance(
        mini,
        this.body.position.x + Math.cos(a) * r,
        this.body.position.z + Math.sin(a) * r,
        0.1
      );
    }
    playSound('spawn', 0.7);
  }

  private spitAcid(game: Game) {
    const from = new THREE.Vector3(this.body.position.x, 2.4, this.body.position.z);
    const target = game.player.body.position.clone();
    const count = this.enraged ? SPIT_COUNT_2 : SPIT_COUNT;
    const spread = 0.35;
    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 * spread;
      const dir = new THREE.Vector3()
        .addScaledVector(baseDir, 1)
        .addScaledVector(perp, off)
        .normalize();
      const t = from.clone().add(dir.multiplyScalar(20));
      t.y = 0;
      game.spawnEnemyProjectile(from, t, SPIT_DAMAGE);
    }
    playSound('spawn', 0.6);
  }

  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() {
    for (const blob of this.webBlobs) blob.dispose();
    this.webBlobs = [];
    for (const web of this.webs) web.dispose();
    this.webs = [];
    for (const mat of this.tintMats) mat.color.set(0x9a5ac8);
    this.model.scale.setScalar(1);
  }
}

// Spinnling: wie die normale Spinne, stirbt aber sichtbar (schrumpft + sinkt),
// damit keine stehenden Leichen in der Arena zurückbleiben.
export class Spiderling extends Spider {
  private fadeDying = false;
  private fadeDieTime = 0;

  constructor() {
    super();
    this.health = 25;
    this.speed = 4.2;
    this.xp = 5;
    this.contactDps = 12;
    this.contactRadius = 1.5;
    // Kein grace: Spider dekrementiert grace nie, sonst wären die Spinnlinge unverwundbar.
    this.body.scale.setScalar(0.5);
  }

  protected onDeath(_game: Game) {
    this.fadeDying = true;
  }

  updateAnimation(dt: number) {
    super.updateAnimation(dt);
    if (this.fadeDying) {
      this.fadeDieTime += dt;
      const t = Math.min(this.fadeDieTime / 0.5, 1);
      this.body.scale.setScalar(0.5 * (1 - t));
      this.body.position.y -= dt * 1.2;
    }
  }
}

// Netz-Ball: fliegt zum Spieler und hinterlässt beim Auftreffen ein WebPatch
class WebBlob {
  body: THREE.Group;
  target: THREE.Vector3;
  private vel: THREE.Vector3;
  private life = 6;
  dead = false;

  constructor(from: THREE.Vector3, target: THREE.Vector3) {
    this.target = target.clone();
    this.body = new THREE.Group();
    this.body.position.copy(from);

    this.vel = new THREE.Vector3().subVectors(target, from);
    this.vel.y = 0;
    const dist = this.vel.length();
    if (dist > 0.01) {
      this.vel.normalize().multiplyScalar(WEB_BLOB_SPEED);
    }

    const glowMat = new THREE.MeshBasicMaterial({
      color: 0xddddff,
      transparent: true,
      opacity: 0.5,
      blending: THREE.AdditiveBlending,
      depthWrite: false,
    });
    const glow = new THREE.Mesh(new THREE.SphereGeometry(0.5, 12, 12), glowMat);
    this.body.add(glow);

    const coreMat = new THREE.MeshToonMaterial({ color: 0xf0f0ff });
    const core = new THREE.Mesh(new THREE.SphereGeometry(0.3, 10, 10), coreMat);
    this.body.add(core);
  }

  update(dt: number, game: Game): boolean {
    this.life -= dt;
    this.body.position.x += this.vel.x * dt;
    this.body.position.z += this.vel.z * dt;
    this.body.position.y = 1.4 + Math.sin(this.life * 4) * 0.1;

    if (game.player.health > 0) {
      const dist = this.body.position.distanceTo(game.player.body.position);
      if (dist < WEB_BLOB_PROXIMITY && game.player.rootCooldown <= 0) {
        game.player.rootTime = WEB_DIRECT_ROOT;
        game.player.rootCooldown = 1.0;
        game.player.damage(WEB_BLOB_DAMAGE, this.body.position);
      }
    }

    const dx = this.target.x - this.body.position.x;
    const dz = this.target.z - this.body.position.z;
    if (this.life <= 0 || dx * dx + dz * dz < 0.25) {
      this.dead = true;
      return false;
    }
    return true;
  }

  dispose() {
    this.body.removeFromParent();
    for (const child of [...this.body.children]) {
      if (child instanceof THREE.Mesh) {
        child.geometry.dispose();
        (child.material as THREE.Material).dispose();
      }
    }
  }
}
