import { Sprite, SpriteMaterial, Texture, TextureLoader, SRGBColorSpace, Vector3 } from "three";
import * as TWEEN from "@tweenjs/tween.js";
import { UpdateController, CameraC_internal } from "@24tools/playable_template";
import { ThreeC } from "./ThreeC";
import { TestSceneC } from "./TestSceneC"; // for groundY (ground level)
import { woodIconUrl } from "../resources/images/woodIcon";

// Tunables — tweak here
const PIECES_MIN       = 3;     // min pieces per drop
const PIECES_MAX       = 6;     // max pieces per drop
const SCATTER_MIN      = 0.6;   // near landing radius
const SCATTER_MAX      = 1.4;   // far landing radius (wider scatter)
const ANGLE_JITTER     = 0.3;   // fraction of the sector used for jitter (smaller = more even, fewer overlaps)
const PIECE_SIZE       = 0.6;   // size of the falling loot (bigger than UI → shrinks in flight)

const ARC_HEIGHT       = 1.2;   // height of the first (main) flight — the tallest hop
const FLIGHT_MS        = 520;   // duration of the first flight
const BOUNCES          = 2;     // how many bounces after landing
const BOUNCE_HEIGHT    = 0.4;   // each bounce = this fraction of the previous height
const BOUNCE_TIME      = 0.6;   // each bounce is shorter in time
const BOUNCE_FORWARD   = 0.5;   // each bounce covers this fraction of the previous horizontal step
const FLIGHT_STRETCH   = 0.35;  // vertical stretch in flight (scaled by hop height)
const LAND_SQUASH      = 0.6;   // squash on the final landing
const LAND_POP_MS      = 160;   // duration of the final "pop"

// Collect (#8): delay after landing before flying to the corner, UI icon size, etc.
const COLLECT_DELAY_MS = 100;   // almost immediately after the bounces
const UI_ICON_SIZE     = 28;    // wood UI icon size (px) — smaller than loot on the ground, but not tiny
const UI_RIGHT         = 16;    // offset from the right edge (px)
const UI_TOP           = 110;   // offset from the top (px) — lower, like in the REF
const SHRINK_MS        = 250;   // shrink to UI size before the flight
const FLY_MS           = 500;   // duration of the flight to the corner
const BLINK_MS         = 120;   // ramp-up duration of the white flash (fade-out is longer)

const _ndc  = new Vector3();
const _topV = new Vector3();

export class LootC {
  static pieces: Sprite[] = [];
  private static texture: Texture | null = null;
  private static uiIcon: HTMLImageElement | null = null;
  // Our own tween group. In tween.js v25 `new Tween(obj)` does NOT join the
  // default group automatically, so we keep and update our own (else tweens freeze).
  private static tweens = new TWEEN.Group();

  static init() {
    this.texture = new TextureLoader().load(woodIconUrl);
    this.texture.colorSpace = SRGBColorSpace; // correct color

    // Wood UI icon in the top-right corner (HTML overlay). Loot flies into it.
    const icon = document.createElement("img");
    icon.id = "wood-ui"; // stable id → UI/counter hooks onto it, LootC reads its position
    icon.src = woodIconUrl;
    icon.style.cssText =
      `position:fixed; top:${UI_TOP}px; right:${UI_RIGHT}px;` +
      `width:${UI_ICON_SIZE}px; height:${UI_ICON_SIZE}px;` +
      `z-index:1001; pointer-events:none; transition:transform 0.12s ease-out;`;
    document.body.appendChild(icon);
    this.uiIcon = icon;

    // ⚠️ Key: pump our group every frame, otherwise the tweens don't advance.
    UpdateController.Instance.onUpdate.addDelegate(() => this.tweens.update());
  }

  /** Spawn loot at a point. If count is omitted → random PIECES_MIN..PIECES_MAX. */
  static spawn(origin: Vector3, count?: number) {
    const n = count ?? (PIECES_MIN + Math.floor(Math.random() * (PIECES_MAX - PIECES_MIN + 1)));
    const slice = (Math.PI * 2) / n; // each piece gets its own sector of the circle

    for (let i = 0; i < n; i++) {
      const piece = this.createPiece();
      piece.position.copy(origin);

      // Even sector + a little jitter → pieces spread out and don't clump.
      const angle = i * slice + (Math.random() - 0.5) * slice * ANGLE_JITTER;
      const dist = SCATTER_MIN + Math.random() * (SCATTER_MAX - SCATTER_MIN);
      const landing = new Vector3(
        origin.x + Math.cos(angle) * dist,
        TestSceneC.groundY + PIECE_SIZE / 2,  // sprite center above ground → its bottom touches the ground
        origin.z + Math.sin(angle) * dist,
      );

      this.animatePiece(piece, origin.clone(), landing);
      this.pieces.push(piece);
    }
  }

