import { createOpaqueId } from "./ids";
import {
  resolveSelectionActiveId,
  type EditorSelection
} from "./selection";
import type { WhiteboxSelectionMode } from "./whitebox-selection-mode";
import type { Vec3 } from "./vector";
import {
  cloneBrushGeometry,
  createBoxBrush,
  createConeBrush,
  createRadialPrismBrush,
  createTorusBrush,
  createWedgeBrush,
  type Brush,
  type BrushGeometry,
  type BrushKind,
  type BoxBrushGeometry,
  type ConeBrushGeometry,
  type RadialPrismBrushGeometry,
  type TorusBrushGeometry,
  type WedgeBrushGeometry,
  type WhiteboxEdgeId,
  type WhiteboxFaceId,
  type WhiteboxVertexId
} from "../document/brushes";
import { getScenePathPoint } from "../document/paths";
import type { SceneDocument } from "../document/scene-document";
import {
  cloneEntityInstance,
  getEntityKindLabel,
  type EntityInstance,
  type EntityKind
} from "../entities/entity-instances";
import {
  cloneModelInstance,
  getModelInstanceKindLabel
} from "../assets/model-instances";
import type { ViewportPanelId } from "../viewport-three/viewport-layout";
import {
  getBrushEdgeLabel,
  getBrushFaceLabel,
  getBrushKindLabel,
  getBrushVertexLabel
} from "../geometry/whitebox-topology";
import {
  getBrushEdgeAxis,
  getBrushEdgeScaleAxes,
  getBrushFaceAxis
} from "../geometry/whitebox-brush";

export type TransformOperation = "translate" | "rotate" | "scale";
export type TransformAxis = "x" | "y" | "z";
export type TransformAxisSpace = "world" | "local";
export type TransformSessionSource = "keyboard" | "gizmo" | "toolbar";

export interface YawEntityRotationState {
  kind: "yaw";
  yawDegrees: number;
}

export interface DirectionEntityRotationState {
  kind: "direction";
  direction: Vec3;
}

export interface NoEntityRotationState {
  kind: "none";
}

export type EntityTransformRotationState =
  | NoEntityRotationState
  | YawEntityRotationState
  | DirectionEntityRotationState;

export interface BrushTransformTarget {
  kind: "brush";
  brushId: string;
  brushKind: BrushKind;
  sideCount?: number;
  majorSegmentCount?: number;
  tubeSegmentCount?: number;
  initialCenter: Vec3;
  initialRotationDegrees: Vec3;
  initialSize: Vec3;
  initialGeometry: BrushGeometry;
}

export interface BrushFaceTransformTarget {
  kind: "brushFace";
  brushId: string;
  brushKind: BrushKind;
  sideCount?: number;
  majorSegmentCount?: number;
  tubeSegmentCount?: number;
  faceId: WhiteboxFaceId;
  initialCenter: Vec3;
  initialRotationDegrees: Vec3;
  initialSize: Vec3;
  initialGeometry: BrushGeometry;
}

export interface BrushEdgeTransformTarget {
  kind: "brushEdge";
  brushId: string;
  brushKind: BrushKind;
  sideCount?: number;
  majorSegmentCount?: number;
  tubeSegmentCount?: number;
  edgeId: WhiteboxEdgeId;
  initialCenter: Vec3;
  initialRotationDegrees: Vec3;
  initialSize: Vec3;
  initialGeometry: BrushGeometry;
}

export interface BrushVertexTransformTarget {
  kind: "brushVertex";
  brushId: string;
  brushKind: BrushKind;
  sideCount?: number;
  majorSegmentCount?: number;
  tubeSegmentCount?: number;
  vertexId: WhiteboxVertexId;
  initialCenter: Vec3;
  initialRotationDegrees: Vec3;
  initialSize: Vec3;
  initialGeometry: BrushGeometry;
}

export interface ModelInstanceTransformTarget {
  kind: "modelInstance";
  modelInstanceId: string;
  assetId: string;
  initialPosition: Vec3;
  initialRotationDegrees: Vec3;
  initialScale: Vec3;
}

export interface PathPointTransformTarget {
  kind: "pathPoint";
  pathId: string;
  pointId: string;
  initialPosition: Vec3;
}

export interface EntityTransformTarget {
  kind: "entity";
  entityId: string;
  entityKind: EntityKind;
  initialPosition: Vec3;
  initialRotation: EntityTransformRotationState;
}

export interface BrushesTransformTarget {
  kind: "brushes";
  activeBrushId: string;
  initialPivot: Vec3;
  items: BrushTransformTarget[];
}

export interface ModelInstancesTransformTarget {
  kind: "modelInstances";
  activeModelInstanceId: string;
  initialPivot: Vec3;
  items: ModelInstanceTransformTarget[];
}

export interface EntitiesTransformTarget {
  kind: "entities";
  activeEntityId: string;
  initialPivot: Vec3;
  items: EntityTransformTarget[];
}

export type TransformTarget =
  | BrushTransformTarget
  | BrushesTransformTarget
  | BrushFaceTransformTarget
  | BrushEdgeTransformTarget
  | BrushVertexTransformTarget
  | ModelInstanceTransformTarget
  | ModelInstancesTransformTarget
  | PathPointTransformTarget
  | EntityTransformTarget
  | EntitiesTransformTarget;

export interface BrushTransformPreview {
  kind: "brush";
  center: Vec3;
  rotationDegrees: Vec3;
  size: Vec3;
  geometry: BrushGeometry;
}

function areBrushGeometriesEqual(
  left: BrushGeometry,
  right: BrushGeometry
): boolean {
  const leftVertexIds = Object.keys(left.vertices);
  const rightVertexIds = Object.keys(right.vertices);

  if (leftVertexIds.length !== rightVertexIds.length) {
    return false;
  }

  return leftVertexIds.every((vertexId) => {
    const leftVertex = left.vertices[vertexId];
    const rightVertex = right.vertices[vertexId];
    return rightVertex !== undefined && areVec3Equal(leftVertex, rightVertex);
  });
}

export interface ModelInstanceTransformPreview {
  kind: "modelInstance";
  position: Vec3;
  rotationDegrees: Vec3;
  scale: Vec3;
}

export interface PathPointTransformPreview {
  kind: "pathPoint";
  position: Vec3;
}

export interface EntityTransformPreview {
  kind: "entity";
  position: Vec3;
  rotation: EntityTransformRotationState;
}

export interface BrushTransformPreviewItem {
  brushId: string;
  center: Vec3;
  rotationDegrees: Vec3;
  size: Vec3;
  geometry: BrushGeometry;
}

