import { Object3D, Vector3 } from "three";
import { ThreeC } from "./ThreeC";
import { PlayerC } from "./PlayerC";
import { CameraFollowC } from "./CameraFollowC";
import { PhysicsBody, PhysicsLayer } from "./PhysicsC";
import { BreakablePropC } from "./BreakablePropC";
import { ResourceC } from "./ResourceC";
import { PayZoneC } from "./PayZoneC";
import { HudC } from "./HudC";
import { PropVfxC } from "./PropVfxC";
import { WeaponTrailC } from "./WeaponTrailC";
import { SparkleFxC } from "./SparkleFxC";

// Above and behind the player, along the road's forward (+Z) direction —
// player now spawns at the other end of the road, walking toward +Z, so
// "behind" flipped from +Z to -Z to keep looking the same way they walk.
const CAMERA_OFFSET = new Vector3(0, 9, -9);

export class TestSceneC {
  static init() {
    PropVfxC.init();
    SparkleFxC.init();

    const { colliders, payZoneNode } = this.createMap();

    PlayerC.init(colliders);
    CameraFollowC.init(PlayerC.object, CAMERA_OFFSET);
    // Lets the camera's look-ahead react when the player locks facing onto
    // a nearby breakable while stopped (a pure in-place rotation with no
    // movement of its own) — see CameraFollowC.setFacingTargetGetter.
    CameraFollowC.setFacingTargetGetter(() => PlayerC.getFacingTarget());

    // Must come after CameraFollowC.init(): the trail's screen-facing
    // ribbon captures the camera's view direction once at init time,
    // relying on the fact that CameraFollowC's rotation is frozen for good
    // right after its own init (see CameraFollowC — it never rotates again).
    WeaponTrailC.init(PlayerC.getWeaponAnchor());

    // Reuses the map's own "UI_Interactive_Zone_02" marker as the Pay
    // Zone's position/visual (see PayZoneC) — drains ResourceC's collected
    // total into the zone while the player stands in it. Must come after
    // PlayerC.init(): PlayerC.object doesn't exist yet inside createMap().
    PayZoneC.init(payZoneNode, PlayerC.object);

    // HUD counter (top-right) — collected resources fly to it instead of
    // just vanishing, and it's also where PayZoneC's deposit-into-the-zone
    // flight originates from. The displayed number is collected minus
    // delivered — rises on collection, falls as PayZoneC drains it into
    // the zone.
    HudC.init();
    ResourceC.setFlyTarget(() => HudC.getWorldAnchorPosition());
    HudC.setBalanceGetter(() => ResourceC.getCollectedCount() - PayZoneC.getDepositedCount());
  }

  private static createMap() {
    const map = ThreeC.getObject("map");
    const colliders: Object3D[] = [];

    // physics-only collision proxies exported alongside the visual meshes;
    // hide them from render, but keep them as camera-obstacle geometry
    map.traverse((child) => {
      if (/collider/i.test(child.name)) {
        child.visible = false;
        colliders.push(child);
      }
    });

    // The map ships a real wood-icon quad, just sitting unused in the scene
    // (textured with Icon_Wood) — hand it to ResourceC as the pickup visual
    // instead of the generic placeholder cube, and hide the static instance
    // since it's now a template to clone, not scenery.
    const woodIcon = map.getObjectByName("UI_Wood");
    if (woodIcon) {
      ResourceC.setPickupTemplate(woodIcon);
      woodIcon.visible = false;
    }

    // Leftover art scaffolding near the Pay Zone — a second wood-icon quad
    // stacked on it, an unused "tool zone" plate, and a rotated
    // Background/Foreground/Middleground billboard group (likely a fake
    // install-button mockup, floating above ground nearby) — always
    // visible by default, unused by any code, just cluttering the scene.
    // ⚠️ Named "UI_Wood.001" (with a dot) in the source glb, but whatever
    // loads it here strips the dot — the live scene has it as "UI_Wood001".
    // getObjectByName("UI_Wood.001") always silently returned undefined,
    // so this never actually hid anything (confirmed by traversing the live
    // scene — see chat). This is why it kept showing up no matter what else
    // got hidden/removed.
    const woodIconAlt = map.getObjectByName("UI_Wood001");
    if (woodIconAlt) woodIconAlt.visible = false;

    const toolZone = map.getObjectByName("UI_Tool_Zone");
    if (toolZone) toolZone.visible = false;

    const installMockup = map.getObjectByName("UI");
    if (installMockup) installMockup.visible = false;

    // "Lootable" crates own their collider bodies themselves (so they can be
    // torn down again when a crate breaks) — everything else just gets a
    // plain static wall body.
    const propColliders = BreakablePropC.init(map.getObjectByName("Lootable"), (colliderNode) => {
      const index = colliders.indexOf(colliderNode);
      if (index !== -1) colliders.splice(index, 1);
      PlayerC.removeObstacle(colliderNode);
    });

    for (const collider of colliders) {
      if (propColliders.has(collider)) continue;
      new PhysicsBody(collider, false, 0, PhysicsLayer.Wall, PhysicsLayer.Player | PhysicsLayer.Enemy);
    }

    ThreeC.setShadowsStateForChildren(map, true, true);
    // Must run after the line above — it force-enables shadows on every
    // mesh under the map, including BreakablePropC's flash overlays, which
    // this turns back off (see disableFlashShadows).
    BreakablePropC.disableFlashShadows();

    ThreeC.addToScene(map);

    return { colliders, payZoneNode: map.getObjectByName("UI_Interactive_Zone_02") };
  }
}