  /** Flat piece: a Sprite (billboard — always faces the camera) with the wood texture. */
  private static createPiece(): Sprite {
    const mat = new SpriteMaterial({ map: this.texture, transparent: true });
    const piece = new Sprite(mat);
    piece.scale.set(PIECE_SIZE, PIECE_SIZE, 1);
    ThreeC.addToScene(piece);
    return piece;
  }

  /**
   * Scatter: a tall first arc (stretched along its motion), then a few decaying
   * bounces off the ground, and a final "pop" (squash → springs back to normal).
   */
  private static animatePiece(piece: Sprite, from: Vector3, to: Vector3) {
    const restY = to.y; // sprite center at rest (= groundY + PIECE_SIZE/2)

    // One "hop": parabola fx,fz→tx,tz peaking at peak; stretched by speed.
    const hop = (fx: number, fz: number, tx: number, tz: number, peak: number, ms: number) =>
      new TWEEN.Tween({ t: 0 }, this.tweens)
        .to({ t: 1 }, ms)
        .easing(TWEEN.Easing.Linear.None)
        .onUpdate(({ t }) => {
          piece.position.x = fx + (tx - fx) * t;
          piece.position.z = fz + (tz - fz) * t;
          piece.position.y = restY + peak * 4 * t * (1 - t);   // parabolic arc
          // |1-2t|: fast on the way up/down → stretch; at the peak → normal.
          // Scale the stretch by hop height (small bounces stretch less).
          const s = 1 + FLIGHT_STRETCH * (peak / ARC_HEIGHT) * Math.abs(1 - 2 * t);
          piece.scale.set(PIECE_SIZE / s, PIECE_SIZE * s, 1);
        });

    // Horizontal throw direction (target = the final resting spot).
    const dx = to.x - from.x, dz = to.z - from.z;
    const totalDist = Math.hypot(dx, dz) || 1e-4;
    const dirX = dx / totalDist, dirZ = dz / totalDist;

    // Split the horizontal distance between the flight and the bounces (geometric
    // decay) so the plank also moves forward on bounces, not just up; sum = totalDist.
    const hops = BOUNCES + 1;
    const series = (1 - Math.pow(BOUNCE_FORWARD, hops)) / (1 - BOUNCE_FORWARD);
    let step = totalDist / series;
    let cx = from.x, cz = from.z;
    let peak = ARC_HEIGHT, ms = FLIGHT_MS;
    let first: TWEEN.Tween<{ t: number }> | null = null;
    let prev: TWEEN.Tween<{ t: number }> | null = null;

    for (let k = 0; k < hops; k++) {
      const nx = cx + dirX * step, nz = cz + dirZ * step;
      const h = hop(cx, cz, nx, nz, peak, ms);
      if (!first) first = h; else prev!.chain(h);
      prev = h;
      cx = nx; cz = nz;
      step *= BOUNCE_FORWARD; peak *= BOUNCE_HEIGHT; ms *= BOUNCE_TIME;
    }

    // 3) final impact: sharp squash (bottom on the ground) → springs back to normal
    const groundY = restY - PIECE_SIZE / 2;
    const pop = new TWEEN.Tween({ k: 0 }, this.tweens)
      .to({ k: 1 }, LAND_POP_MS)
      .easing(TWEEN.Easing.Back.Out)
      .onUpdate(({ k }) => {
        const s = LAND_SQUASH + (1 - LAND_SQUASH) * k; // 0.6 → 1 (with a slight overshoot)
        piece.scale.set(PIECE_SIZE / s, PIECE_SIZE * s, 1);
        piece.position.y = groundY + (PIECE_SIZE * s) / 2; // bottom stays on the ground
      })
      .onComplete(() => {
        // After resting briefly → flies into the UI icon.
        setTimeout(() => this.collect(piece), COLLECT_DELAY_MS);
      });
    prev!.chain(pop);

    first!.start();
  }

