/**
 * Thin Three.js presentation for `robotActivity.ts`.
 *
 * Position, job, battery, payload, progress, recovery, and terminals are owned
 * by the fixed-step renderer-independent controller. This module never invents
 * an errand and never moves a robot independently; it mirrors snapshots into a
 * shared procedural rig and honest visual cues.
 */

import * as THREE from "three";
import { createAssetContext } from "../assets/kit.ts";
import type { MaterialRegistry } from "../assets/materials.ts";
import {
  buildOptimus,
  cloneOptimus,
  disposeOptimus,
  restOptimus,
  type OptimusRig,
} from "../assets/office/optimus.ts";
import {
  createRobotActivity,
  type RobotActivitySnapshot,
  type RobotActivityState,
  type RobotPayload,
} from "./robotActivity.ts";
import {
  resolveRobotOperations,
  type RobotActivityMode,
  type RobotOperationsDefinition,
} from "./robotOperations.ts";
import type { Plan } from "./plan.ts";

const STATUS_COLORS: Record<RobotActivityMode, number> = {
  idle: 0x74808b,
  patrol: 0x61d7a8,
  deliver: 0xf2b134,
  inspect: 0x67b7ff,
  charge: 0xb78cff,
  "blocked-recovery": 0xff6f61,
};

export interface RobotLayerOptions {
  materials: MaterialRegistry;
  operations: RobotOperationsDefinition;
  seed?: number;
}

/** Stable live projection consumed by lighting, minimap, and diagnostics. */
export interface RobotView {
  id: string;
  levelId: string;
  position: THREE.Vector3;
  mode: RobotActivityMode;
  phase: string;
  battery: number;
  payload: RobotPayload;
  progress: number;
  terminalReason: RobotActivityState["terminalReason"];
  simulated: true;
}

export interface RobotLayer {
  group: THREE.Group;
  disclosure: string;
  tick(dt: number): void;
  robots(): readonly RobotView[];
  snapshot(): RobotActivitySnapshot;
  dispose(): void;
}

interface PresentedRobot {
  rig: OptimusRig;
  view: RobotView;
  status: THREE.Mesh<THREE.TorusGeometry, THREE.MeshBasicMaterial>;
  parcel: THREE.Mesh<THREE.BoxGeometry, THREE.MeshStandardMaterial>;
  tool: THREE.Group;
}