export interface BrushesTransformPreview {
  kind: "brushes";
  pivot: Vec3;
  items: BrushTransformPreviewItem[];
}

export interface ModelInstanceTransformPreviewItem {
  modelInstanceId: string;
  position: Vec3;
  rotationDegrees: Vec3;
  scale: Vec3;
}

export interface ModelInstancesTransformPreview {
  kind: "modelInstances";
  pivot: Vec3;
  items: ModelInstanceTransformPreviewItem[];
}

export interface EntityTransformPreviewItem {
  entityId: string;
  position: Vec3;
  rotation: EntityTransformRotationState;
}

export interface EntitiesTransformPreview {
  kind: "entities";
  pivot: Vec3;
  items: EntityTransformPreviewItem[];
}

export type TransformPreview =
  | BrushTransformPreview
  | BrushesTransformPreview
  | ModelInstanceTransformPreview
  | ModelInstancesTransformPreview
  | PathPointTransformPreview
  | EntityTransformPreview
  | EntitiesTransformPreview;

export interface ActiveTransformSession {
  kind: "active";
  id: string;
  source: TransformSessionSource;
  sourcePanelId: ViewportPanelId;
  operation: TransformOperation;
  surfaceSnapEnabled: boolean;
  axisConstraint: TransformAxis | null;
  axisConstraintSpace: TransformAxisSpace;
  target: TransformTarget;
  preview: TransformPreview;
}

export type TransformSessionState = ActiveTransformSession | { kind: "none" };

export interface TransformTargetResolution {
  target: TransformTarget | null;
  message: string | null;
}

function cloneVec3(vector: Vec3): Vec3 {
  return {
    x: vector.x,
    y: vector.y,
    z: vector.z
  };
}

function areVec3Equal(left: Vec3, right: Vec3): boolean {
  return left.x === right.x && left.y === right.y && left.z === right.z;
}

function averageVec3(vectors: readonly Vec3[]): Vec3 {
  if (vectors.length === 0) {
    return {
      x: 0,
      y: 0,
      z: 0
    };
  }

  const totals = vectors.reduce(
    (accumulator, vector) => ({
      x: accumulator.x + vector.x,
      y: accumulator.y + vector.y,
      z: accumulator.z + vector.z
    }),
    {
      x: 0,
      y: 0,
      z: 0
    }
  );

  return {
    x: totals.x / vectors.length,
    y: totals.y / vectors.length,
    z: totals.z / vectors.length
  };
}

function cloneEntityTransformRotationState(
  rotation: EntityTransformRotationState
): EntityTransformRotationState {
  switch (rotation.kind) {
    case "none":
      return {
        kind: "none"
      };
    case "yaw":
      return {
        kind: "yaw",
        yawDegrees: rotation.yawDegrees
      };
    case "direction":
      return {
        kind: "direction",
        direction: cloneVec3(rotation.direction)
      };
  }
}

function cloneBrushTransformPreviewItem(
  preview: BrushTransformPreviewItem
): BrushTransformPreviewItem {
  return {
    brushId: preview.brushId,
    center: cloneVec3(preview.center),
    rotationDegrees: cloneVec3(preview.rotationDegrees),
    size: cloneVec3(preview.size),
    geometry: cloneBrushGeometry(preview.geometry)
  };
}

function cloneModelInstanceTransformPreviewItem(
  preview: ModelInstanceTransformPreviewItem
): ModelInstanceTransformPreviewItem {
  return {
    modelInstanceId: preview.modelInstanceId,
    position: cloneVec3(preview.position),
    rotationDegrees: cloneVec3(preview.rotationDegrees),
    scale: cloneVec3(preview.scale)
  };
}

function cloneEntityTransformPreviewItem(
  preview: EntityTransformPreviewItem
): EntityTransformPreviewItem {
  return {
    entityId: preview.entityId,
    position: cloneVec3(preview.position),
    rotation: cloneEntityTransformRotationState(preview.rotation)
  };
}

function areBrushTransformPreviewItemsEqual(
  left: BrushTransformPreviewItem,
  right: BrushTransformPreviewItem
): boolean {
  return (
    left.brushId === right.brushId &&
    areVec3Equal(left.center, right.center) &&
    areVec3Equal(left.rotationDegrees, right.rotationDegrees) &&
    areVec3Equal(left.size, right.size) &&
    areBrushGeometriesEqual(left.geometry, right.geometry)
  );
}

function areModelInstanceTransformPreviewItemsEqual(
  left: ModelInstanceTransformPreviewItem,
  right: ModelInstanceTransformPreviewItem
): boolean {
  return (
    left.modelInstanceId === right.modelInstanceId &&
    areVec3Equal(left.position, right.position) &&
    areVec3Equal(left.rotationDegrees, right.rotationDegrees) &&
    areVec3Equal(left.scale, right.scale)
  );
}

function areEntityTransformPreviewItemsEqual(
  left: EntityTransformPreviewItem,
  right: EntityTransformPreviewItem
): boolean {
  return (
    left.entityId === right.entityId &&
    areVec3Equal(left.position, right.position) &&
    areEntityTransformRotationsEqual(left.rotation, right.rotation)
  );
}

function createBrushSnapshotFromTarget(
  target:
    | BrushTransformTarget
    | BrushFaceTransformTarget
    | BrushEdgeTransformTarget
    | BrushVertexTransformTarget
): Brush {
  switch (target.brushKind) {
    case "box":
      return createBoxBrush({
        id: target.brushId,
        center: target.initialCenter,
        rotationDegrees: target.initialRotationDegrees,
        size: target.initialSize,
        geometry: target.initialGeometry as BoxBrushGeometry
      });
    case "wedge":
      return createWedgeBrush({
        id: target.brushId,
        center: target.initialCenter,
        rotationDegrees: target.initialRotationDegrees,
        size: target.initialSize,
        geometry: target.initialGeometry as WedgeBrushGeometry
      });
    case "radialPrism":
      return createRadialPrismBrush({
        id: target.brushId,
        center: target.initialCenter,
        rotationDegrees: target.initialRotationDegrees,
        size: target.initialSize,
        sideCount: target.sideCount,
        geometry: target.initialGeometry as RadialPrismBrushGeometry
      });
    case "cone":
      return createConeBrush({
        id: target.brushId,
        center: target.initialCenter,
        rotationDegrees: target.initialRotationDegrees,
        size: target.initialSize,
        sideCount: target.sideCount,
        geometry: target.initialGeometry as ConeBrushGeometry
      });
    case "torus":
      return createTorusBrush({
        id: target.brushId,
        center: target.initialCenter,
        rotationDegrees: target.initialRotationDegrees,
        size: target.initialSize,
        majorSegmentCount: target.majorSegmentCount,
        tubeSegmentCount: target.tubeSegmentCount,
        geometry: target.initialGeometry as TorusBrushGeometry
      });
  }
}

