import * as THREE from 'three';

const DURATION = 0.3;
const SPARK_COUNT = 20;
const SLASH_HALF_ANGLE = 1.22;

// Enemies are ~0.5-0.7 wide; allow their body to overlap the arc edge.
const HIT_MARGIN = 0.6;

function arcShape(r0: number, r1: number): THREE.Shape {
  const shape = new THREE.Shape();
  shape.absarc(0, 0, r1, -SLASH_HALF_ANGLE, SLASH_HALF_ANGLE, false);
  shape.absarc(0, 0, r0, SLASH_HALF_ANGLE, -SLASH_HALF_ANGLE, true);
  shape.closePath();
  return shape;
}

// ShapeGeometry builds the arc around +X in the XY plane; rotate it so it
// lies flat on the ground and is centered on +Z (the player's forward).
function arcGeometry(r0: number, r1: number): THREE.ShapeGeometry {
  const geom = new THREE.ShapeGeometry(arcShape(r0, r1), 32);
  geom.rotateX(-Math.PI / 2);
  geom.rotateY(-Math.PI / 2);
  return geom;
}

export function isInSlashArc(
  playerPos: THREE.Vector3,
  forward: THREE.Vector3,
  targetPos: THREE.Vector3,
  radius: number
): boolean {
  const fwd = new THREE.Vector3(forward.x, 0, forward.z).normalize();
  const toTarget = new THREE.Vector3().subVectors(targetPos, playerPos);
  toTarget.y = 0;
  const dist = toTarget.length();
  if (dist > radius + HIT_MARGIN) return false;
  if (dist < 0.01) return true;
  return fwd.dot(toTarget.normalize()) > Math.cos(SLASH_HALF_ANGLE);
}

export class SlashEffect {
  private group: THREE.Group;
  private outerMat: THREE.MeshBasicMaterial;
  private innerMat: THREE.MeshBasicMaterial;
  private sparkMat: THREE.PointsMaterial;
  private sparks: THREE.Points;
  private sparkVelocities: Float32Array;
  private sparkLives: Float32Array;
  private sparkMaxLife = 0.25;
  private sparkPositions: Float32Array;
  private t = 0;
  private done = false;

  constructor(
    position: THREE.Vector3,
    forward: THREE.Vector3,
    scene: THREE.Scene,
    radius: number
  ) {
    const yaw = Math.atan2(forward.x, forward.z);
    this.group = new THREE.Group();
    this.group.position.copy(position);
    this.group.position.y = 0.12;
    this.group.rotation.y = yaw;
    scene.add(this.group);

    // Outer soft arc (marks the full hit area)
    this.outerMat = new THREE.MeshBasicMaterial({
      color: 0xffffff,
      transparent: true,
      opacity: 0.5,
      blending: THREE.AdditiveBlending,
      depthWrite: false,
      side: THREE.DoubleSide,
    });
    const outer = new THREE.Mesh(
      arcGeometry(0.5, radius),
      this.outerMat
    );
    this.group.add(outer);

    // Inner hot arc
    this.innerMat = new THREE.MeshBasicMaterial({
      color: 0xffffcc,
      transparent: true,
      opacity: 0.85,
      blending: THREE.AdditiveBlending,
      depthWrite: false,
      side: THREE.DoubleSide,
    });
    const inner = new THREE.Mesh(
      arcGeometry(0.7, radius * 0.8),
      this.innerMat
    );
    this.group.add(inner);

    // Sparks along the arc (centered on +Z, the forward direction)
    this.sparkPositions = new Float32Array(SPARK_COUNT * 3);
    this.sparkVelocities = new Float32Array(SPARK_COUNT * 3);
    this.sparkLives = new Float32Array(SPARK_COUNT);
    for (let i = 0; i < SPARK_COUNT; i++) {
      const angle = -SLASH_HALF_ANGLE
        + (i / (SPARK_COUNT - 1)) * SLASH_HALF_ANGLE * 2;
      const sparkRadius = radius * 0.55 + Math.random() * radius * 0.45;
      this.sparkPositions[i * 3] = Math.sin(angle) * sparkRadius;
      this.sparkPositions[i * 3 + 1] = 0.2 + Math.random() * 0.3;
      this.sparkPositions[i * 3 + 2] = Math.cos(angle) * sparkRadius;
      this.sparkVelocities[i * 3] = Math.sin(angle) * 2.5;
      this.sparkVelocities[i * 3 + 1] = 2 + Math.random() * 3;
      this.sparkVelocities[i * 3 + 2] = Math.cos(angle) * 2.5;
      this.sparkLives[i] = this.sparkMaxLife * (0.5 + Math.random());
    }

    const sparkGeom = new THREE.BufferGeometry();
    sparkGeom.setAttribute(
      'position',
      new THREE.BufferAttribute(this.sparkPositions, 3)
    );
    this.sparkMat = new THREE.PointsMaterial({
      color: 0xffffff,
      size: 0.12,
      blending: THREE.AdditiveBlending,
      depthWrite: false,
      transparent: true,
    });
    this.sparks = new THREE.Points(sparkGeom, this.sparkMat);
    this.group.add(this.sparks);
  }

  update(dt: number): boolean {
    if (this.done) return false;
    this.t += dt;
    const progress = Math.min(this.t / DURATION, 1);

    const scale = 1 + progress * 0.3;
    this.group.scale.set(scale, 1, scale);
    this.outerMat.opacity = 0.5 * Math.pow(1 - progress, 1.5);
    this.innerMat.opacity = 0.85 * Math.pow(1 - progress, 1.5);
    this.sparkMat.opacity = Math.pow(1 - progress, 1.5);

    for (let i = 0; i < SPARK_COUNT; i++) {
      if (this.sparkLives[i] <= 0) continue;
      this.sparkLives[i] -= dt;
      this.sparkPositions[i * 3] += this.sparkVelocities[i * 3] * dt;
      this.sparkPositions[i * 3 + 1] += this.sparkVelocities[i * 3 + 1] * dt;
      this.sparkPositions[i * 3 + 2] += this.sparkVelocities[i * 3 + 2] * dt;
      this.sparkVelocities[i * 3 + 1] -= 6 * dt;
    }
    const attr = this.sparks.geometry
      .getAttribute('position') as THREE.BufferAttribute;
    attr.needsUpdate = true;

    if (progress >= 1) {
      this.dispose();
      return false;
    }
    return true;
  }

  dispose() {
    if (this.done) return;
    this.done = true;
    this.group.removeFromParent();
    for (const child of [...this.group.children]) {
      if (child instanceof THREE.Mesh || child instanceof THREE.Points) {
        child.geometry.dispose();
        (child.material as THREE.Material).dispose();
      }
    }
  }
}