  /**
   * Collect (#8): project the piece into screen pixels, swap the 3D sprite for an
   * HTML image of the same size, shrink it to the UI icon size and fly it to the
   * top-right corner — sizes match there, so the "arrival" is seamless.
   */
  private static collect(piece: Sprite) {
    if (!this.pieces.includes(piece)) return; // already collected/removed
    const cam = CameraC_internal.camera;
    const canvas = document.querySelector("canvas");
    if (!cam || !canvas) return;
    const rect = canvas.getBoundingClientRect();

    // sprite center and top → screen pixels (for on-screen position and size)
    const center = this.toScreen(piece.position, cam, rect);
    _topV.copy(piece.position); _topV.y += piece.scale.y / 2;
    const sizePx = Math.max(8, Math.abs(center.y - this.toScreen(_topV, cam, rect).y) * 2);

    // drop the 3D sprite, replace it with an HTML image at the same point/size
    this.remove(piece);

    const flier = document.createElement("img");
    flier.src = woodIconUrl;
    flier.style.cssText =
      `position:fixed; left:0; top:0; width:${sizePx}px; height:${sizePx}px;` +
      `z-index:1000; pointer-events:none; transform:translate(-50%,-50%); will-change:left,top,width,height;`;
    document.body.appendChild(flier);

    const st = { x: center.x, y: center.y, size: sizePx };
    const apply = () => {
      flier.style.left = `${st.x}px`;
      flier.style.top = `${st.y}px`;
      flier.style.width = `${st.size}px`;
      flier.style.height = `${st.size}px`;
    };
    apply();

    const target = this.uiIconCenter();
    // 1) shrink to UI size in place → 2) fly to the corner
    const shrink = new TWEEN.Tween(st, this.tweens)
      .to({ size: UI_ICON_SIZE }, SHRINK_MS)
      .easing(TWEEN.Easing.Quadratic.Out)
      .onUpdate(apply);
    const fly = new TWEEN.Tween(st, this.tweens)
      .to({ x: target.x, y: target.y }, FLY_MS)
      .easing(TWEEN.Easing.Quadratic.In)
      .onUpdate(apply)
      .onComplete(() => { flier.remove(); this.pulseUiIcon(); });
    shrink.chain(fly);

    // Smooth white blink before the flight: a white copy of the plank fades in
    // and out on top ("collected" feedback), then the shrink + flight.
    const flash = document.createElement("img");
    flash.src = woodIconUrl;
    flash.style.cssText = flier.style.cssText; // same position/size
    flash.style.filter = "brightness(0) invert(1)"; // solid white silhouette
    flash.style.opacity = "0";
    flash.style.zIndex = "1002";
    document.body.appendChild(flash);

    const fl = { o: 0 };
    const setO = () => { flash.style.opacity = `${fl.o}`; };
    const flashIn = new TWEEN.Tween(fl, this.tweens)
      .to({ o: 1 }, BLINK_MS)
      .easing(TWEEN.Easing.Quadratic.Out)
      .onUpdate(setO);
    const flashOut = new TWEEN.Tween(fl, this.tweens)
      .to({ o: 0 }, BLINK_MS * 1.6)
      .easing(TWEEN.Easing.Quadratic.In)
      .onUpdate(setO)
      .onComplete(() => { flash.remove(); shrink.start(); });
    flashIn.chain(flashOut);
    flashIn.start();
  }

  /** World point → screen pixels (accounting for the canvas position on the page). */
  private static toScreen(world: Vector3, cam: any, rect: DOMRect) {
    _ndc.copy(world).project(cam);
    return {
      x: rect.left + (_ndc.x * 0.5 + 0.5) * rect.width,
      y: rect.top + (-_ndc.y * 0.5 + 0.5) * rect.height,
    };
  }

  private static uiIconCenter() {
    const r = this.uiIcon?.getBoundingClientRect();
    return r ? { x: r.left + r.width / 2, y: r.top + r.height / 2 } : { x: 0, y: 0 };
  }

  /** A small "pulse" of the UI icon when a piece arrives. */
  private static pulseUiIcon() {
    const el = this.uiIcon;
    if (!el) return;
    el.style.transform = "scale(1.25)";
    setTimeout(() => { if (this.uiIcon) this.uiIcon.style.transform = "scale(1)"; }, 120);
  }

  /** Remove a piece from the scene (used by #8 — after collecting). */
  static remove(piece: Sprite) {
    ThreeC.removeFromScene(piece);
    const i = this.pieces.indexOf(piece);
    if (i >= 0) this.pieces.splice(i, 1);
  }
}
