import * as THREE from 'three';
import { TAU } from './util';

export type PickupKind = 'heart' | 'gem';

interface Pickup {
  kind: PickupKind;
  mesh: THREE.Mesh;
  x: number;
  y: number;
  z: number;
  vx: number;
  vy: number;
  vz: number;
  age: number;
  magnet: boolean;
  speed: number;
}

const LIFETIME = 14;

/** Hearts and gems that pop out of things and magnetize to the player. */
export class Pickups {
  private list: Pickup[] = [];
  private heartGeo: THREE.BufferGeometry;
  private gemGeo: THREE.BufferGeometry;
  private heartMat: THREE.MeshLambertMaterial;
  private gemMat: THREE.MeshStandardMaterial;

  constructor(
    private scene: THREE.Scene,
    private groundY: (x: number, z: number) => number,
    private onCollect: (kind: PickupKind, x: number, y: number, z: number) => void,
  ) {
    const shape = new THREE.Shape();
    shape.moveTo(0, 0.32);
    shape.bezierCurveTo(0, 0.55, -0.5, 0.55, -0.5, 0.22);
    shape.bezierCurveTo(-0.5, -0.08, -0.16, -0.28, 0, -0.48);
    shape.bezierCurveTo(0.16, -0.28, 0.5, -0.08, 0.5, 0.22);
    shape.bezierCurveTo(0.5, 0.55, 0, 0.55, 0, 0.32);
    this.heartGeo = new THREE.ExtrudeGeometry(shape, {
      depth: 0.16,
      bevelEnabled: true,
      bevelThickness: 0.05,
      bevelSize: 0.05,
      bevelSegments: 1,
      steps: 1,
    });
    this.heartGeo.center();
    this.heartGeo.scale(0.42, 0.42, 0.42);
    this.heartMat = new THREE.MeshLambertMaterial({ color: '#e8404f', emissive: '#5e0a14' });

    this.gemGeo = new THREE.OctahedronGeometry(0.17);
    this.gemGeo.scale(1, 1.35, 1);
    this.gemMat = new THREE.MeshStandardMaterial({
      color: '#3ed27a',
      roughness: 0.2,
      metalness: 0.1,
      emissive: '#0a5c2e',
      emissiveIntensity: 0.5,
      flatShading: true,
    });
  }

  get count(): number {
    return this.list.length;
  }

  spawn(kind: PickupKind, x: number, y: number, z: number): void {
    const mesh = new THREE.Mesh(
      kind === 'heart' ? this.heartGeo : this.gemGeo,
      kind === 'heart' ? this.heartMat : this.gemMat,
    );
    mesh.position.set(x, y + 0.3, z);
    this.scene.add(mesh);
    const a = Math.random() * TAU;
    this.list.push({
      kind, mesh,
      x, y: y + 0.3, z,
      vx: Math.sin(a) * 1.1,
      vy: 3.6,
      vz: Math.cos(a) * 1.1,
      age: 0,
      magnet: false,
      speed: 0,
    });
  }

  update(dt: number, heroX: number, heroZ: number): void {
    for (let i = this.list.length - 1; i >= 0; i--) {
      const p = this.list[i]!;
      p.age += dt;
      if (p.age > LIFETIME) {
        this.scene.remove(p.mesh);
        this.list.splice(i, 1);
        continue;
      }
      const dx = heroX - p.x;
      const dz = heroZ - p.z;
      const d = Math.hypot(dx, dz);
      if (d < 2.1) p.magnet = true;

      const floor = this.groundY(p.x, p.z) + 0.32;
      if (p.magnet) {
        p.speed = Math.min(11, p.speed + 36 * dt);
        if (d > 1e-4) {
          p.x += (dx / d) * p.speed * dt;
          p.z += (dz / d) * p.speed * dt;
        }
        p.y += (floor + 0.15 - p.y) * Math.min(1, 10 * dt);
        if (d < 0.5) {
          this.onCollect(p.kind, p.x, p.y, p.z);
          this.scene.remove(p.mesh);
          this.list.splice(i, 1);
          continue;
        }
      } else {
        p.vy -= 11 * dt;
        p.x += p.vx * dt;
        p.y += p.vy * dt;
        p.z += p.vz * dt;
        if (p.y < floor && p.vy < 0) {
          p.y = floor;
          p.vy = -p.vy * 0.35;
          p.vx *= 0.5;
          p.vz *= 0.5;
        }
      }

      const bob = p.magnet ? 0 : Math.sin(p.age * 3.2 + p.mesh.id) * 0.05;
      p.mesh.position.set(p.x, p.y + bob, p.z);
      p.mesh.rotation.y += dt * 2.6;
      // blink before despawn
      p.mesh.visible = p.age < LIFETIME - 2.5 || Math.floor(p.age * 9) % 2 === 0;
    }
  }
}