function areEntityTransformRotationsEqual(
  left: EntityTransformRotationState,
  right: EntityTransformRotationState
): boolean {
  if (left.kind !== right.kind) {
    return false;
  }

  switch (left.kind) {
    case "none":
      return true;
    case "yaw":
      return right.kind === "yaw" && left.yawDegrees === right.yawDegrees;
    case "direction":
      return (
        right.kind === "direction" &&
        areVec3Equal(left.direction, right.direction)
      );
  }
}

export function createInactiveTransformSession(): TransformSessionState {
  return {
    kind: "none"
  };
}

export function cloneTransformTarget(target: TransformTarget): TransformTarget {
  switch (target.kind) {
    case "brush":
      return {
        kind: "brush",
        brushId: target.brushId,
        brushKind: target.brushKind,
        sideCount: target.sideCount,
        majorSegmentCount: target.majorSegmentCount,
        tubeSegmentCount: target.tubeSegmentCount,
        initialCenter: cloneVec3(target.initialCenter),
        initialRotationDegrees: cloneVec3(target.initialRotationDegrees),
        initialSize: cloneVec3(target.initialSize),
        initialGeometry: cloneBrushGeometry(target.initialGeometry)
      };
    case "brushes":
      return {
        kind: "brushes",
        activeBrushId: target.activeBrushId,
        initialPivot: cloneVec3(target.initialPivot),
        items: target.items.map(
          (item) => cloneTransformTarget(item) as BrushTransformTarget
        )
      };
    case "brushFace":
      return {
        kind: "brushFace",
        brushId: target.brushId,
        brushKind: target.brushKind,
        sideCount: target.sideCount,
        majorSegmentCount: target.majorSegmentCount,
        tubeSegmentCount: target.tubeSegmentCount,
        faceId: target.faceId,
        initialCenter: cloneVec3(target.initialCenter),
        initialRotationDegrees: cloneVec3(target.initialRotationDegrees),
        initialSize: cloneVec3(target.initialSize),
        initialGeometry: cloneBrushGeometry(target.initialGeometry)
      };
    case "brushEdge":
      return {
        kind: "brushEdge",
        brushId: target.brushId,
        brushKind: target.brushKind,
        sideCount: target.sideCount,
        majorSegmentCount: target.majorSegmentCount,
        tubeSegmentCount: target.tubeSegmentCount,
        edgeId: target.edgeId,
        initialCenter: cloneVec3(target.initialCenter),
        initialRotationDegrees: cloneVec3(target.initialRotationDegrees),
        initialSize: cloneVec3(target.initialSize),
        initialGeometry: cloneBrushGeometry(target.initialGeometry)
      };
    case "brushVertex":
      return {
        kind: "brushVertex",
        brushId: target.brushId,
        brushKind: target.brushKind,
        sideCount: target.sideCount,
        majorSegmentCount: target.majorSegmentCount,
        tubeSegmentCount: target.tubeSegmentCount,
        vertexId: target.vertexId,
        initialCenter: cloneVec3(target.initialCenter),
        initialRotationDegrees: cloneVec3(target.initialRotationDegrees),
        initialSize: cloneVec3(target.initialSize),
        initialGeometry: cloneBrushGeometry(target.initialGeometry)
      };
    case "modelInstance":
      return {
        kind: "modelInstance",
        modelInstanceId: target.modelInstanceId,
        assetId: target.assetId,
        initialPosition: cloneVec3(target.initialPosition),
        initialRotationDegrees: cloneVec3(target.initialRotationDegrees),
        initialScale: cloneVec3(target.initialScale)
      };
    case "modelInstances":
      return {
        kind: "modelInstances",
        activeModelInstanceId: target.activeModelInstanceId,
        initialPivot: cloneVec3(target.initialPivot),
        items: target.items.map(
          (item) => cloneTransformTarget(item) as ModelInstanceTransformTarget
        )
      };
    case "pathPoint":
      return {
        kind: "pathPoint",
        pathId: target.pathId,
        pointId: target.pointId,
        initialPosition: cloneVec3(target.initialPosition)
      };
    case "entity":
      return {
        kind: "entity",
        entityId: target.entityId,
        entityKind: target.entityKind,
        initialPosition: cloneVec3(target.initialPosition),
        initialRotation: cloneEntityTransformRotationState(
          target.initialRotation
        )
      };
    case "entities":
      return {
        kind: "entities",
        activeEntityId: target.activeEntityId,
        initialPivot: cloneVec3(target.initialPivot),
        items: target.items.map(
          (item) => cloneTransformTarget(item) as EntityTransformTarget
        )
      };
  }
}

export function cloneTransformPreview(
  preview: TransformPreview
): TransformPreview {
  switch (preview.kind) {
    case "brush":
      return {
        kind: "brush",
        center: cloneVec3(preview.center),
        rotationDegrees: cloneVec3(preview.rotationDegrees),
        size: cloneVec3(preview.size),
        geometry: cloneBrushGeometry(preview.geometry)
      };
    case "brushes":
      return {
        kind: "brushes",
        pivot: cloneVec3(preview.pivot),
        items: preview.items.map(cloneBrushTransformPreviewItem)
      };
    case "modelInstance":
      return {
        kind: "modelInstance",
        position: cloneVec3(preview.position),
        rotationDegrees: cloneVec3(preview.rotationDegrees),
        scale: cloneVec3(preview.scale)
      };
    case "modelInstances":
      return {
        kind: "modelInstances",
        pivot: cloneVec3(preview.pivot),
        items: preview.items.map(cloneModelInstanceTransformPreviewItem)
      };
    case "pathPoint":
      return {
        kind: "pathPoint",
        position: cloneVec3(preview.position)
      };
    case "entity":
      return {
        kind: "entity",
        position: cloneVec3(preview.position),
        rotation: cloneEntityTransformRotationState(preview.rotation)
      };
    case "entities":
      return {
        kind: "entities",
        pivot: cloneVec3(preview.pivot),
        items: preview.items.map(cloneEntityTransformPreviewItem)
      };
  }
}

export function cloneTransformSession(
  session: TransformSessionState
): TransformSessionState {
  if (session.kind === "none") {
    return session;
  }

  return {
    kind: "active",
    id: session.id,
    source: session.source,
    sourcePanelId: session.sourcePanelId,
    operation: session.operation,
    surfaceSnapEnabled: session.surfaceSnapEnabled,
    axisConstraint: session.axisConstraint,
    axisConstraintSpace: session.axisConstraintSpace,
    target: cloneTransformTarget(session.target),
    preview: cloneTransformPreview(session.preview)
  };
}

