import { Sprite, SpriteMaterial, Texture, TextureLoader, SRGBColorSpace, Vector3 } from "three";
import * as TWEEN from "@tweenjs/tween.js";
import { CameraC_internal } from "@hitplay/playable_template";
import { ThreeC } from "./ThreeC";
import { TestSceneC } from "./TestSceneC"; // for groundY (ground level)
import { GameEvents } from "../core/GameEvents";
import { GameLoop } from "../core/GameLoop";
import { images } from "../resources/resources";
import { worldToScreen } from "../utils/screen";

// 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.22;  // vertical stretch in flight (softer = smoother, less rubbery)
const LAND_SQUASH      = 0.72;  // squash on the final landing (gentler)
const LAND_POP_MS      = 180;   // duration of the final "pop"

// Collect (#8): when/where the wood flies to the UI.
const COLLECT_LEAD_MS    = 170;  // start the collect this long BEFORE the bounces finish,
                                 // so the shrink+flight flow out of the last bounce
const COLLECT_STAGGER_MS = 70;   // extra per-piece delay so they stream in, not all at once
const UI_ICON_SIZE       = 28;   // wood arrival size (px) — about the wood plank on the panel
const UI_WOOD_X_FRAC     = 0.82; // where the wood art sits across the panel (right-side plank)
const FLY_MS             = 950;  // flight duration — slower, calmer travel to the UI (size tracks it)
const FLY_ARC_PX         = 90;   // how high the flight bows upward (curved path, not a straight line)
const BLINK_MS           = 120;  // ramp-up of the white glint

const _spriteTop = new Vector3(); // scratch: a sprite's top point, for measuring its screen size

export class LootC {
  static pieces: Sprite[] = [];
  static balance = 0;                                  // collected wood the player can spend
  private static texture: Texture | null = null;
  private static uiIcon: HTMLImageElement | null = null;
  private static countEl: HTMLElement | null = null;   // the "×N" label next to the icon
  // Our tween group, pumped each frame (tween.js v25 needs an explicit group).
  private static tweens = new TWEEN.Group();

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

    // The wood icon/count live in the HUD (built by HudC). We just reference
    // them: read the icon's screen position as the fly target, write the count.
    this.uiIcon = document.getElementById("wood-ui") as HTMLImageElement | null;
    this.countEl = document.getElementById("wood-count");
    this.renderCount();

    // Listen for loot spawns (from crate breaks or state changes)
    GameEvents.onLootSpawn.addDelegate(({ position, count }) => this.spawn(position, count));

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

  /** Current spendable wood. */
  static getBalance(): number {
    return this.balance;
  }

  /** Spend up to `amount` wood; returns how much was actually taken (clamped to balance). */
  static spend(amount: number): number {
    const taken = Math.min(amount, this.balance);
    this.balance -= taken;
    this.renderCount();
    if (taken > 0) {
      GameEvents.onWoodSpent.Invoke({ amount: taken, balance: this.balance });
    }
    return taken;
  }

  /** Screen-pixel center of the UI wood icon (used as a fly source/target). */
  static uiIconScreenCenter(): { x: number; y: number } {
    return this.uiIconCenter();
  }

  private static renderCount() {
    if (this.countEl) this.countEl.textContent = `${this.balance}`;
  }

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

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

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

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

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

