import {
  AdditiveBlending,
  Color,
  DoubleSide,
  Mesh,
  MeshBasicMaterial,
  Shape,
  ShapeGeometry,
  Vector3,
} from "three";
import { CameraC_internal, TweenC } from "@hitplay/playable_template";
import { Easing, Tween } from "@tweenjs/tween.js";
import { ThreeC } from "./ThreeC";

const SPARKLE_COUNT = 5;
const SPARKLE_SCATTER_RADIUS = 0.28; // world units around the burst point
const SPARKLE_OUTER_RADIUS = 0.07;
const SPARKLE_INNER_RADIUS = 0.02;
const SPARKLE_COLOR = new Color(0xfff6d8); // warm white — a "reward" twinkle, not a colored magic effect

const POP_IN_MS = 80;
const HOLD_MS = 90;
const FADE_OUT_MS = 220;

// Sized for the worst realistic case, not the common one: a crate can drop
// several resources at once (see ResourceC), each bouncing independently
// but on identical timing constants, so their second-bounce bursts land
// within the same frame or two of each other — up to
// (max resources per hit) * SPARKLE_COUNT stars alive simultaneously.
const POOL_SIZE = 30;

// A small 4-point twinkle star (outer/inner radius alternating around the
// ring) — the classic "sparkle" shape, not a designer sprite/atlas asset
// (none exists yet for this, see PropVfxC for the kind of VFX that does
// have one). Built once and shared read-only across every spawned star.
function buildStarShape(): Shape {
  const points = 4;
  const shape = new Shape();
  for (let i = 0; i < points * 2; i++) {
    const radius = i % 2 === 0 ? SPARKLE_OUTER_RADIUS : SPARKLE_INNER_RADIUS;
    const angle = (i / (points * 2)) * Math.PI * 2;
    const x = Math.cos(angle) * radius;
    const y = Math.sin(angle) * radius;
    if (i === 0) shape.moveTo(x, y);
    else shape.lineTo(x, y);
  }
  shape.closePath();
  return shape;
}

const starGeometry = new ShapeGeometry(buildStarShape());

interface StarSlot {
  mesh: Mesh;
  material: MeshBasicMaterial;
  inUse: boolean;
}

// A short "twinkle" burst of a handful of star-shaped quads around a point
// — used for the second bounce of a landed resource (see ResourceC), but
// not tied to that specifically, so it's its own small reusable effect.
//
// Pooled (see chat: "давай зробимо пул") — every burst used to `new Mesh` +
// `new MeshBasicMaterial` per star and throw them away ~390ms later via
// removeFromScene(). Multiple resources bouncing in from the same crate
// hit fire their bursts within a frame or two of each other, so that
// pattern meant a dozen-plus create+GC cycles clustered into under half a
// second — exactly the "small VFX objects churning" case object pooling
// exists for (see Day 7 brief: "recommended to use pools for VFX"). Now a
// fixed set of star meshes is built once at init() and reused: spawnBurst
// just grabs whichever slots aren't currently animating.
export class SparkleFxC {
  private static pool: StarSlot[] = [];
  private static initialized = false;

  static init() {
    if (this.initialized) return;
    this.initialized = true;

    for (let i = 0; i < POOL_SIZE; i++) {
      const material = new MeshBasicMaterial({
        color: SPARKLE_COLOR,
        transparent: true,
        opacity: 0,
        blending: AdditiveBlending,
        depthWrite: false,
        side: DoubleSide,
        toneMapped: false,
      });

      const mesh = new Mesh(starGeometry, material);
      mesh.visible = false;
      mesh.scale.setScalar(0);
      mesh.renderOrder = 10; // draws after the resource/flash it bursts around
      ThreeC.addToScene(mesh);

      this.pool.push({ mesh, material, inUse: false });
    }
  }

  static spawnBurst(position: Vector3) {
    const cameraPosition = CameraC_internal.getCamera().getWorldPosition(new Vector3());

    let spawned = 0;
    for (const slot of this.pool) {
      if (slot.inUse) continue;

      this.activate(slot, position, cameraPosition);
      spawned++;
      if (spawned >= SPARKLE_COUNT) return;
    }

    // Not a hard failure — just fewer stars than usual this burst — but
    // worth knowing about if it ever actually happens, rather than a
    // silently smaller effect (see Day 7 workflow guidance on silent caps).
    if (spawned < SPARKLE_COUNT) {
      console.debug(`[SparkleFxC] pool exhausted — spawned ${spawned}/${SPARKLE_COUNT} stars`);
    }
  }

  private static activate(slot: StarSlot, position: Vector3, cameraPosition: Vector3) {
    slot.inUse = true;
    const { mesh, material } = slot;

    const angle = Math.random() * Math.PI * 2;
    const radius = Math.random() * SPARKLE_SCATTER_RADIUS;
    const offset = new Vector3(
      Math.cos(angle) * radius,
      Math.random() * SPARKLE_SCATTER_RADIUS * 0.6,
      Math.sin(angle) * radius
    );

    mesh.position.copy(position).add(offset);
    // Faces the actual camera position rather than a shared fixed
    // quaternion — cheap since it only runs once per activation, and stays
    // correct even though each star sits at a slightly different offset
    // around the burst point.
    mesh.lookAt(cameraPosition);
    mesh.scale.setScalar(0);
    mesh.visible = true;
    material.opacity = 0;

    const popIn = new Tween({ t: 0 })
      .to({ t: 1 }, POP_IN_MS)
      .easing(Easing.Back.Out)
      .onUpdate(({ t }) => {
        mesh.scale.setScalar(Math.max(t, 0));
        material.opacity = Math.min(t, 1);
      });

    const fadeOut = new Tween({ t: 1 })
      .to({ t: 0 }, FADE_OUT_MS)
      .delay(HOLD_MS)
      .easing(Easing.Quadratic.In)
      .onUpdate(({ t }) => {
        mesh.scale.setScalar(t);
        material.opacity = t;
      })
      .onComplete(() => {
        mesh.visible = false;
        slot.inUse = false;
      });

    // Both links added to TweenC individually — .chain() only tells
    // popIn to call fadeOut.start() on completion, it doesn't register
    // fadeOut for update ticks on its own (the same gotcha BreakablePropC
    // hit with its hit-flash chain — see CLAUDE.md).
    popIn.chain(fadeOut);
    TweenC.add(popIn);
    TweenC.add(fadeOut);
    popIn.start();
  }
}