export function areTransformSessionsEqual(
  left: TransformSessionState,
  right: TransformSessionState
): boolean {
  if (left.kind !== right.kind) {
    return false;
  }

  if (left.kind === "none" || right.kind === "none") {
    return true;
  }

  return (
    left.id === right.id &&
    left.source === right.source &&
    left.sourcePanelId === right.sourcePanelId &&
    left.operation === right.operation &&
    left.surfaceSnapEnabled === right.surfaceSnapEnabled &&
    left.axisConstraint === right.axisConstraint &&
    left.axisConstraintSpace === right.axisConstraintSpace &&
    areTransformTargetsEqual(left.target, right.target) &&
    areTransformPreviewsEqual(left.preview, right.preview)
  );
}

function areTransformTargetsEqual(
  left: TransformTarget,
  right: TransformTarget
): boolean {
  if (left.kind !== right.kind) {
    return false;
  }

  switch (left.kind) {
    case "brush":
      return (
        right.kind === "brush" &&
        left.brushId === right.brushId &&
        left.brushKind === right.brushKind &&
        left.sideCount === right.sideCount &&
        left.majorSegmentCount === right.majorSegmentCount &&
        left.tubeSegmentCount === right.tubeSegmentCount &&
        areVec3Equal(left.initialCenter, right.initialCenter) &&
        areVec3Equal(
          left.initialRotationDegrees,
          right.initialRotationDegrees
        ) &&
        areVec3Equal(left.initialSize, right.initialSize) &&
        areBrushGeometriesEqual(left.initialGeometry, right.initialGeometry)
      );
    case "brushes":
      return (
        right.kind === "brushes" &&
        left.activeBrushId === right.activeBrushId &&
        areVec3Equal(left.initialPivot, right.initialPivot) &&
        left.items.length === right.items.length &&
        left.items.every((item, index) =>
          areTransformTargetsEqual(item, right.items[index])
        )
      );
    case "brushFace":
      return (
        right.kind === "brushFace" &&
        left.brushId === right.brushId &&
        left.brushKind === right.brushKind &&
        left.sideCount === right.sideCount &&
        left.majorSegmentCount === right.majorSegmentCount &&
        left.tubeSegmentCount === right.tubeSegmentCount &&
        left.faceId === right.faceId &&
        areVec3Equal(left.initialCenter, right.initialCenter) &&
        areVec3Equal(
          left.initialRotationDegrees,
          right.initialRotationDegrees
        ) &&
        areVec3Equal(left.initialSize, right.initialSize) &&
        areBrushGeometriesEqual(left.initialGeometry, right.initialGeometry)
      );
    case "brushEdge":
      return (
        right.kind === "brushEdge" &&
        left.brushId === right.brushId &&
        left.brushKind === right.brushKind &&
        left.sideCount === right.sideCount &&
        left.majorSegmentCount === right.majorSegmentCount &&
        left.tubeSegmentCount === right.tubeSegmentCount &&
        left.edgeId === right.edgeId &&
        areVec3Equal(left.initialCenter, right.initialCenter) &&
        areVec3Equal(
          left.initialRotationDegrees,
          right.initialRotationDegrees
        ) &&
        areVec3Equal(left.initialSize, right.initialSize) &&
        areBrushGeometriesEqual(left.initialGeometry, right.initialGeometry)
      );
    case "brushVertex":
      return (
        right.kind === "brushVertex" &&
        left.brushId === right.brushId &&
        left.brushKind === right.brushKind &&
        left.sideCount === right.sideCount &&
        left.majorSegmentCount === right.majorSegmentCount &&
        left.tubeSegmentCount === right.tubeSegmentCount &&
        left.vertexId === right.vertexId &&
        areVec3Equal(left.initialCenter, right.initialCenter) &&
        areVec3Equal(
          left.initialRotationDegrees,
          right.initialRotationDegrees
        ) &&
        areVec3Equal(left.initialSize, right.initialSize) &&
        areBrushGeometriesEqual(left.initialGeometry, right.initialGeometry)
      );
    case "modelInstance":
      return (
        right.kind === "modelInstance" &&
        left.modelInstanceId === right.modelInstanceId &&
        left.assetId === right.assetId &&
        areVec3Equal(left.initialPosition, right.initialPosition) &&
        areVec3Equal(
          left.initialRotationDegrees,
          right.initialRotationDegrees
        ) &&
        areVec3Equal(left.initialScale, right.initialScale)
      );
    case "modelInstances":
      return (
        right.kind === "modelInstances" &&
        left.activeModelInstanceId === right.activeModelInstanceId &&
        areVec3Equal(left.initialPivot, right.initialPivot) &&
        left.items.length === right.items.length &&
        left.items.every((item, index) =>
          areTransformTargetsEqual(item, right.items[index])
        )
      );
    case "pathPoint":
      return (
        right.kind === "pathPoint" &&
        left.pathId === right.pathId &&
        left.pointId === right.pointId &&
        areVec3Equal(left.initialPosition, right.initialPosition)
      );
    case "entity":
      return (
        right.kind === "entity" &&
        left.entityId === right.entityId &&
        left.entityKind === right.entityKind &&
        areVec3Equal(left.initialPosition, right.initialPosition) &&
        areEntityTransformRotationsEqual(
          left.initialRotation,
          right.initialRotation
        )
      );
    case "entities":
      return (
        right.kind === "entities" &&
        left.activeEntityId === right.activeEntityId &&
        areVec3Equal(left.initialPivot, right.initialPivot) &&
        left.items.length === right.items.length &&
        left.items.every((item, index) =>
          areTransformTargetsEqual(item, right.items[index])
        )
      );
  }
}