export function createRobotLayer(plan: Plan, options: RobotLayerOptions): RobotLayer {
  const operations = resolveRobotOperations(plan, options.operations);
  const activity = createRobotActivity(plan, operations, { seed: options.seed });
  const group = new THREE.Group();
  group.name = "simulated-robot-operations";
  group.userData.disclosure = activity.disclosure;

  const context = createAssetContext({ materials: options.materials });
  const prototype = buildOptimus(context);
  const ringGeometry = new THREE.TorusGeometry(0.34, 0.025, 6, 24);
  ringGeometry.rotateX(Math.PI / 2);
  const parcelGeometry = new THREE.BoxGeometry(0.25, 0.18, 0.2);
  const parcelMaterial = new THREE.MeshStandardMaterial({ color: 0xc78b48, roughness: 0.8 });
  const statusMaterials = new Map<RobotActivityMode, THREE.MeshBasicMaterial>();
  for (const [mode, color] of Object.entries(STATUS_COLORS) as Array<[RobotActivityMode, number]>) {
    statusMaterials.set(mode, new THREE.MeshBasicMaterial({ color, transparent: true, opacity: 0.88 }));
  }
  const toolMaterial = new THREE.MeshStandardMaterial({
    color: 0x73c6ff,
    emissive: 0x15384f,
    roughness: 0.34,
    metalness: 0.18,
  });
  const dockMaterial = new THREE.MeshStandardMaterial({ color: 0x3b4651, roughness: 0.7, metalness: 0.25 });
  const dockLightMaterial = new THREE.MeshBasicMaterial({ color: STATUS_COLORS.charge });
  const dockPadGeometry = new THREE.BoxGeometry(0.72, 0.035, 0.62);
  const dockPostGeometry = new THREE.BoxGeometry(0.12, 0.55, 0.12);
  const dockLightGeometry = new THREE.BoxGeometry(0.14, 0.08, 0.06);
  const toolBodyGeometry = new THREE.BoxGeometry(0.07, 0.2, 0.05);
  const toolLensGeometry = new THREE.BoxGeometry(0.09, 0.05, 0.07);

  for (const station of operations.stations.values()) {
    if (station.role !== "charge") continue;
    const floorY = plan.level(station.levelId)?.floorY ?? 0;
    const dock = new THREE.Group();
    dock.name = `charge-dock:${station.id}`;
    dock.position.set(station.position.x, floorY, station.position.z);
    dock.rotation.y = Math.atan2(-station.facing.x, -station.facing.z);
    const pad = new THREE.Mesh(dockPadGeometry, dockMaterial);
    pad.position.y = 0.018;
    const post = new THREE.Mesh(dockPostGeometry, dockMaterial);
    post.position.set(0, 0.275, 0.25);
    const light = new THREE.Mesh(dockLightGeometry, dockLightMaterial);
    light.position.set(0, 0.44, 0.18);
    dock.add(pad, post, light);
    group.add(dock);
  }

  const presented: PresentedRobot[] = [];
  for (const state of activity.states()) {
    const rig = cloneOptimus(prototype);
    rig.root.name = `simulated-robot:${state.id}`;
    rig.root.userData.simulated = true;
    const status = new THREE.Mesh(ringGeometry, statusMaterials.get(state.mode)!);
    status.name = "activity-status";
    status.position.y = 0.035;
    const parcel = new THREE.Mesh(parcelGeometry, parcelMaterial);
    parcel.name = "parcel-cue";
    parcel.position.set(0, 1.04, -0.21);
    parcel.visible = false;
    const tool = new THREE.Group();
    tool.name = "inspection-tool-cue";
    const toolBody = new THREE.Mesh(toolBodyGeometry, toolMaterial);
    const toolLens = new THREE.Mesh(toolLensGeometry, toolMaterial);
    toolLens.position.y = 0.11;
    tool.add(toolBody, toolLens);
    tool.position.set(0.27, 1.15, -0.1);
    tool.rotation.z = -0.24;
    tool.visible = false;
    rig.root.add(status, parcel, tool);
    group.add(rig.root);
    presented.push({
      rig,
      status,
      parcel,
      tool,
      view: {
        id: state.id,
        levelId: state.levelId,
        position: new THREE.Vector3(),
        mode: state.mode,
        phase: state.phase,
        battery: state.battery,
        payload: state.payload,
        progress: state.progress,
        terminalReason: state.terminalReason,
        simulated: true,
      },
    });
  }
  const views = presented.map((robot) => robot.view);

  function present(snapshot: RobotActivitySnapshot): void {
    for (let index = 0; index < snapshot.robots.length; index += 1) {
      const state = snapshot.robots[index]!;
      const robot = presented[index]!;
      const floorY = plan.level(state.levelId)?.floorY ?? 0;
      robot.view.position.set(state.position.x, floorY, state.position.z);
      robot.view.mode = state.mode;
      robot.view.phase = state.phase;
      robot.view.battery = state.battery;
      robot.view.payload = state.payload;
      robot.view.progress = state.progress;
      robot.view.terminalReason = state.terminalReason;
      robot.rig.root.position.copy(robot.view.position);
      robot.rig.root.rotation.y = Math.atan2(-state.facing.x, -state.facing.z);
      robot.status.material = statusMaterials.get(state.mode)!;
      robot.status.scale.setScalar(0.9 + state.progress * 0.18);
      robot.status.visible = state.terminalReason === null;
      robot.parcel.visible = state.payload === "parcel";
      robot.tool.visible = state.mode === "inspect" && state.phase === "inspecting";
      pose(robot.rig, state);
    }
  }

  present(activity.snapshot());
  return {
    group,
    disclosure: activity.disclosure,
    tick(dt) {
      present(activity.tick(dt));
    },
    robots: () => views,
    snapshot: () => activity.snapshot(),
    dispose() {
      for (const robot of presented) group.remove(robot.rig.root);
      disposeOptimus(prototype);
      ringGeometry.dispose();
      parcelGeometry.dispose();
      parcelMaterial.dispose();
      for (const material of statusMaterials.values()) material.dispose();
      toolMaterial.dispose();
      dockMaterial.dispose();
      dockLightMaterial.dispose();
      dockPadGeometry.dispose();
      dockPostGeometry.dispose();
      dockLightGeometry.dispose();
      toolBodyGeometry.dispose();
      toolLensGeometry.dispose();
    },
  };
}

function pose(rig: OptimusRig, state: RobotActivityState): void {
  restOptimus(rig.joints);
  if (state.mode === "charge") {
    rig.joints.head.rotation.x = 0.12;
    rig.joints.shoulderL.rotation.x = 0.18;
    rig.joints.shoulderR.rotation.x = 0.18;
    return;
  }
  if (state.mode === "inspect" && state.phase === "inspecting") {
    rig.joints.shoulderR.rotation.x = -0.72;
    rig.joints.elbowR.rotation.x = -0.58;
    rig.joints.head.rotation.y = 0.08 * Math.sin(state.objectiveTicks * 0.3);
    return;
  }
  if (state.phase.startsWith("to-") || state.phase === "patrolling" || state.mode === "blocked-recovery") {
    const wave = Math.sin(state.travelledM * 8.8);
    rig.joints.hipL.rotation.x = wave * 0.34;
    rig.joints.hipR.rotation.x = -wave * 0.34;
    rig.joints.kneeL.rotation.x = Math.max(0, -wave) * 0.42;
    rig.joints.kneeR.rotation.x = Math.max(0, wave) * 0.42;
    rig.joints.shoulderL.rotation.x = -wave * 0.22;
    rig.joints.shoulderR.rotation.x = wave * 0.22;
  }
}
