// Ground-targeting reticle for area spells (Blizzard): a frost circle of the real radius that follows the mouse on the
// ground. Everything is built once at load and added hidden to the scene (warmShaders compiles hidden parts too), so
// showing it mid-fight never compiles a shader or allocates.
import * as THREE from "three";
import { mergeGeometries } from "three/examples/jsm/utils/BufferGeometryUtils.js";

const OK = new THREE.Color("#7fe8ff");
const BAD = new THREE.Color("#ff4a4a");

function basic(opacity: number) {
  return new THREE.MeshBasicMaterial({ color: OK, transparent: true, opacity, depthWrite: false, toneMapped: false, side: THREE.DoubleSide });
}

/** `n` short arcs around a unit circle (a dashed ring). */
function dashedRing(inner: number, outer: number, n: number, fill: number) {
  const parts: THREE.BufferGeometry[] = [];
  const step = (Math.PI * 2) / n;
  for (let i = 0; i < n; i++) parts.push(new THREE.RingGeometry(inner, outer, 4, 1, i * step, step * fill));
  const g = mergeGeometries(parts) ?? parts[0];
  for (const p of parts) if (p !== g) p.dispose();
  return g;
}

export class Reticle {
  readonly group = new THREE.Group();
  private mats: THREE.MeshBasicMaterial[] = [];
  private spin: THREE.Mesh;
  private spin2: THREE.Mesh;
  private pulse: THREE.Mesh;
  private on = false;
  private bad = false;

  constructor(scene: THREE.Scene) {
    const g = this.group;
    const add = (geo: THREE.BufferGeometry, opacity: number, y: number) => {
      const m = basic(opacity);
      this.mats.push(m);
      const mesh = new THREE.Mesh(geo, m);
      mesh.rotation.x = -Math.PI / 2;
      mesh.position.y = y;
      mesh.renderOrder = 6;
      mesh.frustumCulled = false;
      g.add(mesh);
      return mesh;
    };
    add(new THREE.CircleGeometry(1, 56), 0.14, 0.12); // soft fill
    add(new THREE.RingGeometry(0.965, 1, 72, 1), 0.95, 0.14); // the exact edge
    add(new THREE.RingGeometry(0.9, 0.915, 72, 1), 0.35, 0.14);
    this.spin = add(dashedRing(0.6, 0.625, 18, 0.55), 0.55, 0.14);
    this.spin2 = add(dashedRing(0.3, 0.315, 10, 0.5), 0.45, 0.14);
    this.pulse = add(new THREE.RingGeometry(0.96, 1, 72, 1), 0.5, 0.14);
    add(new THREE.CircleGeometry(0.035, 12), 0.9, 0.15); // centre dot
    g.visible = false;
    scene.add(g);
  }

  get visible() {
    return this.on;
  }

  show() {
    this.on = true;
    this.group.visible = true;
  }
  hide() {
    this.on = false;
    this.group.visible = false;
  }

  /** Place the circle: world x, z, real radius in metres, red when the spot is not allowed. */
  set(x: number, z: number, radius: number, valid: boolean, t: number) {
    const g = this.group;
    g.position.set(x, 0, z);
    g.scale.set(radius, 1, radius);
    this.spin.rotation.z = t * 0.5;
    this.spin2.rotation.z = -t * 0.9;
    const k = (t * 1.1) % 1;
    this.pulse.scale.set(k, k, 1);
    (this.pulse.material as THREE.MeshBasicMaterial).opacity = 0.5 * (1 - k);
    if (this.bad === valid) {
      this.bad = !valid;
      const c = this.bad ? BAD : OK;
      for (const m of this.mats) m.color.copy(c);
    }
  }

  dispose(scene: THREE.Scene) {
    scene.remove(this.group);
    this.group.traverse((o) => (o as THREE.Mesh).geometry?.dispose());
    for (const m of this.mats) m.dispose();
  }
}

/** WoW-style selection ring under the targeted enemy: a bright double ring with turning notches, gold, never hidden by the sand. */
export class TargetRing {
  readonly group = new THREE.Group();
  private notch: THREE.Mesh;
  private halo: THREE.Mesh;

  constructor(scene: THREE.Scene) {
    const mk = (geo: THREE.BufferGeometry, color: string, opacity: number, y: number) => {
      const m = new THREE.Mesh(geo, new THREE.MeshBasicMaterial({ color, transparent: true, opacity, depthWrite: false, toneMapped: false, side: THREE.DoubleSide }));
      m.rotation.x = -Math.PI / 2;
      m.position.y = y;
      m.renderOrder = 6;
      m.frustumCulled = false;
      this.group.add(m);
      return m;
    };
    this.halo = mk(new THREE.CircleGeometry(1.25, 40), "#ff5a3c", 0.22, 0.11);
    mk(new THREE.RingGeometry(1.12, 1.24, 56, 1), "#ffd95a", 0.98, 0.13);
    mk(new THREE.RingGeometry(0.9, 0.94, 56, 1), "#ffb347", 0.6, 0.13);
    this.notch = mk(dashedRing(1.3, 1.5, 4, 0.22), "#ffffff", 0.95, 0.13);
    this.group.visible = false;
    scene.add(this.group);
  }

  set(x: number | null, z: number, t: number) {
    if (x === null) {
      this.group.visible = false;
      return;
    }
    this.group.visible = true;
    this.group.position.set(x, 0, z);
    this.notch.rotation.z = t * 1.4;
    const k = 1 + Math.sin(t * 4) * 0.04;
    this.halo.scale.set(k, k, 1);
  }

  dispose(scene: THREE.Scene) {
    scene.remove(this.group);
    this.group.traverse((o) => {
      const m = o as THREE.Mesh;
      m.geometry?.dispose();
      (m.material as THREE.Material | undefined)?.dispose();
    });
  }
}
