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

const JUMP_DISTANCE = 6.5;
const JUMP_HEIGHT = 2.6;
const JUMP_DURATION = 0.65;
const CROUCH_TIME = 0.35;
const LAND_TIME = 0.3;
const PAUSE_TIME = 0.6;

type FrogState = 'crouch' | 'leap' | 'land' | 'pause';

export class Frog extends Enemy {
  private model: THREE.Group;
  private bodyMesh: THREE.Mesh;
  private eyeL: THREE.Mesh;
  private eyeR: THREE.Mesh;
  private legs: THREE.Mesh[] = [];
  private shadow: THREE.Mesh;
  private shadowMat: THREE.MeshBasicMaterial;
  private whiteMat: THREE.MeshToonMaterial;
  private state: FrogState = 'pause';
  private stateTimer = 0.4;
  private jumpFrom = new THREE.Vector3();
  private jumpTo = new THREE.Vector3();
  private jumpProgress = 0;
  private airborne = false;
  private hopTime = 0;

  constructor() {
    super();
    this.health = 35;
    this.xp = 12;

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

    const greenMat = new THREE.MeshToonMaterial({ color: 0x55cc33 });
    this.whiteMat = new THREE.MeshToonMaterial({ color: 0xf0fff0 });

    // Squat body
    this.bodyMesh = new THREE.Mesh(new THREE.SphereGeometry(0.55, 20, 16), greenMat);
    this.bodyMesh.scale.set(1, 0.72, 1.1);
    this.bodyMesh.position.y = 0.42;
    this.bodyMesh.castShadow = true;
    this.registerFadeMesh(this.bodyMesh);
    this.model.add(this.bodyMesh);

    // Belly
    const belly = new THREE.Mesh(new THREE.SphereGeometry(0.4, 16, 12), this.whiteMat);
    belly.scale.set(0.85, 0.5, 0.85);
    belly.position.set(0, 0.3, 0.42);
    this.registerFadeMesh(belly);
    this.model.add(belly);

    // Bulging eyes (face toward the player: +Z)
    const eyeGeom = new THREE.SphereGeometry(0.17, 14, 12);
    const pupilGeom = new THREE.SphereGeometry(0.08, 10, 8);
    const pupilMat = new THREE.MeshToonMaterial({ color: 0x111122 });
    this.eyeL = new THREE.Mesh(eyeGeom, this.whiteMat);
    this.eyeR = new THREE.Mesh(eyeGeom, this.whiteMat);
    this.eyeL.position.set(-0.24, 0.72, 0.28);
    this.eyeR.position.set(0.24, 0.72, 0.28);
    this.eyeL.castShadow = true;
    this.eyeR.castShadow = true;
    this.registerFadeMesh(this.eyeL);
    this.registerFadeMesh(this.eyeR);
    this.model.add(this.eyeL, this.eyeR);

    for (const eye of [this.eyeL, this.eyeR]) {
      const pupil = new THREE.Mesh(pupilGeom, pupilMat);
      pupil.position.set(0, 0.02, 0.12);
      this.registerFadeMesh(pupil);
      eye.add(pupil);
    }

    // Folded back legs
    const legGeom = new THREE.SphereGeometry(0.16, 12, 10);
    for (const side of [-1, 1]) {
      const thigh = new THREE.Mesh(legGeom, greenMat);
      thigh.scale.set(0.8, 1, 1.2);
      thigh.position.set(side * 0.42, 0.25, -0.35);
      thigh.rotation.y = side * 0.5;
      thigh.castShadow = true;
      this.registerFadeMesh(thigh);
      this.model.add(thigh);
      this.legs.push(thigh);
    }

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

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

  updateAnimation(dt: number) {
    this.updateFadeDeath(dt, { duration: 0.8, sink: 0.6 });
  }

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

    // Knocked back: fall to the ground and slide away from the player
    if (this.knockback > 0) {
      this.knockback -= dt;
      this.airborne = false;
      this.state = 'pause';
      this.stateTimer = 0.3;
      const away = new THREE.Vector3()
        .subVectors(this.body.position, game.player.body.position);
      away.y = 0;
      away.normalize().multiplyScalar(this.speed);
      this.body.position.x += away.x * dt;
      this.body.position.z += away.z * dt;
      this.body.position.y = 0.1;
    } else {
      switch (this.state) {
        case 'pause':
          this.body.position.y = 0.1;
          this.stateTimer -= dt;
          if (this.stateTimer <= 0) {
            this.state = 'crouch';
            this.stateTimer = CROUCH_TIME;
          }
          break;

        case 'crouch': {
          this.body.position.y = 0.1;
          const target = game.player.body.position.clone();
          const toPlayer = target.sub(this.body.position);
          toPlayer.y = 0;
          if (toPlayer.length() > JUMP_DISTANCE) {
            toPlayer.normalize().multiplyScalar(JUMP_DISTANCE);
          }
          this.jumpFrom.copy(this.body.position);
          this.jumpTo.set(
            this.body.position.x + toPlayer.x,
            0.1,
            this.body.position.z + toPlayer.z
          );
          this.body.lookAt(this.jumpTo.x, this.body.position.y, this.jumpTo.z);
          this.stateTimer -= dt;
          if (this.stateTimer <= 0) {
            this.state = 'leap';
            this.jumpProgress = 0;
            this.airborne = true;
          }
          break;
        }

        case 'leap': {
          this.jumpProgress += dt / JUMP_DURATION;
          const t = Math.min(this.jumpProgress, 1);
          this.body.position.x = THREE.MathUtils.lerp(this.jumpFrom.x, this.jumpTo.x, t);
          this.body.position.z = THREE.MathUtils.lerp(this.jumpFrom.z, this.jumpTo.z, t);
          this.body.position.y = 0.1 + Math.sin(t * Math.PI) * JUMP_HEIGHT;
          if (this.jumpProgress >= 1) {
            this.airborne = false;
            this.state = 'land';
            this.stateTimer = LAND_TIME;
            this.spawnDust(game.scene);
          }
          break;
        }

        case 'land':
          this.body.position.y = 0.1;
          this.stateTimer -= dt;
          if (this.stateTimer <= 0) {
            this.state = 'pause';
            this.stateTimer = PAUSE_TIME + Math.random() * 0.5;
          }
          break;
      }
    }

    // Squash & stretch animation
    let sy = 1;
    let sxz = 1;
    if (this.state === 'crouch') {
      sy = 0.62;
      sxz = 1.25;
    } else if (this.state === 'leap') {
      sy = 1.3;
      sxz = 0.8;
    } else if (this.state === 'land') {
      sy = 0.7;
      sxz = 1.25;
    }
    this.model.scale.set(sxz, sy, sxz);
    this.eyeL.position.y = 0.72 * sy;
    this.eyeR.position.y = 0.72 * sy;

    // Leg twitch
    for (let i = 0; i < this.legs.length; i++) {
      const side = i === 0 ? -1 : 1;
      this.legs[i].rotation.z = side * Math.sin(this.hopTime * 8) * 0.1;
    }

    // Shadow stays on the ground
    this.shadow.position.y = 0.06 - this.body.position.y;
    const shadowScale = 1 - (this.body.position.y - 0.1) / (JUMP_HEIGHT * 2);
    this.shadowMat.opacity = 0.25 * Math.max(0.4, shadowScale);

    game.clampToArena(this.body.position);
  }