function areTransformPreviewsEqual(
  left: TransformPreview,
  right: TransformPreview
): boolean {
  if (left.kind !== right.kind) {
    return false;
  }

  switch (left.kind) {
    case "brush":
      return (
        right.kind === "brush" &&
        areVec3Equal(left.center, right.center) &&
        areVec3Equal(left.rotationDegrees, right.rotationDegrees) &&
        areVec3Equal(left.size, right.size) &&
        areBrushGeometriesEqual(left.geometry, right.geometry)
      );
    case "brushes":
      return (
        right.kind === "brushes" &&
        areVec3Equal(left.pivot, right.pivot) &&
        left.items.length === right.items.length &&
        left.items.every((item, index) =>
          areBrushTransformPreviewItemsEqual(item, right.items[index])
        )
      );
    case "modelInstance":
      return (
        right.kind === "modelInstance" &&
        areVec3Equal(left.position, right.position) &&
        areVec3Equal(left.rotationDegrees, right.rotationDegrees) &&
        areVec3Equal(left.scale, right.scale)
      );
    case "modelInstances":
      return (
        right.kind === "modelInstances" &&
        areVec3Equal(left.pivot, right.pivot) &&
        left.items.length === right.items.length &&
        left.items.every((item, index) =>
          areModelInstanceTransformPreviewItemsEqual(item, right.items[index])
        )
      );
    case "pathPoint":
      return (
        right.kind === "pathPoint" &&
        areVec3Equal(left.position, right.position)
      );
    case "entity":
      return (
        right.kind === "entity" &&
        areVec3Equal(left.position, right.position) &&
        areEntityTransformRotationsEqual(left.rotation, right.rotation)
      );
    case "entities":
      return (
        right.kind === "entities" &&
        areVec3Equal(left.pivot, right.pivot) &&
        left.items.length === right.items.length &&
        left.items.every((item, index) =>
          areEntityTransformPreviewItemsEqual(item, right.items[index])
        )
      );
  }
}

export function createTransformSession(options: {
  source: TransformSessionSource;
  sourcePanelId: ViewportPanelId;
  operation: TransformOperation;
  surfaceSnapEnabled?: boolean;
  axisConstraint?: TransformAxis | null;
  axisConstraintSpace?: TransformAxisSpace;
  target: TransformTarget;
}): ActiveTransformSession {
  return {
    kind: "active",
    id: createOpaqueId("transform-session"),
    source: options.source,
    sourcePanelId: options.sourcePanelId,
    operation: options.operation,
    surfaceSnapEnabled: options.surfaceSnapEnabled ?? false,
    axisConstraint: options.axisConstraint ?? null,
    axisConstraintSpace: options.axisConstraintSpace ?? "world",
    target: cloneTransformTarget(options.target),
    preview: createTransformPreviewFromTarget(options.target)
  };
}

export function createTransformPreviewFromTarget(
  target: TransformTarget
): TransformPreview {
  switch (target.kind) {
    case "brush":
    case "brushFace":
    case "brushEdge":
    case "brushVertex":
      return {
        kind: "brush",
        center: cloneVec3(target.initialCenter),
        rotationDegrees: cloneVec3(target.initialRotationDegrees),
        size: cloneVec3(target.initialSize),
        geometry: cloneBrushGeometry(target.initialGeometry)
      };
    case "brushes":
      return {
        kind: "brushes",
        pivot: cloneVec3(target.initialPivot),
        items: target.items.map((item) => ({
          brushId: item.brushId,
          center: cloneVec3(item.initialCenter),
          rotationDegrees: cloneVec3(item.initialRotationDegrees),
          size: cloneVec3(item.initialSize),
          geometry: cloneBrushGeometry(item.initialGeometry)
        }))
      };
    case "modelInstance":
      return {
        kind: "modelInstance",
        position: cloneVec3(target.initialPosition),
        rotationDegrees: cloneVec3(target.initialRotationDegrees),
        scale: cloneVec3(target.initialScale)
      };
    case "modelInstances":
      return {
        kind: "modelInstances",
        pivot: cloneVec3(target.initialPivot),
        items: target.items.map((item) => ({
          modelInstanceId: item.modelInstanceId,
          position: cloneVec3(item.initialPosition),
          rotationDegrees: cloneVec3(item.initialRotationDegrees),
          scale: cloneVec3(item.initialScale)
        }))
      };
    case "pathPoint":
      return {
        kind: "pathPoint",
        position: cloneVec3(target.initialPosition)
      };
    case "entity":
      return {
        kind: "entity",
        position: cloneVec3(target.initialPosition),
        rotation: cloneEntityTransformRotationState(target.initialRotation)
      };
    case "entities":
      return {
        kind: "entities",
        pivot: cloneVec3(target.initialPivot),
        items: target.items.map((item) => ({
          entityId: item.entityId,
          position: cloneVec3(item.initialPosition),
          rotation: cloneEntityTransformRotationState(item.initialRotation)
        }))
      };
  }
}

export function doesTransformSessionChangeTarget(
  session: ActiveTransformSession
): boolean {
  switch (session.target.kind) {
    case "brush":
    case "brushFace":
    case "brushEdge":
    case "brushVertex":
      return (
        session.preview.kind === "brush" &&
        (!areVec3Equal(session.preview.center, session.target.initialCenter) ||
          !areVec3Equal(
            session.preview.rotationDegrees,
            session.target.initialRotationDegrees
          ) ||
          !areVec3Equal(session.preview.size, session.target.initialSize) ||
          !areBrushGeometriesEqual(
            session.preview.geometry,
            session.target.initialGeometry
          ))
      );
    case "brushes":
      const brushesTarget = session.target;
      return (
        session.preview.kind === "brushes" &&
        (!areVec3Equal(session.preview.pivot, brushesTarget.initialPivot) ||
          session.preview.items.length !== brushesTarget.items.length ||
          session.preview.items.some((item, index) => {
            const targetItem = brushesTarget.items[index];

            return (
              item.brushId !== targetItem.brushId ||
              !areVec3Equal(item.center, targetItem.initialCenter) ||
              !areVec3Equal(
                item.rotationDegrees,
                targetItem.initialRotationDegrees
              ) ||
              !areVec3Equal(item.size, targetItem.initialSize) ||
              !areBrushGeometriesEqual(item.geometry, targetItem.initialGeometry)
            );
          }))
      );
    case "modelInstance":
      return (
        session.preview.kind === "modelInstance" &&
        (!areVec3Equal(
          session.preview.position,
          session.target.initialPosition
        ) ||
          !areVec3Equal(
            session.preview.rotationDegrees,
            session.target.initialRotationDegrees
          ) ||
          !areVec3Equal(session.preview.scale, session.target.initialScale))
      );
    case "modelInstances":
      const modelInstancesTarget = session.target;
      return (
        session.preview.kind === "modelInstances" &&
        (!areVec3Equal(
          session.preview.pivot,
          modelInstancesTarget.initialPivot
        ) ||
          session.preview.items.length !== modelInstancesTarget.items.length ||
          session.preview.items.some((item, index) => {
            const targetItem = modelInstancesTarget.items[index];

            return (
              item.modelInstanceId !== targetItem.modelInstanceId ||
              !areVec3Equal(item.position, targetItem.initialPosition) ||
              !areVec3Equal(
                item.rotationDegrees,
                targetItem.initialRotationDegrees
              ) ||
              !areVec3Equal(item.scale, targetItem.initialScale)
            );
          }))
      );
    case "pathPoint":
      return (
        session.preview.kind === "pathPoint" &&
        !areVec3Equal(
          session.preview.position,
          session.target.initialPosition
        )
      );
    case "entity":
      return (
        session.preview.kind === "entity" &&
        (!areVec3Equal(
          session.preview.position,
          session.target.initialPosition
        ) ||
          !areEntityTransformRotationsEqual(
            session.preview.rotation,
            session.target.initialRotation
          ))
      );
    case "entities":
      const entitiesTarget = session.target;
      return (
        session.preview.kind === "entities" &&
        (!areVec3Equal(session.preview.pivot, entitiesTarget.initialPivot) ||
          session.preview.items.length !== entitiesTarget.items.length ||
          session.preview.items.some((item, index) => {
            const targetItem = entitiesTarget.items[index];

            return (
              item.entityId !== targetItem.entityId ||
              !areVec3Equal(item.position, targetItem.initialPosition) ||
              !areEntityTransformRotationsEqual(
                item.rotation,
                targetItem.initialRotation
              )
            );
          }))
      );
  }
}

