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

const FLOAT_HEIGHT = 1.0;
const BOB_AMPLITUDE = 0.3;
const BOB_SPEED = 2.4;
const VISIBLE_TIME = 2.5;
const FADE_TIME = 0.35;
const HIDDEN_MIN = 0.6;
const HIDDEN_MAX = 1.5;

type VisState = 'visible' | 'fadingOut' | 'hidden' | 'fadingIn';

export class Ghost extends Enemy {
  private model: THREE.Group;
  private shadow: THREE.Mesh;
  private shadowMat: THREE.MeshBasicMaterial;
  private whiteMat: THREE.MeshToonMaterial;
  private darkMat: THREE.MeshToonMaterial;
  private blushMat: THREE.MeshToonMaterial;
  private shadowCasters: THREE.Mesh[] = [];
  private arms: THREE.Mesh[] = [];
  private bobTime = Math.random() * Math.PI * 2;
  private visState: VisState = 'visible';
  private visTimer = VISIBLE_TIME + Math.random();
  private fadeDying = false;
  private fadeDieTime = 0;

  constructor() {
    super();
    this.health = 40;
    this.speed = 7.5;
    this.xp = 15;

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

    this.whiteMat = new THREE.MeshToonMaterial({
      color: 0xf8f8ff,
      emissive: 0x2a2a3a,
      transparent: true,
      opacity: 0.96,
    });
    this.darkMat = new THREE.MeshToonMaterial({
      color: 0x1c1c2e,
      transparent: true,
      opacity: 1,
    });
    this.blushMat = new THREE.MeshToonMaterial({
      color: 0xffaacc,
      transparent: true,
      opacity: 0.85,
    });

    // Meringue body: stacked blobs tapering to a swirl peak
    const base = new THREE.Mesh(new THREE.SphereGeometry(0.62, 24, 18), this.whiteMat);
    base.scale.set(1.05, 0.8, 1.05);
    base.position.y = 0.3;
    this.addCaster(base);

    const mid = new THREE.Mesh(new THREE.SphereGeometry(0.45, 20, 16), this.whiteMat);
    mid.position.y = 0.78;
    this.addCaster(mid);

    const tip = new THREE.Mesh(new THREE.SphereGeometry(0.27, 16, 12), this.whiteMat);
    tip.position.y = 1.18;
    this.addCaster(tip);

    // Swirl peak (little curled top like a meringue)
    const swirl = new THREE.Mesh(new THREE.TorusGeometry(0.2, 0.075, 8, 20), this.whiteMat);
    swirl.position.y = 1.3;
    swirl.rotation.x = -0.6;
    this.addCaster(swirl);

    const curl = new THREE.Mesh(new THREE.SphereGeometry(0.1, 10, 8), this.whiteMat);
    curl.position.set(0, 1.48, -0.02);
    this.addCaster(curl);

    // Stubby little arms
    const armGeom = new THREE.CapsuleGeometry(0.09, 0.16, 4, 10);
    for (const side of [-1, 1]) {
      const arm = new THREE.Mesh(armGeom, this.whiteMat);
      arm.position.set(side * 0.72, 0.72, 0.12);
      arm.rotation.z = side * 0.55;
      arm.rotation.x = -0.35;
      this.addCaster(arm);
      this.arms.push(arm);
    }

    // Face
    const eyeGeom = new THREE.SphereGeometry(0.09, 12, 10);
    for (const side of [-1, 1]) {
      const eye = new THREE.Mesh(eyeGeom, this.darkMat);
      eye.position.set(side * 0.2, 0.85, 0.44);
      this.model.add(eye);
    }

    const mouth = new THREE.Mesh(
      new THREE.TorusGeometry(0.09, 0.025, 6, 14, Math.PI),
      this.darkMat
    );
    mouth.position.set(0, 0.66, 0.48);
    mouth.rotation.z = Math.PI;
    this.model.add(mouth);

    for (const side of [-1, 1]) {
      const blush = new THREE.Mesh(new THREE.SphereGeometry(0.075, 10, 8), this.blushMat);
      blush.position.set(side * 0.34, 0.68, 0.38);
      this.model.add(blush);
    }

    // Blob shadow on the ground (floating ghost, no real shadow casting)
    this.shadowMat = new THREE.MeshBasicMaterial({
      color: 0x000000,
      transparent: true,
      opacity: 0.25,
    });
    this.shadow = new THREE.Mesh(new THREE.CircleGeometry(0.55, 20), this.shadowMat);
    this.shadow.rotation.x = -Math.PI / 2;
    this.shadow.renderOrder = 998;
    this.body.add(this.shadow);
  }