  /** A piece flies in an arc, bounces a couple times, then pops on landing. */
  private static animatePiece(piece: Sprite, from: Vector3, to: Vector3, index = 0) {
    const restY = to.y; // sprite center at rest

    // One hop: a parabolic arc from (fromX,fromZ) to (toX,toZ), stretched while
    // moving fast (the "squash & stretch" that sells the speed).
    const hop = (fromX: number, fromZ: number, toX: number, toZ: number, peakHeight: number, durationMs: number) =>
      new TWEEN.Tween({ t: 0 }, this.tweens)
        .to({ t: 1 }, durationMs)
        .easing(TWEEN.Easing.Linear.None)
        .onUpdate(({ t }) => {
          piece.position.x = fromX + (toX - fromX) * t;
          piece.position.z = fromZ + (toZ - fromZ) * t;
          piece.position.y = restY + peakHeight * 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 stretch = 1 + FLIGHT_STRETCH * (peakHeight / ARC_HEIGHT) * Math.abs(1 - 2 * t);
          piece.scale.set(PIECE_SIZE / stretch, PIECE_SIZE * stretch, 1);
        });

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

    // Share the horizontal distance across the flight + bounces (so the piece
    // also moves forward on each bounce, not just up). Each hop covers
    // BOUNCE_FORWARD× the previous one, so the steps form a geometric series
    // whose sum we divide the total distance by to get the first step.
    const hopCount = BOUNCES + 1;
    const forwardSum = (1 - Math.pow(BOUNCE_FORWARD, hopCount)) / (1 - BOUNCE_FORWARD);
    let stepDist = totalDist / forwardSum;
    let curX = from.x, curZ = from.z;
    let peakHeight = ARC_HEIGHT, durationMs = FLIGHT_MS;
    let firstHop: TWEEN.Tween<{ t: number }> | null = null;
    let prevHop: TWEEN.Tween<{ t: number }> | null = null;
    const allTweens: TWEEN.Tween<any>[] = []; // every hop/pop tween (so we can stop them early)
    let bouncesMs = 0;                        // total duration of all the hop arcs

    for (let i = 0; i < hopCount; i++) {
      const nextX = curX + dirX * stepDist, nextZ = curZ + dirZ * stepDist;
      const hopTween = hop(curX, curZ, nextX, nextZ, peakHeight, durationMs);
      allTweens.push(hopTween);
      if (!firstHop) firstHop = hopTween; else prevHop!.chain(hopTween);
      prevHop = hopTween;
      bouncesMs += durationMs;
      curX = nextX; curZ = nextZ;
      stepDist *= BOUNCE_FORWARD; peakHeight *= BOUNCE_HEIGHT; durationMs *= BOUNCE_TIME;
    }

    // Landing pop: squash on the ground, then spring back. Only seen if the piece
    // somehow isn't collected first (the collect normally lifts off before this).
    const groundY = restY - PIECE_SIZE / 2;
    const pop = new TWEEN.Tween({ t: 0 }, this.tweens)
      .to({ t: 1 }, LAND_POP_MS)
      .easing(TWEEN.Easing.Back.Out)
      .onUpdate(({ t }) => {
        const squash = LAND_SQUASH + (1 - LAND_SQUASH) * t; // 0.72 → 1 (with a slight overshoot)
        piece.scale.set(PIECE_SIZE / squash, PIECE_SIZE * squash, 1);
        piece.position.y = groundY + (PIECE_SIZE * squash) / 2; // bottom stays on the ground
      });
    allTweens.push(pop);
    prevHop!.chain(pop);

    firstHop!.start();

    // Lift off into the UI a bit BEFORE the bounces finish, so the shrink + flight
    // flow straight out of the last bounce (no "settle, pause, then fly"). Stop the
    // remaining bounce/pop on this piece and hand straight over to the collect.
    const liftOffDelayMs = Math.max(FLIGHT_MS * 0.7, bouncesMs - COLLECT_LEAD_MS) + index * COLLECT_STAGGER_MS;
    setTimeout(() => {
      allTweens.forEach(tween => tween.stop());
      this.collect(piece);
    }, liftOffDelayMs);
  }

  /**
   * Collect: swap the 3D sprite for an HTML image at the same screen spot, then
   * shrink it and fly it into the top-right UI counter.
   */
  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 + size in screen pixels.
    const center = worldToScreen(piece.position, cam, rect);
    _spriteTop.copy(piece.position); _spriteTop.y += piece.scale.y / 2;
    const sizePx = Math.max(8, Math.abs(center.y - worldToScreen(_spriteTop, cam, rect).y) * 2);

    // Drop the 3D sprite; the HTML image takes over from the same spot.
    this.remove(piece);

