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

const IDEAL_MIN = 8;
const IDEAL_MAX = 13;
const SHOOT_RANGE = 18;
const SHOOT_COOLDOWN = 3;
const TELEGRAPH_TIME = 0.6;
const ARROW_DAMAGE = 18;
const HIT_FLASH_TIME = 0.14;

// Bogenschütze: hält Distanz und schießt Pfeile mit Boden-Telegraf (ausweichbar)
export class Archer extends Enemy {
  private model: THREE.Group;
  private boneMat!: THREE.MeshToonMaterial;
  private darkMat!: THREE.MeshToonMaterial;
  private bowGroup!: THREE.Group;
  private legL!: THREE.Group;
  private legR!: THREE.Group;
  private walkPhase = 0;
  private walkAmp = 0;
  private handL!: THREE.Mesh;
  private arrowNock!: THREE.Mesh;
  private draw = 0; // 0 = Bogen entspannt, 1 = voll ausgezogen
  private recoil = 0; // klingt nach dem Abschuss ab
  private shootTimer = 1 + Math.random();
  private telegraphs: { mesh: THREE.Mesh; mat: THREE.MeshBasicMaterial; timer: number }[] = [];
  private time = Math.random() * Math.PI * 2;
  private hitFlash = 0;

  constructor() {
    super();
    this.health = 55;
    this.speed = 4.2;
    this.xp = 22;
    this.contactDps = 15;
    this.contactRadius = 1.5;

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

    this.boneMat = new THREE.MeshToonMaterial({ color: 0xe8dcc0 });
    this.darkMat = new THREE.MeshToonMaterial({ color: 0x8a7a5a });
    const eyeMat = new THREE.MeshBasicMaterial({ color: 0xff4422 });

    // --- Skelett (Blickrichtung +Z) ---

    // Becken + Gürtel
    const pelvis = new THREE.Mesh(new THREE.SphereGeometry(0.16, 10, 8), this.boneMat);
    pelvis.scale.set(1.2, 0.7, 0.9);
    pelvis.position.y = 0.78;
    this.registerFadeMesh(pelvis);
    this.model.add(pelvis);
    const belt = new THREE.Mesh(new THREE.TorusGeometry(0.17, 0.03, 6, 12), this.darkMat);
    belt.rotation.x = Math.PI / 2;
    belt.position.y = 0.82;
    this.registerFadeMesh(belt);
    this.model.add(belt);

    // Wirbelsäule + Rippenbögen
    const spine = new THREE.Mesh(new THREE.CylinderGeometry(0.035, 0.045, 0.55, 6), this.boneMat);
    spine.position.y = 1.08;
    this.registerFadeMesh(spine);
    this.model.add(spine);
    for (const [y, r] of [[1.02, 0.2], [1.14, 0.23], [1.26, 0.19]] as const) {
      const rib = new THREE.Mesh(new THREE.TorusGeometry(r, 0.025, 6, 12), this.boneMat);
      rib.rotation.x = Math.PI / 2;
      rib.scale.set(1, 1, 0.8);
      rib.position.y = y;
      this.registerFadeMesh(rib);
      this.model.add(rib);
    }

    // Schädel mit Kiefer, Augenhöhlen und roten Augen (+Z)
    const skull = new THREE.Mesh(new THREE.SphereGeometry(0.22, 12, 10), this.boneMat);
    skull.scale.set(0.95, 1.05, 1);
    skull.position.y = 1.52;
    skull.castShadow = true;
    this.registerFadeMesh(skull);
    this.model.add(skull);
    const jaw = new THREE.Mesh(new THREE.BoxGeometry(0.16, 0.09, 0.16), this.boneMat);
    jaw.position.set(0, 1.38, 0.1);
    this.registerFadeMesh(jaw);
    this.model.add(jaw);
    for (const side of [-1, 1]) {
      const socket = new THREE.Mesh(new THREE.SphereGeometry(0.055, 8, 6), this.darkMat);
      socket.position.set(side * 0.09, 1.55, 0.17);
      this.registerFadeMesh(socket);
      this.model.add(socket);
      const eye = new THREE.Mesh(new THREE.SphereGeometry(0.035, 6, 5), eyeMat);
      eye.position.set(side * 0.09, 1.55, 0.2);
      this.model.add(eye);
    }

    // Schultern + Arme (rechts hält den Bogen, links zieht die Sehne)
    for (const side of [-1, 1]) {
      const shoulder = new THREE.Mesh(new THREE.SphereGeometry(0.07, 8, 6), this.boneMat);
      shoulder.position.set(side * 0.28, 1.34, 0);
      this.registerFadeMesh(shoulder);
      this.model.add(shoulder);
    }
    const upperArmR = new THREE.Mesh(new THREE.CylinderGeometry(0.04, 0.05, 0.32, 6), this.boneMat);
    upperArmR.position.set(0.34, 1.26, 0.08);
    upperArmR.rotation.z = -1.0;
    this.registerFadeMesh(upperArmR);
    this.model.add(upperArmR);
    const foreArmR = new THREE.Mesh(new THREE.CylinderGeometry(0.035, 0.04, 0.3, 6), this.boneMat);
    foreArmR.position.set(0.4, 1.18, 0.25);
    foreArmR.rotation.x = -1.2;
    this.registerFadeMesh(foreArmR);
    this.model.add(foreArmR);
    const handR = new THREE.Mesh(new THREE.SphereGeometry(0.05, 8, 6), this.boneMat);
    handR.position.set(0.42, 1.15, 0.4);
    this.registerFadeMesh(handR);
    this.model.add(handR);

    const upperArmL = new THREE.Mesh(new THREE.CylinderGeometry(0.04, 0.05, 0.32, 6), this.boneMat);
    upperArmL.position.set(-0.34, 1.24, 0.05);
    upperArmL.rotation.z = 1.1;
    this.registerFadeMesh(upperArmL);
    this.model.add(upperArmL);
    const foreArmL = new THREE.Mesh(new THREE.CylinderGeometry(0.035, 0.04, 0.3, 6), this.boneMat);
    foreArmL.position.set(-0.24, 1.25, 0.15);
    foreArmL.rotation.x = -1.6;
    foreArmL.rotation.z = 0.6;
    this.registerFadeMesh(foreArmL);
    this.model.add(foreArmL);
    this.handL = new THREE.Mesh(new THREE.SphereGeometry(0.05, 8, 6), this.boneMat);
    this.handL.position.set(-0.1, 1.3, 0.28);
    this.registerFadeMesh(this.handL);
    this.model.add(this.handL);

    // Beine mit Kniegelenken und Füßen (Gruppen mit Hüft-Pivot für Laufanimation)
    for (const side of [-1, 1]) {
      const leg = new THREE.Group();
      leg.position.set(side * 0.15, 0.72, 0);
      this.model.add(leg);
      const thigh = new THREE.Mesh(new THREE.CylinderGeometry(0.06, 0.07, 0.4, 6), this.boneMat);
      thigh.position.y = -0.17;
      this.registerFadeMesh(thigh);
      leg.add(thigh);
      const knee = new THREE.Mesh(new THREE.SphereGeometry(0.06, 8, 6), this.boneMat);
      knee.position.y = -0.37;
      this.registerFadeMesh(knee);
      leg.add(knee);
      const shin = new THREE.Mesh(new THREE.CylinderGeometry(0.05, 0.06, 0.35, 6), this.boneMat);
      shin.position.y = -0.54;
      this.registerFadeMesh(shin);
      leg.add(shin);
      const foot = new THREE.Mesh(new THREE.BoxGeometry(0.12, 0.06, 0.22), this.boneMat);
      foot.position.set(0, -0.68, 0.06);
      this.registerFadeMesh(foot);
      leg.add(foot);
      if (side < 0) {
        this.legL = leg;
      } else {
        this.legR = leg;
      }
    }

    // Zerfetzter Umhang auf dem Rücken
    const cape = new THREE.Mesh(new THREE.ConeGeometry(0.32, 0.75, 8, 1, true), this.darkMat);
    cape.position.set(0, 1.0, -0.2);
    cape.scale.set(1.1, 1, 0.5);
    this.registerFadeMesh(cape);
    this.model.add(cape);
    for (const [x, z] of [[-0.15, -0.28], [0, -0.32], [0.15, -0.28]] as const) {
      const tatter = new THREE.Mesh(new THREE.ConeGeometry(0.06, 0.25, 4), this.darkMat);
      tatter.rotation.x = Math.PI;
      tatter.position.set(x, 0.62, z);
      this.registerFadeMesh(tatter);
      this.model.add(tatter);
    }

    // Köcher mit Pfeilen am Rücken
    const quiver = new THREE.Mesh(new THREE.CylinderGeometry(0.08, 0.11, 0.5, 8), this.darkMat);
    quiver.position.set(0.12, 1.15, -0.28);
    quiver.rotation.x = 0.5;
    this.registerFadeMesh(quiver);
    this.model.add(quiver);
    for (const dx of [-0.03, 0.02, 0.06]) {
      const qArrow = new THREE.Mesh(new THREE.CylinderGeometry(0.012, 0.012, 0.28, 5), this.boneMat);
      qArrow.position.set(0.12 + dx, 1.42, -0.4);
      qArrow.rotation.x = 0.5;
      this.registerFadeMesh(qArrow);
      this.model.add(qArrow);
    }

    // Bogen (hängt an Gruppe, wird beim Schuss angehoben):
    // Halbkreis-Bogen mit Sehne und eingelegtem Pfeil
    this.bowGroup = new THREE.Group();
    this.bowGroup.position.set(0.42, 1.15, 0.45);
    this.model.add(this.bowGroup);
    const bowOrient = new THREE.Group();
    bowOrient.rotation.y = -Math.PI / 2; // Bogenebene in Blickrichtung
    this.bowGroup.add(bowOrient);
    const bow = new THREE.Mesh(new THREE.TorusGeometry(0.4, 0.028, 6, 16, Math.PI), this.darkMat);
    bow.rotation.z = -Math.PI / 2; // Bogenbauch zeigt nach +Z
    this.registerFadeMesh(bow);
    bowOrient.add(bow);
    const string = new THREE.Mesh(new THREE.CylinderGeometry(0.012, 0.012, 0.8, 5), this.darkMat);
    this.registerFadeMesh(string);
    bowOrient.add(string);
    // Pfeil sitzt auf Sehne und Bogen: Schaft entlang lokaler X-Achse
    // (zeigt nach Welt-+Z), Nocke an der Sehne
    this.arrowNock = new THREE.Mesh(new THREE.CylinderGeometry(0.014, 0.014, 0.75, 5), this.boneMat);
    this.arrowNock.rotation.z = -Math.PI / 2;
    this.arrowNock.position.x = 0.28;
    this.registerFadeMesh(this.arrowNock);
    bowOrient.add(this.arrowNock);
    const arrowHead = new THREE.Mesh(new THREE.ConeGeometry(0.035, 0.1, 6), this.darkMat);
    arrowHead.position.y = 0.42;
    this.registerFadeMesh(arrowHead);
    this.arrowNock.add(arrowHead);
  }