export function getTransformOperationLabel(
  operation: TransformOperation
): string {
  switch (operation) {
    case "translate":
      return "Move";
    case "rotate":
      return "Rotate";
    case "scale":
      return "Scale";
  }
}

export function getTransformAxisLabel(axis: TransformAxis): string {
  return axis.toUpperCase();
}

export function getTransformAxisSpaceLabel(
  axisSpace: TransformAxisSpace
): string {
  switch (axisSpace) {
    case "world":
      return "World";
    case "local":
      return "Local";
  }
}

export function getTransformTargetLabel(target: TransformTarget): string {
  switch (target.kind) {
    case "brush":
      return getBrushKindLabel(createBrushSnapshotFromTarget(target));
    case "brushes":
      return `${target.items.length} Whitebox Solid${target.items.length === 1 ? "" : "s"}`;
    case "brushFace":
      return `Whitebox Face (${getBrushFaceLabel(createBrushSnapshotFromTarget(target), target.faceId)})`;
    case "brushEdge":
      return `Whitebox Edge (${getBrushEdgeLabel(createBrushSnapshotFromTarget(target), target.edgeId)})`;
    case "brushVertex":
      return `Whitebox Vertex (${getBrushVertexLabel(createBrushSnapshotFromTarget(target), target.vertexId)})`;
    case "modelInstance":
      return getModelInstanceKindLabel();
    case "modelInstances":
      return `${target.items.length} ${getModelInstanceKindLabel()}${target.items.length === 1 ? "" : "s"}`;
    case "pathPoint":
      return "Path Point";
    case "entity":
      return getEntityKindLabel(target.entityKind);
    case "entities":
      return `${target.items.length} Entit${target.items.length === 1 ? "y" : "ies"}`;
  }
}

export function getSupportedTransformOperations(
  target: TransformTarget
): TransformOperation[] {
  switch (target.kind) {
    case "brush":
    case "brushes":
    case "brushFace":
    case "brushEdge":
      return ["translate", "rotate", "scale"];
    case "brushVertex":
    case "pathPoint":
      return ["translate"];
    case "modelInstance":
    case "modelInstances":
      return ["translate", "rotate", "scale"];
    case "entity":
      return target.initialRotation.kind === "none"
        ? ["translate"]
        : ["translate", "rotate"];
    case "entities":
      return target.items.every(
        (item) => getSupportedTransformOperations(item).includes("rotate")
      )
        ? ["translate", "rotate"]
        : ["translate"];
  }
}

export function supportsTransformOperation(
  target: TransformTarget,
  operation: TransformOperation
): boolean {
  return getSupportedTransformOperations(target).includes(operation);
}

export function supportsTransformSurfaceSnapTarget(
  target: TransformTarget
): boolean {
  switch (target.kind) {
    case "brush":
    case "brushes":
    case "modelInstance":
    case "modelInstances":
      return true;
    case "entity":
      return target.entityKind === "triggerVolume";
    case "entities":
      return target.items.every((item) => item.entityKind === "triggerVolume");
    case "brushFace":
    case "brushEdge":
    case "brushVertex":
    case "pathPoint":
      return false;
  }
}

export function supportsTransformAxisConstraint(
  session: ActiveTransformSession,
  axis: TransformAxis
): boolean {
  const brushTarget =
    session.target.kind === "brush" ||
    session.target.kind === "brushes" ||
    session.target.kind === "brushFace" ||
    session.target.kind === "brushEdge" ||
    session.target.kind === "brushVertex"
      ? session.target.kind === "brushes"
        ? createBrushSnapshotFromTarget(session.target.items[0])
        : createBrushSnapshotFromTarget(session.target)
      : null;

  switch (session.operation) {
    case "translate":
      return true;
    case "scale":
      if (
        session.target.kind === "modelInstance" ||
        session.target.kind === "modelInstances" ||
        session.target.kind === "brush"
        ||
        session.target.kind === "brushes"
      ) {
        return true;
      }

      if (
        session.target.kind === "brushVertex" ||
        session.target.kind === "pathPoint"
      ) {
        return false;
      }

      if (session.target.kind === "brushFace") {
        if (brushTarget === null) {
          return false;
        }

        return axis === getBrushFaceAxis(brushTarget, session.target.faceId);
      }

      if (session.target.kind === "brushEdge") {
        if (brushTarget === null) {
          return false;
        }

        return getBrushEdgeScaleAxes(brushTarget, session.target.edgeId).includes(
          axis
        );
      }

      return false;
    case "rotate":
      if (
        session.target.kind === "entity" &&
        session.target.initialRotation.kind === "yaw"
      ) {
        return axis === "y";
      }

      if (session.target.kind === "entities") {
        return session.target.items.every((item) =>
          item.initialRotation.kind === "none"
            ? false
            : item.initialRotation.kind === "yaw"
              ? axis === "y"
              : true
        );
      }

      if (session.target.kind === "brushFace") {
        if (brushTarget === null) {
          return false;
        }

        return axis === getBrushFaceAxis(brushTarget, session.target.faceId);
      }

      if (session.target.kind === "brushEdge") {
        if (brushTarget === null) {
          return false;
        }

        return axis === getBrushEdgeAxis(brushTarget, session.target.edgeId);
      }

      if (session.target.kind === "brushVertex") {
        return false;
      }

      return true;
  }
}