    const flier = document.createElement("img");
    flier.src = images.woodIconUrl;
    flier.style.cssText =
      `position:fixed; left:0; top:0; width:${sizePx}px; height:${sizePx}px;` +
      // above #hud (z-index 9999) so the wood clearly flies on top of, and into, the icon
      `z-index:10000; pointer-events:none; transform:translate(-50%,-50%); will-change:left,top,width,height;`;
    document.body.appendChild(flier);

    // White "glint" copy that rides on top of the flier and fades out as it moves.
    const flash = document.createElement("img");
    flash.src = images.woodIconUrl;
    flash.style.cssText = flier.style.cssText;
    flash.style.filter = "brightness(0) invert(1)"; // solid white silhouette
    flash.style.zIndex = "10001";
    document.body.appendChild(flash);

    const target = this.uiIconCenter();

    // Curved flight path (quadratic Bézier): start → a lifted control point → UI.
    // The upward bow makes the wood swoop in an arc instead of a flat diagonal,
    // which reads much smoother.
    const startX = center.x, startY = center.y;
    const endX = target.x, endY = target.y;
    const ctrlX = (startX + endX) / 2;
    const ctrlY = Math.min(startY, endY) - FLY_ARC_PX;

    // Shared animation state: progress along the path (0→1), current size, and
    // the white glint's opacity. All three tweens below mutate this one object.
    const anim = { progress: 0, size: sizePx, glow: 0 };
    const place = (el: HTMLElement) => {
      const inv = 1 - anim.progress; // (1−t) term of the Bézier
      const x = inv * inv * startX + 2 * inv * anim.progress * ctrlX + anim.progress * anim.progress * endX;
      const y = inv * inv * startY + 2 * inv * anim.progress * ctrlY + anim.progress * anim.progress * endY;
      el.style.left = `${x}px`;
      el.style.top = `${y}px`;
      el.style.width = `${anim.size}px`;
      el.style.height = `${anim.size}px`;
    };
    const apply = () => { place(flier); place(flash); flash.style.opacity = `${anim.glow}`; };
    apply();

    // Flight along the curve — owns the cleanup. Ease in AND out so it starts and
    // arrives gently.
    const fly = new TWEEN.Tween(anim, this.tweens)
      .to({ progress: 1 }, FLY_MS)
      .easing(TWEEN.Easing.Quadratic.InOut)
      .onUpdate(apply)
      .onComplete(() => {
        flier.remove();
        flash.remove();
        this.balance++;
        this.renderCount();
        this.pulseUiIcon();
        GameEvents.onWoodCollected.Invoke({ amount: 1, balance: this.balance });
      });
    // Shrink — SAME duration as the flight, so the two start AND finish together
    // (no "shrink first"); Sinusoidal makes the size change extra smooth.
    const shrink = new TWEEN.Tween(anim, this.tweens)
      .to({ size: UI_ICON_SIZE }, FLY_MS)
      .easing(TWEEN.Easing.Sinusoidal.InOut)
      .onUpdate(apply);
    // Blink — a quick glint that overlaps the start of the motion.
    const flashIn = new TWEEN.Tween(anim, this.tweens)
      .to({ glow: 1 }, BLINK_MS)
      .easing(TWEEN.Easing.Quadratic.Out)
      .onUpdate(apply);
    const flashOut = new TWEEN.Tween(anim, this.tweens)
      .to({ glow: 0 }, BLINK_MS * 1.6)
      .easing(TWEEN.Easing.Quadratic.In)
      .onUpdate(apply);
    flashIn.chain(flashOut);

    // Kick them off together → one blended, fluid collect.
    fly.start();
    shrink.start();
    flashIn.start();
  }

  private static uiIconCenter() {
    const r = this.uiIcon?.getBoundingClientRect();
    // Aim at the wood plank on the right of the panel, not the panel's center,
    // so the loot lands on the actual wood art.
    return r ? { x: r.left + r.width * UI_WOOD_X_FRAC, 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 and free its material (the texture is shared,
   *  so it's loaded once in init() and never disposed per-piece). */
  static remove(piece: Sprite) {
    ThreeC.removeFromScene(piece);
    (piece.material as SpriteMaterial).dispose();
    const i = this.pieces.indexOf(piece);
    if (i >= 0) this.pieces.splice(i, 1);
  }
}
