import { UpdateController } from "@24tools/playable_template";
import { Easing, Group, Tween } from "@tweenjs/tween.js";
import { Object3D, Vector3 } from "three";
import { ThreeC } from "../ThreeC";
import { ResourceInventoryC } from "./ResourceInventoryC";
import { ResourceScreenFly } from "./ResourceScreenFly";
import { ResourceType } from "./ResourceType";
import { ResourceUIC } from "./ResourceUIC";

const FALL_MS = 300;
const EJECT_MS = 180;
const SPAWN_HEIGHT = 0.55;
const SCATTER_MIN = 1;
const SCATTER_MAX = 1.05;
const BOUNCE_HEIGHTS = [0.22, 0.09];
const BOUNCE_UP_MS = 200;
const BOUNCE_DOWN_MS = 180;
const REST_AFTER_BOUNCE_MS = 400;
const FLY_DURATION_MS = 520;
const CHAIN_FLY_DELAY_MS = 140;
const FLY_SCALE = 0.35;
const STAGGER_MS = 70;
const GROUND_LIFT = 0.08;

type FlyQueueItem = {
  pickup: Object3D;
  type: ResourceType;
};


export class ResourceFlyC {
  private static inited = false;
  private static tweenGroup = new Group();
  private static flyQueue: FlyQueueItem[] = [];
  private static isProcessingFlyQueue = false;

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

    this.hideTemplateMeshes();

    UpdateController.Instance.onUpdate.addDelegate(() => {
      this.tweenGroup.update(performance.now());
    });
  }

  private static hideTemplateMeshes() {
    Object.values(ResourceType).forEach((type) => {
      const template = ThreeC.getObject(type);
      if (!template) return;

      template.visible = false;
      template.removeFromParent();
    });
  }

  static launch(origin: Vector3, type: ResourceType, index: number, count: number) {
    window.setTimeout(() => {
      this.startPickup(origin, type, index, count);
    }, index * STAGGER_MS);
  }

  private static startPickup(origin: Vector3, type: ResourceType, index: number, count: number) {
    const pickup = ResourceScreenFly.createPickup(type);
    if (!pickup) return;

    const scatter = this.getScatterOffset(index, count);
    const landPos = origin.clone().add(scatter);
    landPos.y += GROUND_LIFT;
    const spawnPos = origin.clone().add(new Vector3(0, SPAWN_HEIGHT * 0.25, 0));
    const peakPos = origin.clone()
      .add(scatter.clone().multiplyScalar(0.65))
      .add(new Vector3(0, SPAWN_HEIGHT, 0));

    pickup.position.copy(spawnPos);
    ResourceScreenFly.orientToCamera(pickup);
    ThreeC.addToScene(pickup);

    const ejectTween = new Tween(pickup.position)
      .to({ x: peakPos.x, y: peakPos.y, z: peakPos.z }, EJECT_MS)
      .easing(Easing.Quadratic.Out)
      .onUpdate(() => {
        ResourceScreenFly.orientToCamera(pickup);
      });

    const fallTween = new Tween(pickup.position)
      .to({ x: landPos.x, y: landPos.y, z: landPos.z }, FALL_MS)
      .easing(Easing.Quadratic.In)
      .onUpdate(() => {
        ResourceScreenFly.orientToCamera(pickup);
      })
      .onComplete(() => {
        this.playBounces(pickup, landPos, type);
      });

    ejectTween.chain(fallTween);
    this.tweenGroup.add(ejectTween);
    this.tweenGroup.add(fallTween);
    ejectTween.start(performance.now());
  }

  private static getScatterOffset(index: number, count: number) {
    const baseAngle = (index / Math.max(count, 1)) * Math.PI * 2;
    const angle = baseAngle + (Math.random() - 0.5) * 0.7;
    const distance = SCATTER_MIN + Math.random() * (SCATTER_MAX - SCATTER_MIN);

    return new Vector3(
      Math.cos(angle) * distance,
      0,
      Math.sin(angle) * distance,
    );
  }

  private static playBounces(pickup: Object3D, landPos: Vector3, type: ResourceType) {
    const runBounce = (bounceIndex: number) => {
      if (bounceIndex >= BOUNCE_HEIGHTS.length) {
        window.setTimeout(() => {
          this.enqueueFlyToUI(pickup, type);
        }, REST_AFTER_BOUNCE_MS);
        return;
      }

      const height = BOUNCE_HEIGHTS[bounceIndex];
      const durationScale = height / BOUNCE_HEIGHTS[0];
      const peakY = landPos.y + height;

      const upTween = new Tween(pickup.position)
        .to({ y: peakY }, BOUNCE_UP_MS * durationScale)
        .easing(Easing.Quadratic.Out)
        .onUpdate(() => {
          ResourceScreenFly.orientToCamera(pickup);
        });

      const downTween = new Tween(pickup.position)
        .to({ y: landPos.y }, BOUNCE_DOWN_MS * durationScale)
        .easing(Easing.Quadratic.In)
        .onUpdate(() => {
          ResourceScreenFly.orientToCamera(pickup);
        })
        .onComplete(() => {
          runBounce(bounceIndex + 1);
        });

      upTween.chain(downTween);
      this.tweenGroup.add(upTween);
      this.tweenGroup.add(downTween);
      upTween.start(performance.now());
    };

    runBounce(0);
  }

  private static enqueueFlyToUI(pickup: Object3D, type: ResourceType) {
    this.flyQueue.push({ pickup, type });
    this.processFlyQueue();
  }

  private static processFlyQueue() {
    if (this.isProcessingFlyQueue || this.flyQueue.length === 0) return;

    this.isProcessingFlyQueue = true;
    const item = this.flyQueue.shift();
    if (!item) {
      this.isProcessingFlyQueue = false;
      return;
    }

    this.flyToUI(item.pickup, item.type, () => {
      window.setTimeout(() => {
        this.isProcessingFlyQueue = false;
        this.processFlyQueue();
      }, CHAIN_FLY_DELAY_MS);
    });
  }

  private static flyToUI(pickup: Object3D, type: ResourceType, onComplete: () => void) {
    const iconCenter = ResourceUIC.getIconCenter(type);
    if (!iconCenter) {
      ThreeC.removeFromScene(pickup);
      onComplete();
      return;
    }

    const startScreen = ResourceScreenFly.worldToScreen(pickup.position);
    const endScreen = ResourceScreenFly.screenPointFromClient(
      iconCenter.x,
      iconCenter.y,
      startScreen.z,
    );

    ResourceScreenFly.flyAlongScreen(
      pickup,
      startScreen,
      endScreen,
      this.tweenGroup,
      FLY_DURATION_MS,
      FLY_SCALE,
      () => {
        ResourceInventoryC.add(type, 1);
        ResourceUIC.refresh(type);
        onComplete();
      },
    );
  }
}
