import { Object3D, Vector3 } from "three";
import { PhysicsBody, PhysicsLayer } from "./PhysicsC";
import { Trigger } from "./TriggerC";
import { LootC } from "./LootC";

// A full crate's health. There are 3 damage levels (S1/S2/S3) splitting it
// evenly: S1 = 100–67%, S2 = 66–34%, S3 = 33–1%, broken at 0. A crate that
// ships already damaged (no S1 node, etc.) starts at the matching lower health.
const CRATE_MAX_HEALTH = 100;
const LEVELS = 3;

/** One breakable crate. Damage states are indexed by level: 0=S1, 1=S2, 2=S3. */
export interface Crate {
  root: Object3D;
  statesByLevel: (Object3D | null)[]; // length 3; null where that state isn't authored
  startLevel: number;                 // lowest authored state = how damaged it starts
  level: number;                      // currently shown level
  collider: PhysicsBody;
  trigger: Trigger | null;
  health: number;
  maxHealth: number;
  broken: boolean;
}

/**
 * Sets up the crates baked into the map's "Lootable" group.
 *
 * Each Wooden_Box ships its damage states (S1/S2/S3) all visible at once, but
 * NOT every crate has all three — some start at S2 or S3, i.e. pre-damaged. We
 * read the lowest authored state, show only it, set health to the matching
 * percentage, and turn the collider proxy into a STATIC cannon box.
 */
export class LootableC {
  static crates: Crate[] = [];

  static init(lootableGroup: Object3D | null) {
    if (!lootableGroup) {
      console.warn("[Lootable] group not found");
      return;
    }

    lootableGroup.visible = true;
    lootableGroup.updateWorldMatrix(true, true); // collider world positions must be current

    for (const crate of lootableGroup.children) {
      // Map each authored damage state to its level via the _S<n> suffix.
      const statesGroup = crate.children.find(c => c.name.includes("_States"));
      const statesByLevel: (Object3D | null)[] = [null, null, null];
      if (statesGroup) {
        for (const s of statesGroup.children) {
          const m = s.name.match(/_S(\d)$/);
          if (m) {
            const lvl = parseInt(m[1], 10) - 1; // S1→0, S2→1, S3→2
            if (lvl >= 0 && lvl < LEVELS) statesByLevel[lvl] = s;
          }
        }
      }

      // Start at the lowest authored state (most intact one present).
      let startLevel = statesByLevel.findIndex(s => s !== null);
      if (startLevel < 0) startLevel = 0;
      statesByLevel.forEach((s, lvl) => { if (s) s.visible = lvl === startLevel; });

      // Health for that starting level (full crate = 100, S2 ≈ 67, S3 ≈ 33).
      const health = CRATE_MAX_HEALTH * (LEVELS - startLevel) / LEVELS;

      // The per-crate collider proxy → static box, then hide it (physics only).
      const proxy = crate.children.find(c => c.name.startsWith("BoxCollider"));
      if (!proxy) continue;

      const collider = new PhysicsBody(
        proxy,
        false,               // not a trigger — it's solid
        0,                   // mass 0 → static
        PhysicsLayer.Wall,   // same layer as walls, so the player collides with it
        PhysicsLayer.Player
      );
      proxy.visible = false;

      this.crates.push({
        root: crate, statesByLevel, startLevel, level: startLevel,
        collider, trigger: null, health, maxHealth: CRATE_MAX_HEALTH, broken: false,
      });
    }

    console.log(`[Lootable] crates built: ${this.crates.length}`);
  }

  /** Subtract health; switch to the matching damage state, or break at 0. */
  static damageCrate(crate: Crate, amount: number) {
    if (crate.broken) return;

    crate.health -= amount;
    if (crate.health <= 0) {
      this.breakCrate(crate);
      return;
    }

    // Map remaining health to a level, never below where this crate started.
    let level = Math.floor((1 - crate.health / crate.maxHealth) * LEVELS);
    level = Math.max(crate.startLevel, Math.min(LEVELS - 1, level));
    if (level !== crate.level) {
      crate.level = level;
      crate.statesByLevel.forEach((s, lvl) => { if (s) s.visible = lvl === level; });
      // Loot drops on every state change, not only on destruction.
      LootC.spawn(crate.root.getWorldPosition(new Vector3()));
    }
  }

  /** Crate destroyed: hide it and remove its physics + trigger from the world. */
  static breakCrate(crate: Crate) {
    if (crate.broken) return;
    crate.broken = true;

    crate.statesByLevel.forEach(s => { if (s) s.visible = false; });
    crate.collider.destroy();
    crate.trigger?.destroy();
    crate.trigger = null;

    // TODO #7/#8: spawn wood loot at crate.root world position, scatter with a
    // bounce, then tween it into the resource counter.
    console.log("[Lootable] crate broken");

    LootC.spawn(crate.root.getWorldPosition(new Vector3()));
  }
}
