/**
 * Renderer-neutral ownership for the one local player and the one local camera.
 *
 * A mode is deliberately more specific than "play": it names the subsystem
 * allowed to consume held input and write the follow camera. Keeping this as a
 * small pure state machine lets the DOM, Three.js scene and Journey reducer
 * agree on a transition without any of them becoming the source of truth for
 * the others.
 */

export type CityControlMode = "overview" | "drive" | "actor" | "aircraft";
export type OfficeControlMode = "office-overview" | "office-walk";
export type ControlMode = CityControlMode | OfficeControlMode;

export interface ControlModeAvailability {
  insideOffice: boolean;
  drive: boolean;
  actor: boolean;
  aircraft: boolean;
  officeWalk: boolean;
}

export interface ControlModeState {
  mode: ControlMode;
  /** Increases only for an accepted change, useful to invalidate stale input. */
  revision: number;
}

export interface ControlModeTransition {
  previous: ControlMode;
  state: ControlModeState;
  changed: boolean;
  accepted: boolean;
  reason: "unchanged" | "unavailable" | null;
}

export interface CityControlOwnership {
  orbit: boolean;
  drive: boolean;
  actor: boolean;
  aircraft: boolean;
}

/** The renderer consumes this table; exactly one camera writer is always true. */
export function cityControlOwnership(mode: CityControlMode): CityControlOwnership {
  return {
    orbit: mode === "overview",
    drive: mode === "drive",
    actor: mode === "actor",
    aircraft: mode === "aircraft",
  };
}

export function createControlModeState(
  mode: ControlMode = "overview",
): ControlModeState {
  return { mode, revision: 0 };
}

export function cityControlMode(mode: ControlMode): CityControlMode {
  if (mode === "drive" || mode === "actor" || mode === "aircraft") return mode;
  return "overview";
}

export function controlModeAvailable(
  mode: ControlMode,
  availability: Readonly<ControlModeAvailability>,
): boolean {
  if (availability.insideOffice) {
    return mode === "office-overview" || (mode === "office-walk" && availability.officeWalk);
  }
  if (mode === "overview") return true;
  if (mode === "drive") return availability.drive;
  if (mode === "actor") return availability.actor;
  if (mode === "aircraft") return availability.aircraft;
  return false;
}

/** Resolve one requested transition without performing any side effects. */
export function transitionControlMode(
  current: Readonly<ControlModeState>,
  requested: ControlMode,
  availability: Readonly<ControlModeAvailability>,
): ControlModeTransition {
  const fallback: ControlMode = availability.insideOffice ? "office-overview" : "overview";
  const next = controlModeAvailable(requested, availability) ? requested : fallback;
  const accepted = next === requested;
  if (next === current.mode) {
    return {
      previous: current.mode,
      state: { ...current },
      changed: false,
      accepted,
      reason: accepted ? "unchanged" : "unavailable",
    };
  }
  return {
    previous: current.mode,
    state: { mode: next, revision: current.revision + 1 },
    changed: true,
    accepted,
    reason: accepted ? null : "unavailable",
  };
}