export function supportsLocalTransformAxisConstraint(
  session: ActiveTransformSession,
  axis: TransformAxis
): boolean {
  if (!supportsTransformAxisConstraint(session, axis)) {
    return false;
  }

  if (session.operation === "scale") {
    return false;
  }

  switch (session.target.kind) {
    case "brush":
    case "brushes":
    case "modelInstance":
    case "modelInstances":
      return true;
    case "entity":
      return session.target.initialRotation.kind !== "none";
    case "entities":
      return session.target.items.every(
        (item) => item.initialRotation.kind !== "none"
      );
    case "brushFace":
    case "brushEdge":
    case "brushVertex":
      return session.operation === "translate";
    case "pathPoint":
      return false;
  }
}

function resolveEntityRotation(
  entity: EntityInstance
): EntityTransformRotationState {
  switch (entity.kind) {
    case "cameraRig":
      return {
        kind: "none"
      };
    case "playerStart":
    case "sceneEntry":
    case "npc":
    case "teleportTarget":
      return {
        kind: "yaw",
        yawDegrees: entity.yawDegrees
      };
    case "spotLight":
      return {
        kind: "direction",
        direction: cloneVec3(entity.direction)
      };
    case "pointLight":
    case "soundEmitter":
    case "triggerVolume":
    case "interactable":
      return {
        kind: "none"
      };
  }
}

function createBrushTransformTarget(
  document: SceneDocument,
  brushId: string
): TransformTargetResolution {
  const brush = document.brushes[brushId];

  if (brush === undefined) {
    return {
      target: null,
      message: "Select a supported whitebox solid before transforming it."
    };
  }

  return {
    target: {
      kind: "brush",
      brushId: brush.id,
      brushKind: brush.kind,
      sideCount:
        brush.kind === "radialPrism" || brush.kind === "cone"
          ? brush.sideCount
          : undefined,
      majorSegmentCount:
        brush.kind === "torus" ? brush.majorSegmentCount : undefined,
      tubeSegmentCount:
        brush.kind === "torus" ? brush.tubeSegmentCount : undefined,
      initialCenter: cloneVec3(brush.center),
      initialRotationDegrees: cloneVec3(brush.rotationDegrees),
      initialSize: cloneVec3(brush.size),
      initialGeometry: cloneBrushGeometry(brush.geometry)
    },
    message: null
  };
}

function resolveActiveTransformTargetId(
  ids: readonly string[],
  activeSelectionId: string | null
): string | null {
  if (activeSelectionId !== null && ids.includes(activeSelectionId)) {
    return activeSelectionId;
  }

  return ids.at(-1) ?? null;
}

function createBrushesTransformTarget(
  document: SceneDocument,
  brushIds: string[],
  activeSelectionId: string | null
): TransformTargetResolution {
  const items: BrushTransformTarget[] = [];

  for (const brushId of brushIds) {
    const itemResolution = createBrushTransformTarget(document, brushId);

    if (itemResolution.target === null || itemResolution.target.kind !== "brush") {
      return itemResolution;
    }

    items.push(itemResolution.target);
  }

  const activeBrushId = resolveActiveTransformTargetId(
    brushIds,
    activeSelectionId
  );

  if (activeBrushId === null) {
    return {
      target: null,
      message: "Select at least one whitebox solid before transforming it."
    };
  }

  return {
    target: {
      kind: "brushes",
      activeBrushId,
      initialPivot: averageVec3(items.map((item) => item.initialCenter)),
      items
    },
    message: null
  };
}

function createBrushFaceTransformTarget(
  document: SceneDocument,
  brushId: string,
  faceId: WhiteboxFaceId
): TransformTargetResolution {
  const brushResolution = createBrushTransformTarget(document, brushId);

  if (
    brushResolution.target === null ||
    brushResolution.target.kind !== "brush"
  ) {
    return brushResolution;
  }

  return {
    target: {
      kind: "brushFace",
      brushId,
      brushKind: brushResolution.target.brushKind,
      sideCount: brushResolution.target.sideCount,
      majorSegmentCount: brushResolution.target.majorSegmentCount,
      tubeSegmentCount: brushResolution.target.tubeSegmentCount,
      faceId,
      initialCenter: cloneVec3(brushResolution.target.initialCenter),
      initialRotationDegrees: cloneVec3(
        brushResolution.target.initialRotationDegrees
      ),
      initialSize: cloneVec3(brushResolution.target.initialSize),
      initialGeometry: cloneBrushGeometry(
        brushResolution.target.initialGeometry
      )
    },
    message: null
  };
}

function createBrushEdgeTransformTarget(
  document: SceneDocument,
  brushId: string,
  edgeId: WhiteboxEdgeId
): TransformTargetResolution {
  const brushResolution = createBrushTransformTarget(document, brushId);

  if (
    brushResolution.target === null ||
    brushResolution.target.kind !== "brush"
  ) {
    return brushResolution;
  }

  return {
    target: {
      kind: "brushEdge",
      brushId,
      brushKind: brushResolution.target.brushKind,
      sideCount: brushResolution.target.sideCount,
      majorSegmentCount: brushResolution.target.majorSegmentCount,
      tubeSegmentCount: brushResolution.target.tubeSegmentCount,
      edgeId,
      initialCenter: cloneVec3(brushResolution.target.initialCenter),
      initialRotationDegrees: cloneVec3(
        brushResolution.target.initialRotationDegrees
      ),
      initialSize: cloneVec3(brushResolution.target.initialSize),
      initialGeometry: cloneBrushGeometry(
        brushResolution.target.initialGeometry
      )
    },
    message: null
  };
}

function createBrushVertexTransformTarget(
  document: SceneDocument,
  brushId: string,
  vertexId: WhiteboxVertexId
): TransformTargetResolution {
  const brushResolution = createBrushTransformTarget(document, brushId);

  if (
    brushResolution.target === null ||
    brushResolution.target.kind !== "brush"
  ) {
    return brushResolution;
  }

  return {
    target: {
      kind: "brushVertex",
      brushId,
      brushKind: brushResolution.target.brushKind,
      sideCount: brushResolution.target.sideCount,
      majorSegmentCount: brushResolution.target.majorSegmentCount,
      tubeSegmentCount: brushResolution.target.tubeSegmentCount,
      vertexId,
      initialCenter: cloneVec3(brushResolution.target.initialCenter),
      initialRotationDegrees: cloneVec3(
        brushResolution.target.initialRotationDegrees
      ),
      initialSize: cloneVec3(brushResolution.target.initialSize),
      initialGeometry: cloneBrushGeometry(
        brushResolution.target.initialGeometry
      )
    },
    message: null
  };
}

