import { BoxGeometry, Color, Group, Mesh, MeshLambertMaterial } from "three";
import { applyPSXMaterial } from "../post/PSXMaterial.js";

/**
 * Fallback player mesh: a blocky figure roughly matching the mover capsule.
 *
 * The harness character (`CharacterModel` + `assets/characters/survivor.glb`)
 * is the primary avatar. This remains as a drop-in if the GLB fails to load,
 * and for remote peers until they share the same skinned model. Proportions
 * still matter — the silhouette has to read at 240p (GDD §14).
 */

export interface PlayerAvatarOptions {
  totalHeight: number;
  color?: number;
}

export class PlayerAvatar {
  readonly group = new Group();
  private readonly leftArm: Mesh;
  private readonly rightArm: Mesh;
  private readonly leftLeg: Mesh;
  private readonly rightLeg: Mesh;
  private stride = 0;

  constructor(options: PlayerAvatarOptions) {
    const height = options.totalHeight;
    const color = options.color ?? 0x9aa3b2;

    const skin = () =>
      applyPSXMaterial(new MeshLambertMaterial({ color, flatShading: true }));
    const dark = () =>
      applyPSXMaterial(
        new MeshLambertMaterial({ color: new Color(color).multiplyScalar(0.55), flatShading: true }),
      );

    const legHeight = height * 0.45;
    const torsoHeight = height * 0.33;
    const headSize = height * 0.16;

    const torso = new Mesh(new BoxGeometry(0.42, torsoHeight, 0.26), skin());
    torso.position.y = legHeight + torsoHeight * 0.5;

    const head = new Mesh(new BoxGeometry(headSize, headSize, headSize), skin());
    head.position.y = legHeight + torsoHeight + headSize * 0.5;

    this.leftArm = new Mesh(new BoxGeometry(0.12, torsoHeight * 0.95, 0.14), dark());
    this.leftArm.position.set(-0.3, legHeight + torsoHeight * 0.52, 0);

    this.rightArm = new Mesh(new BoxGeometry(0.12, torsoHeight * 0.95, 0.14), dark());
    this.rightArm.position.set(0.3, legHeight + torsoHeight * 0.52, 0);

    this.leftLeg = new Mesh(new BoxGeometry(0.16, legHeight, 0.18), dark());
    this.leftLeg.position.set(-0.11, legHeight * 0.5, 0);

    this.rightLeg = new Mesh(new BoxGeometry(0.16, legHeight, 0.18), dark());
    this.rightLeg.position.set(0.11, legHeight * 0.5, 0);

    this.group.add(torso, head, this.leftArm, this.rightArm, this.leftLeg, this.rightLeg);
  }

  /**
   * Procedural walk cycle driven by distance travelled rather than by time, so
   * the legs stay in sync when the player is blocked by a wall.
   */
  update(distanceMoved: number, dt: number): void {
    this.stride += distanceMoved * 3.4;

    const moving = distanceMoved > 1e-4;
    const amplitude = moving ? 0.7 : 0;
    const swing = Math.sin(this.stride) * amplitude;

    this.leftLeg.rotation.x = swing;
    this.rightLeg.rotation.x = -swing;
    this.leftArm.rotation.x = -swing * 0.6;
    this.rightArm.rotation.x = swing * 0.6;

    // Settle back to neutral when standing still.
    if (!moving) {
      const settle = 1 - Math.pow(0.0001, dt);
      for (const limb of [this.leftLeg, this.rightLeg, this.leftArm, this.rightArm]) {
        limb.rotation.x += (0 - limb.rotation.x) * settle;
      }
    }
  }

  setTransform(x: number, y: number, z: number, yaw: number): void {
    this.group.position.set(x, y, z);
    this.group.rotation.y = yaw;
  }

  dispose(): void {
    this.group.traverse((object) => {
      if (!(object instanceof Mesh)) return;
      object.geometry.dispose();
      (object.material as MeshLambertMaterial).dispose();
    });
  }
}