  private addCaster(mesh: THREE.Mesh) {
    mesh.castShadow = true;
    this.model.add(mesh);
    this.shadowCasters.push(mesh);
  }

  playAnim() {
    // No rigged animations, visuals are procedural
  }

  updateAnimation(dt: number) {
    if (!this.fadeDying) return;
    this.fadeDieTime += dt;
    const t = Math.min(this.fadeDieTime / 0.9, 1);
    const scale = Math.max(0.05, 1 - t * 0.95);
    this.model.scale.set(scale, scale, scale);
    this.body.position.y = Math.max(-1, this.body.position.y - dt * 2.2);
    this.setOpacity(1 - t);
  }

  update(dt: number, game: Game) {
    this.bobTime += dt * BOB_SPEED;
    this.body.position.y = FLOAT_HEIGHT + Math.sin(this.bobTime) * BOB_AMPLITUDE;

    // Shadow stays on the ground
    this.shadow.position.y = 0.06 - this.body.position.y;

    // Gentle squash & stretch
    const squash = 1 + Math.sin(this.bobTime * 2) * 0.05;
    this.model.scale.y = squash;
    this.model.scale.x = 1 + (1 - squash) * 0.5;
    this.model.scale.z = this.model.scale.x;

    // Arm wiggle
    for (let i = 0; i < this.arms.length; i++) {
      const side = i === 0 ? -1 : 1;
      this.arms[i].rotation.z = side * 0.55 + Math.sin(this.bobTime + i * Math.PI) * 0.2;
    }

    // Invisibility cycle
    this.visTimer -= dt;
    switch (this.visState) {
      case 'visible':
        if (this.visTimer <= 0) {
          this.visState = 'fadingOut';
          this.visTimer = FADE_TIME;
        }
        break;
      case 'fadingOut': {
        const op = Math.max(0, this.visTimer / FADE_TIME);
        this.setOpacity(op);
        if (this.visTimer <= 0) {
          this.visState = 'hidden';
          this.visTimer = HIDDEN_MIN + Math.random() * (HIDDEN_MAX - HIDDEN_MIN);
        }
        break;
      }
      case 'hidden':
        this.setOpacity(0);
        if (this.visTimer <= 0) {
          this.visState = 'fadingIn';
          this.visTimer = FADE_TIME;
        }
        break;
      case 'fadingIn': {
        const op = 1 - Math.max(0, this.visTimer / FADE_TIME);
        this.setOpacity(op);
        if (this.visTimer <= 0) {
          this.visState = 'visible';
          this.visTimer = VISIBLE_TIME + Math.random() * 0.5;
        }
        break;
      }
    }

    // Chase the player (stays hittable and dangerous even while invisible)
    this.chase(dt, game);
  }

  // Getrennt für Subklassen (z. B. MazeGuard: erst aktiv, wenn der Spieler nah ist)
  protected chase(dt: number, game: Game) {
    const toPlayer = new THREE.Vector3()
      .subVectors(game.player.body.position, 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(this.speed);
      if (this.knockback > 0) {
        moveDir = dir.multiplyScalar(-this.speed);
        this.knockback -= dt;
      }
      this.body.position.x += moveDir.x * dt;
      this.body.position.z += moveDir.z * dt;
      game.clampToArena(this.body.position);
    }
  }

  private setOpacity(op: number) {
    this.whiteMat.opacity = 0.96 * op;
    this.darkMat.opacity = op;
    this.blushMat.opacity = 0.85 * op;
    this.shadowMat.opacity = 0.25 * op;
    const casts = op > 0.4;
    for (const mesh of this.shadowCasters) mesh.castShadow = casts;
  }

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

  dispose() {
    super.dispose();
    this.whiteMat.dispose();
    this.darkMat.dispose();
    this.blushMat.dispose();
    this.shadowMat.dispose();
    this.shadow.geometry.dispose();
  }
}