function createEntityTransformTarget(
  document: SceneDocument,
  entityId: string
): TransformTargetResolution {
  const entity = document.entities[entityId];

  if (entity === undefined) {
    return {
      target: null,
      message: "Select an authored entity before transforming it."
    };
  }

  if (entity.kind === "cameraRig" && entity.rigType === "rail") {
    return {
      target: null,
      message: "Rail camera rigs are positioned by their assigned path."
    };
  }

  if (!("position" in entity)) {
    return {
      target: null,
      message: "Select an authored entity before transforming it."
    };
  }

  const clonedEntity = cloneEntityInstance(entity);

  return {
    target: {
      kind: "entity",
      entityId: clonedEntity.id,
      entityKind: clonedEntity.kind,
      initialPosition: cloneVec3(entity.position),
      initialRotation: resolveEntityRotation(clonedEntity)
    },
    message: null
  };
}

function createEntitiesTransformTarget(
  document: SceneDocument,
  entityIds: string[],
  activeSelectionId: string | null
): TransformTargetResolution {
  const items: EntityTransformTarget[] = [];

  for (const entityId of entityIds) {
    const itemResolution = createEntityTransformTarget(document, entityId);

    if (itemResolution.target === null || itemResolution.target.kind !== "entity") {
      return itemResolution;
    }

    items.push(itemResolution.target);
  }

  const activeEntityId = resolveActiveTransformTargetId(
    entityIds,
    activeSelectionId
  );

  if (activeEntityId === null) {
    return {
      target: null,
      message: "Select at least one authored entity before transforming it."
    };
  }

  return {
    target: {
      kind: "entities",
      activeEntityId,
      initialPivot: averageVec3(items.map((item) => item.initialPosition)),
      items
    },
    message: null
  };
}

function createModelInstanceTransformTarget(
  document: SceneDocument,
  modelInstanceId: string
): TransformTargetResolution {
  const modelInstance = document.modelInstances[modelInstanceId];

  if (modelInstance === undefined) {
    return {
      target: null,
      message: "Select a model instance before transforming it."
    };
  }

  const clonedModelInstance = cloneModelInstance(modelInstance);

  return {
    target: {
      kind: "modelInstance",
      modelInstanceId: clonedModelInstance.id,
      assetId: clonedModelInstance.assetId,
      initialPosition: cloneVec3(clonedModelInstance.position),
      initialRotationDegrees: cloneVec3(clonedModelInstance.rotationDegrees),
      initialScale: cloneVec3(clonedModelInstance.scale)
    },
    message: null
  };
}

function createModelInstancesTransformTarget(
  document: SceneDocument,
  modelInstanceIds: string[],
  activeSelectionId: string | null
): TransformTargetResolution {
  const items: ModelInstanceTransformTarget[] = [];

  for (const modelInstanceId of modelInstanceIds) {
    const itemResolution = createModelInstanceTransformTarget(
      document,
      modelInstanceId
    );

    if (
      itemResolution.target === null ||
      itemResolution.target.kind !== "modelInstance"
    ) {
      return itemResolution;
    }

    items.push(itemResolution.target);
  }

  const activeModelInstanceId = resolveActiveTransformTargetId(
    modelInstanceIds,
    activeSelectionId
  );

  if (activeModelInstanceId === null) {
    return {
      target: null,
      message: "Select at least one model instance before transforming it."
    };
  }

  return {
    target: {
      kind: "modelInstances",
      activeModelInstanceId,
      initialPivot: averageVec3(items.map((item) => item.initialPosition)),
      items
    },
    message: null
  };
}

function createPathPointTransformTarget(
  document: SceneDocument,
  pathId: string,
  pointId: string
): TransformTargetResolution {
  const path = document.paths[pathId];

  if (path === undefined) {
    return {
      target: null,
      message: "Select a path point before transforming it."
    };
  }

  const point = getScenePathPoint(path, pointId);

  if (point === null) {
    return {
      target: null,
      message: "Select a valid path point before transforming it."
    };
  }

  return {
    target: {
      kind: "pathPoint",
      pathId,
      pointId,
      initialPosition: cloneVec3(point.position)
    },
    message: null
  };
}

export function resolveTransformTarget(
  document: SceneDocument,
  selection: EditorSelection,
  whiteboxSelectionMode: WhiteboxSelectionMode = "object",
  activeSelectionId: string | null = resolveSelectionActiveId(selection, null)
): TransformTargetResolution {
  switch (selection.kind) {
    case "none":
      return {
        target: null,
        message:
          "Select a single brush, path point, entity, or model instance before transforming it."
      };
    case "brushFace":
      if (whiteboxSelectionMode !== "face") {
        return {
          target: null,
          message: "Switch to Face mode to transform a selected whitebox face."
        };
      }

      return createBrushFaceTransformTarget(
        document,
        selection.brushId,
        selection.faceId
      );
    case "brushEdge":
      if (whiteboxSelectionMode !== "edge") {
        return {
          target: null,
          message: "Switch to Edge mode to transform a selected whitebox edge."
        };
      }

      return createBrushEdgeTransformTarget(
        document,
        selection.brushId,
        selection.edgeId
      );
    case "brushVertex":
      if (whiteboxSelectionMode !== "vertex") {
        return {
          target: null,
          message:
            "Switch to Vertex mode to transform a selected whitebox vertex."
        };
      }

      return createBrushVertexTransformTarget(
        document,
        selection.brushId,
        selection.vertexId
      );
    case "brushes":
      if (whiteboxSelectionMode !== "object") {
        return {
          target: null,
          message:
            "Switch to Object mode to transform the whole whitebox solid."
        };
      }

      if (selection.ids.length === 0) {
        return {
          target: null,
          message: "Select at least one brush before transforming it."
        };
      }

      return selection.ids.length === 1
        ? createBrushTransformTarget(document, selection.ids[0])
        : createBrushesTransformTarget(document, selection.ids, activeSelectionId);
    case "terrains":
      return {
        target: null,
        message:
          "Terrain transforms and editing tools land in a later inspector slice."
      };
    case "entities":
      if (selection.ids.length === 0) {
        return {
          target: null,
          message: "Select at least one entity before transforming it."
        };
      }

      return selection.ids.length === 1
        ? createEntityTransformTarget(document, selection.ids[0])
        : createEntitiesTransformTarget(document, selection.ids, activeSelectionId);
    case "paths":
      return {
        target: null,
        message:
          "Select a path point before transforming a path."
      };
    case "pathPoint":
      return createPathPointTransformTarget(
        document,
        selection.pathId,
        selection.pointId
      );
    case "modelInstances":
      if (selection.ids.length === 0) {
        return {
          target: null,
          message: "Select at least one model instance before transforming it."
        };
      }

      return selection.ids.length === 1
        ? createModelInstanceTransformTarget(document, selection.ids[0])
        : createModelInstancesTransformTarget(
            document,
            selection.ids,
            activeSelectionId
          );
  }
}