  canBeSlashHit(): boolean {
    return !this.airborne;
  }

  protected onDeath(game: Game) {
    playSound('spawn', 0.4);
    this.beginFadeDeath();
    if (this.body.position.y > 0.15) {
      this.body.position.y = 0.15;
    }
    void game;
  }

  private spawnDust(scene: THREE.Scene) {
    const count = 10;
    const positions = new Float32Array(count * 3);
    const velocities: THREE.Vector3[] = [];
    const lives = new Float32Array(count).fill(0.4);
    for (let i = 0; i < count; i++) {
      positions[i * 3] = this.body.position.x;
      positions[i * 3 + 1] = 0.15;
      positions[i * 3 + 2] = this.body.position.z;
      const angle = Math.random() * Math.PI * 2;
      velocities.push(new THREE.Vector3(
        Math.cos(angle) * 1.2,
        Math.random() * 1.5,
        Math.sin(angle) * 1.2
      ));
    }
    const geom = new THREE.BufferGeometry();
    geom.setAttribute('position', new THREE.BufferAttribute(positions, 3));
    const mat = new THREE.PointsMaterial({
      color: 0xaa9988,
      size: 0.15,
      transparent: true,
      depthWrite: false,
    });
    const points = new THREE.Points(geom, mat);
    points.renderOrder = 999;
    scene.add(points);

    const startTime = performance.now();
    const update = () => {
      const elapsed = (performance.now() - startTime) / 1000;
      if (elapsed > 0.5) {
        points.removeFromParent();
        geom.dispose();
        mat.dispose();
        return;
      }
      const arr = geom.getAttribute('position') as THREE.BufferAttribute;
      for (let i = 0; i < count; i++) {
        lives[i] -= 0.016;
        if (lives[i] <= 0) continue;
        arr.array[i * 3] += velocities[i].x * 0.016;
        arr.array[i * 3 + 1] += velocities[i].y * 0.016;
        arr.array[i * 3 + 2] += velocities[i].z * 0.016;
      }
      arr.needsUpdate = true;
      mat.opacity = Math.max(0, 1 - elapsed / 0.5);
      requestAnimationFrame(update);
    };
    update();
  }
}