  playAnim() {
    // Prozedurale Optik
  }

  updateAnimation(dt: number) {
    this.updateFadeDeath(dt, { duration: 0.9, sink: 0.4 });
  }

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

    // Hit-Feedback
    if (this.hitFlash > 0) {
      this.hitFlash -= dt;
      this.boneMat.color.set(0xffffff);
      this.darkMat.color.set(0xffffff);
    } else {
      this.boneMat.color.set(0xe8dcc0);
      this.darkMat.color.set(0x8a7a5a);
    }

    const toPlayer = new THREE.Vector3()
      .subVectors(game.player.body.position, this.body.position);
    toPlayer.y = 0;
    const dist = toPlayer.length();

    let moved = false;
    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
      );

      // Distanz halten: weg, wenn zu nah; hin, wenn zu weit; sonst seitlich
      let move = new THREE.Vector3();
      if (dist < IDEAL_MIN) {
        move.copy(dir).multiplyScalar(-1);
      } else if (dist > IDEAL_MAX) {
        move.copy(dir);
      } else {
        move.set(-dir.z, 0, dir.x).multiplyScalar(Math.sin(this.time * 2) * 0.6);
      }
      if (this.knockback > 0) {
        move = dir.clone().multiplyScalar(-1);
        this.knockback -= dt;
      }
      if (move.lengthSq() > 0) {
        move.normalize().multiplyScalar(this.speed);
        this.body.position.x += move.x * dt;
        this.body.position.z += move.z * dt;
        game.clampToArena(this.body.position);
        moved = true;
      }
    }

    // Laufanimation: Beine gegengleich schwingen, Amplitude weich ein-/ausblenden
    this.walkAmp += ((moved ? 0.55 : 0) - this.walkAmp) * Math.min(1, dt * 8);
    if (this.walkAmp > 0.01) {
      this.walkPhase += dt * 11;
    }
    const swing = Math.sin(this.walkPhase) * this.walkAmp;
    this.legL.rotation.x = swing;
    this.legR.rotation.x = -swing;
    this.model.position.y = Math.abs(Math.cos(this.walkPhase)) * this.walkAmp * 0.12;

    // Schuss mit Telegraf
    this.shootTimer -= dt;
    if (dist < SHOOT_RANGE && this.shootTimer <= 0 && game.player.health > 0) {
      this.shootTimer = SHOOT_COOLDOWN + Math.random() * 0.5;
      this.startShot(game);
    }

    // Telegrafen ablaufen lassen -> Pfeil los
    for (let i = this.telegraphs.length - 1; i >= 0; i--) {
      const t = this.telegraphs[i];
      t.timer -= dt;
      if (t.timer <= 0) {
        this.fireArrow(game, t.mesh.position);
        game.scene.remove(t.mesh);
        t.mesh.geometry.dispose();
        t.mat.dispose();
        this.telegraphs.splice(i, 1);
      } else {
        t.mat.opacity = 0.25 + 0.3 * Math.abs(Math.sin(this.time * 10));
      }
    }

    // Schießanimation: während des Telegrafs Bogen heben und Sehne spannen,
    // beim Abschuss Recoil-Kick und schnelles Entspannen
    const aiming = this.telegraphs.length > 0;
    const drawRate = aiming ? 6 : this.recoil > 0 ? 25 : 10;
    this.draw += ((aiming ? 1 : 0) - this.draw) * Math.min(1, dt * drawRate);
    this.recoil = Math.max(0, this.recoil - dt);
    const aim = this.draw;
    this.bowGroup.position.set(
      0.42 - aim * 0.17,
      1.15 + aim * 0.2,
      0.45 + aim * 0.1
    );
    this.bowGroup.rotation.x = -aim * 0.15;
    this.bowGroup.rotation.z =
      Math.sin(this.time * 2) * 0.1 * (1 - aim) - this.recoil * 2;
    this.handL.position.z = 0.28 - aim * 0.16;
    this.arrowNock.position.x = 0.28 - aim * 0.14;
  }

  private startShot(game: Game) {
    // Roter Telegraf an der aktuellen Spielerposition (Zielpunkt des Pfeils)
    const mat = new THREE.MeshBasicMaterial({
      color: 0xff3344,
      transparent: true,
      opacity: 0.4,
      side: THREE.DoubleSide,
      depthWrite: false,
    });
    const ring = new THREE.Mesh(new THREE.RingGeometry(0.5, 0.8, 24), mat);
    ring.rotation.x = -Math.PI / 2;
    const pos = game.player.body.position;
    ring.position.set(pos.x, 0.06, pos.z);
    game.scene.add(ring);
    this.telegraphs.push({ mesh: ring, mat, timer: TELEGRAPH_TIME });
    playSound('spawn', 0.4);
  }

  private fireArrow(game: Game, targetPos: THREE.Vector3) {
    if (game.player.health <= 0) return;
    const from = this.body.position.clone();
    from.y += 1.2;
    const target = targetPos.clone();
    target.y += 0.6;
    game.spawnEnemyProjectile(from, target, ARROW_DAMAGE);
    // Recoil-Kick, klingt in update() ab
    this.recoil = 0.25;
  }

  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.cleanupTelegraphs(game);
    this.beginFadeDeath();
  }

  override dispose() {
    super.dispose();
    if (this.game) this.cleanupTelegraphs(this.game);
  }

  private game: Game | null = null;

  private cleanupTelegraphs(game: Game) {
    for (const t of this.telegraphs) {
      game.scene.remove(t.mesh);
      t.mesh.geometry.dispose();
      t.mat.dispose();
    }
    this.telegraphs = [];
  }
}
