export interface AsukaAssemblyVector3 {
  x: number;
  y: number;
  z: number;
}

export interface AsukaAssemblyTransform {
  position?: AsukaAssemblyVector3;
  /** Euler rotation in radians, matching the 3D renderer contract. */
  rotation?: AsukaAssemblyVector3;
  scale?: AsukaAssemblyVector3;
}

export interface AsukaAssemblyBounds {
  min: AsukaAssemblyVector3;
  max: AsukaAssemblyVector3;
  center: AsukaAssemblyVector3;
  size: AsukaAssemblyVector3;
}

export type AsukaAssemblyAnchorRole = 'source' | 'target' | 'both';

/**
 * A local connection point declared on a catalog element.
 *
 * Connector names are deliberately domain-neutral. A connector can represent
 * a furniture joint, a rail slot, a machine coupling, a booth panel edge, or
 * any other spatial relationship.
 */
export interface AsukaAssemblyAnchorDefinition {
  id: string;
  label?: string;
  role?: AsukaAssemblyAnchorRole;
  position: AsukaAssemblyVector3;
  /**
   * `scaled` multiplies the local position by the live geometry resize ratio.
   * Use it for edge anchors that must remain attached to a parametric body.
   * `bounds` interprets each coordinate as a normalized offset from the live
   * component bounds, where -0.5 and 0.5 represent opposite faces.
   */
  positionMode?: 'fixed' | 'scaled' | 'bounds';
  /** Local outward direction. Defaults to +Z. */
  normal?: AsukaAssemblyVector3;
  /** Local up direction. Defaults to +Y. */
  up?: AsukaAssemblyVector3;
  /** Connector families provided by this point. */
  provides?: string[];
  /** Connector families accepted by this point. Empty means any compatible point. */
  accepts?: string[];
  snapDistance?: number;
  maxConnections?: number;
  alignRotation?: boolean;
  metadata?: Record<string, unknown>;
}

export interface AsukaAssemblyCollisionDefinition {
  enabled?: boolean;
  /** Optional local box. Runtime mesh bounds are used when omitted. */
  bounds?: {
    center?: AsukaAssemblyVector3;
    size: AsukaAssemblyVector3;
  };
  margin?: number;
  group?: string;
  collidesWith?: string[];
}

export interface AsukaAssemblyClearanceDefinition {
  id: string;
  label?: string;
  center?: AsukaAssemblyVector3;
  size: AsukaAssemblyVector3;
  /** Keep the reserved zone attached to a parametrically resized surface. */
  positionMode?: 'fixed' | 'scaled';
  /** Resize the reserved volume itself with the component geometry. */
  sizeMode?: 'fixed' | 'scaled';
  severity?: 'error' | 'warning';
  group?: string;
  collidesWith?: string[];
  message?: string;
  metadata?: Record<string, unknown>;
}

export type AsukaAssemblyAdjacencyRuleType =
  | 'requires-connection'
  | 'requires-neighbor'
  | 'forbids-neighbor'
  | 'min-gap'
  | 'max-gap';

export interface AsukaAssemblySelector {
  componentIds?: string[];
  catalogIds?: string[];
  categories?: string[];
  tags?: string[];
  connectorTypes?: string[];
}

export interface AsukaAssemblyAdjacencyRule {
  id: string;
  label?: string;
  type: AsukaAssemblyAdjacencyRuleType;
  target?: AsukaAssemblySelector;
  minDistance?: number;
  maxDistance?: number;
  severity?: 'error' | 'warning';
  message?: string;
  metadata?: Record<string, unknown>;
}

export interface AsukaAssemblyBomItemDefinition {
  id?: string;
  sku?: string;
  label: string;
  quantity?: number;
  unit?: string;
  unitPrice?: number | string;
  category?: string;
  metadata?: Record<string, unknown>;
}

export type AsukaAssemblySupportMode = 'free' | 'floor' | 'wall' | 'ceiling';

export interface AsukaAssemblyPlacementDefinition {
  /**
   * `floor` keeps the component's lowest collision point on the plan floor.
   * `wall` places it against the closest interior wall face.
   * `ceiling` keeps its highest collision point below the plan ceiling.
   * `free` leaves all three axes under API/user control.
   */
  support?: AsukaAssemblySupportMode;
  /** Vertical offset above the support surface, in scene world units. */
  supportOffset?: number;
}

/**
 * Assembly recipe stored on an Asuka catalog element.
 */
export interface AsukaAssemblyComponentDefinition {
  schemaVersion: 1;
  enabled?: boolean;
  category?: string;
  tags?: string[];
  placement?: AsukaAssemblyPlacementDefinition;
  anchors?: AsukaAssemblyAnchorDefinition[];
  collision?: AsukaAssemblyCollisionDefinition;
  clearances?: AsukaAssemblyClearanceDefinition[];
  adjacency?: AsukaAssemblyAdjacencyRule[];
  bom?: AsukaAssemblyBomItemDefinition[];
  metadata?: Record<string, unknown>;
}

export interface AsukaAssemblySurfaceMaterial {
  color?: string;
  textureUrl?: string;
  textureScale?: {
    x?: number;
    y?: number;
  };
  /**
   * Physical size covered by one texture tile, expressed in renderer world
   * units. When present it takes precedence over textureScale.
   */
  textureSize?: {
    width?: number;
    height?: number;
  };
  /** Texture rotation in degrees. */
  textureRotation?: number;
  opacity?: number;
}

export interface AsukaAssemblyMaterialPreset {
  id: string;
  name: string;
  category?: string;
  thumbnailUrl?: string;
  material: AsukaAssemblySurfaceMaterial;
  /** Built-in presets are returned by the API but are not duplicated in state. */
  builtIn?: boolean;
  metadata?: Record<string, unknown>;
}

export type AsukaAssemblyMaterialScope = 'face' | 'surface' | 'room';
export type AsukaAssemblyMaterialSide = 'interior' | 'exterior' | 'both';

export interface AsukaAssemblyMaterialApplyOptions {
  scope?: AsukaAssemblyMaterialScope;
  side?: AsukaAssemblyMaterialSide;
}

export interface AsukaAssemblyPlanPoint {
  x: number;
  z: number;
}

export interface AsukaAssemblyFloorEdgeConstraint {
  lockLength?: boolean;
  lockAngle?: boolean;
}

export interface AsukaAssemblyFloorSurface {
  id: string;
  kind: 'floor';
  label?: string;
  /** Ordered X/Z outline in world units. Concave simple polygons are supported. */
  points: AsukaAssemblyPlanPoint[];
  /** Stable point identities used to keep linked walls attached while the outline changes. */
  pointIds?: string[];
  /** Precision locks keyed by the stable edge id returned by getSurfaceMetrics(). */
  edgeConstraints?: Record<string, AsukaAssemblyFloorEdgeConstraint>;
  y?: number;
  thickness?: number;
  material?: AsukaAssemblySurfaceMaterial;
  metadata?: Record<string, unknown>;
}

export interface AsukaAssemblyFloorEdgeMetric {
  index: number;
  id: string;
  startPointId: string;
  endPointId: string;
  start: AsukaAssemblyPlanPoint;
  end: AsukaAssemblyPlanPoint;
  length: number;
  /** Counter-clockwise world angle from +X, normalized to [-180, 180). */
  angleDegrees: number;
  /** False when this room edge is intentionally left open. */
  wallEnabled: boolean;
  lockLength: boolean;
  lockAngle: boolean;
}

export interface AsukaAssemblyFloorMetrics {
  area: number;
  perimeter: number;
  edges: AsukaAssemblyFloorEdgeMetric[];
}

export interface AsukaAssemblyWallSurface {
  id: string;
  kind: 'wall';
  label?: string;
  start: { x: number; z: number };
  end: { x: number; z: number };
  baseY?: number;
  height?: number;
  thickness?: number;
  lockLength?: boolean;
  lockAngle?: boolean;
  lockHeight?: boolean;
  /** Fallback finish used for edges and for both faces when no side-specific finish is provided. */
  material?: AsukaAssemblySurfaceMaterial;
  /** Finish rendered on the room-facing side of the wall. */
  interiorMaterial?: AsukaAssemblySurfaceMaterial;
  /** Finish rendered on the opposite side of the wall. */
  exteriorMaterial?: AsukaAssemblySurfaceMaterial;
  metadata?: Record<string, unknown>;
}

/** Resolved wall footprint used to create clean joints between room walls. */
export interface AsukaAssemblyRoomWallFootprint {
  interiorStart: AsukaAssemblyPlanPoint;
  interiorEnd: AsukaAssemblyPlanPoint;
  exteriorStart: AsukaAssemblyPlanPoint;
  exteriorEnd: AsukaAssemblyPlanPoint;
  miteredStart: boolean;
  miteredEnd: boolean;
}

export type AsukaAssemblySurface = AsukaAssemblyFloorSurface | AsukaAssemblyWallSurface;

export interface AsukaAssemblySurfacePatch {
  label?: string;
  points?: AsukaAssemblyPlanPoint[];
  pointIds?: string[];
  edgeConstraints?: Record<string, AsukaAssemblyFloorEdgeConstraint>;
  start?: { x: number; z: number };
  end?: { x: number; z: number };
  y?: number;
  baseY?: number;
  height?: number;
  thickness?: number;
  lockLength?: boolean;
  lockAngle?: boolean;
  lockHeight?: boolean;
  material?: AsukaAssemblySurfaceMaterial;
  interiorMaterial?: AsukaAssemblySurfaceMaterial;
  exteriorMaterial?: AsukaAssemblySurfaceMaterial;
  metadata?: Record<string, unknown>;
}

export interface AsukaAssemblySurfaceTransform {
  center: AsukaAssemblyPlanPoint;
  rotationDegrees: number;
}

export interface AsukaAssemblySurfaceTransformInput {
  /** Absolute surface center in world coordinates. Omitted axes stay unchanged. */
  center?: Partial<AsukaAssemblyPlanPoint>;
  /** Absolute orientation of the first floor edge or the wall, in degrees. */
  rotationDegrees?: number;
  /** Optional explicit pivot used while rotating. Defaults to the surface center. */
  pivot?: AsukaAssemblyPlanPoint;
}

export interface AsukaAssemblyPlanDefinition {
  /** X/Z outline in world units. At least three points are required. */
  points: Array<{ x: number; z: number }>;
  floorY?: number;
  height?: number;
  wallThickness?: number;
  closed?: boolean;
  floorColor?: string;
  wallColor?: string;
  gridColor?: string;
  /** Grow the outline when connected components become larger. */
  autoExpand?: boolean;
  /** Empty space kept around the expanded component envelope. */
  autoExpandPadding?: number;
  /**
   * Explicit build-mode surfaces. When either collection is present, the 3D
   * adapter renders these surfaces instead of deriving a single room from
   * `points`.
   */
  floors?: AsukaAssemblyFloorSurface[];
  walls?: AsukaAssemblyWallSurface[];
  metadata?: Record<string, unknown>;
}

export type AsukaAssemblyBuildTool =
  | 'select'
  | 'room'
  | 'floor'
  | 'polygon-room'
  | 'polygon-floor'
  | 'wall'
  | 'erase';

export interface AsukaAssemblyBuildState {
  active: boolean;
  tool: AsukaAssemblyBuildTool | null;
  selectedSurfaceId: string | null;
  dragging: boolean;
  /** Number of committed vertices in the active free-outline draft. */
  draftPointCount: number;
}

export interface AsukaAssemblyInteractionState {
  /** Keep the guided construction session active while using the scene. */
  constructionModeActive: boolean;
  /** When enabled, a regular primary-button drag moves Assembly components. */
  moveToolActive: boolean;
}

export interface AsukaAssemblyStartDrawingInput {
  tool: AsukaAssemblyBuildTool;
  replace?: boolean;
  floorMaterial?: AsukaAssemblySurfaceMaterial;
  wallMaterial?: AsukaAssemblySurfaceMaterial;
  onCreate?: (surfaces: AsukaAssemblySurface[]) => void;
  onSelect?: (surface: AsukaAssemblySurface | null) => void;
  onCancel?: () => void;
}

export interface AsukaAssemblyGridSettings {
  enabled?: boolean;
  size?: number;
  subdivisions?: number;
  snapY?: boolean;
}

export type AsukaAssemblyDisplayUnit = 'm' | 'cm' | 'mm' | 'in';

export interface AsukaAssemblyMeasurementSettings {
  /** Unit shown by the built-in Assembly editor. Renderer APIs stay in world units. */
  displayUnit?: AsukaAssemblyDisplayUnit;
  /** Real meters represented by one renderer world unit. Defaults to 1. */
  worldUnitInMeters?: number;
}

export interface AsukaAssemblySnapSettings {
  enabled?: boolean;
  anchors?: boolean;
  boundaries?: boolean;
  grid?: boolean;
  distance?: number;
  rotation?: boolean;
  rotationStepDegrees?: number;
  autoConnect?: boolean;
}

export interface AsukaAssemblyCollisionSettings {
  enabled?: boolean;
  policy?: 'prevent' | 'warn' | 'allow';
  margin?: number;
  includeClearances?: boolean;
}

export interface AsukaAssemblyAdjacencySettings {
  enabled?: boolean;
  autoCorners?: boolean;
  /** Preserve established anchor connections after a parametric resize. */
  reflowOnResize?: boolean;
  /**
   * Decide how a connected group reacts when a parametric resize no longer
   * fits inside its room.
   *
   * `expand-plan` preserves component dimensions and grows the supporting
   * room. `shrink-components` preserves the room and proportionally scales the
   * connected group.
   */
  resizeOverflowPolicy?: 'expand-plan' | 'shrink-components';
}

/**
 * Editor-only visual helpers rendered by the 3D adapter.
 *
 * These helpers never participate in picking, collisions, exports or the BOM.
 * `selectedOnly` keeps detailed volumes on the selected component while still
 * exposing compatible target anchors on its neighbours.
 */
export interface AsukaAssemblyVisualizationSettings {
  /**
   * Keep the generated legacy room visible. Explicitly drawn floors and walls
   * are physical scene surfaces and remain visible independently.
   */
  environment?: boolean;
  enabled?: boolean;
  anchors?: boolean;
  collisions?: boolean;
  clearances?: boolean;
  connections?: boolean;
  /** Show real-world dimensions for the selected floor or wall. */
  dimensions?: boolean;
  selectedOnly?: boolean;
}

export interface AsukaAssemblySettings {
  grid?: AsukaAssemblyGridSettings;
  measurement?: AsukaAssemblyMeasurementSettings;
  snap?: AsukaAssemblySnapSettings;
  collision?: AsukaAssemblyCollisionSettings;
  adjacency?: AsukaAssemblyAdjacencySettings;
  visualization?: AsukaAssemblyVisualizationSettings;
}

export interface AsukaAssemblyConnectionEndpoint {
  componentId: string;
  anchorId: string;
}

export interface AsukaAssemblyConnection {
  id: string;
  source: AsukaAssemblyConnectionEndpoint;
  target: AsukaAssemblyConnectionEndpoint;
  connectorType?: string;
  createdAt?: string;
  metadata?: Record<string, unknown>;
}

export type AsukaAssemblyStackWidthAxis = 'auto' | 'x' | 'z';

/**
 * Persistent vertical relation between two component instances.
 *
 * The upper component is centered above the lower component and follows its
 * width while never exceeding `upperMaxWidth`. This is intentionally separate
 * from catalog anchors: it can link any two placed instances without requiring
 * model-specific connection points.
 */
export interface AsukaAssemblyStackLink {
  id: string;
  lowerComponentId: string;
  upperComponentId: string;
  /** Horizontal world axis used as the synchronized width. Defaults to `auto`. */
  widthAxis?: AsukaAssemblyStackWidthAxis;
  /** Vertical gap in world units. When omitted, the spacing at link creation is preserved. */
  gap?: number;
  /** Resize the upper component when the lower width changes. */
  syncWidth?: boolean;
  /** Maximum world width restored when the lower component grows again. */
  upperMaxWidth?: number;
  createdAt?: string;
  metadata?: Record<string, unknown>;
}

/**
 * Persisted assembly state. Catalog recipes remain on elements and are not
 * duplicated into every history snapshot.
 */
export interface AsukaAssemblyState {
  schemaVersion: 1;
  enabled?: boolean;
  plan?: AsukaAssemblyPlanDefinition;
  settings?: AsukaAssemblySettings;
  /** User-created reusable surface materials for this product/view. */
  materialLibrary?: AsukaAssemblyMaterialPreset[];
  /** Per-instance support choices persisted independently from catalog recipes. */
  componentPlacements?: Record<string, AsukaAssemblyPlacementDefinition>;
  /**
   * Runtime scale applied by the `shrink-components` overflow policy.
   * Persisting it lets subsequent resizes restore the natural component size
   * before calculating the next fit, avoiding cumulative scale drift.
   */
  componentAutoFitScales?: Record<string, number>;
  connections?: AsukaAssemblyConnection[];
  /** Instance-level vertical stacks, for example a wall cabinet above a base cabinet. */
  stackLinks?: AsukaAssemblyStackLink[];
  metadata?: Record<string, unknown>;
}

export interface AsukaAssemblyWorldAnchor {
  id: string;
  componentId: string;
  label?: string;
  role: AsukaAssemblyAnchorRole;
  position: AsukaAssemblyVector3;
  normal: AsukaAssemblyVector3;
  up: AsukaAssemblyVector3;
  provides: string[];
  accepts: string[];
  snapDistance: number;
  maxConnections: number;
  connectionCount: number;
  /** Rotate the moving component when this anchor connects. */
  alignRotation: boolean;
  metadata?: Record<string, unknown>;
}

export interface AsukaAssemblyComponent {
  id: string;
  catalogId?: string;
  label?: string;
  category?: string;
  tags: string[];
  transform: AsukaAssemblyTransform;
  bounds: AsukaAssemblyBounds;
  /** Runtime-only ratio between the neutral and current parametric geometry. */
  geometryScale?: AsukaAssemblyVector3;
  anchors: AsukaAssemblyWorldAnchor[];
  connectionIds: string[];
  definition?: AsukaAssemblyComponentDefinition;
  metadata?: Record<string, unknown>;
}

export type AsukaAssemblyIssueCode =
  | 'collision'
  | 'clearance'
  | 'outside-plan'
  | 'missing-connection'
  | 'missing-neighbor'
  | 'forbidden-neighbor'
  | 'gap-too-small'
  | 'gap-too-large'
  | 'incompatible-anchor'
  | 'anchor-capacity'
  | 'component-not-found'
  | string;

export interface AsukaAssemblyIssue {
  id: string;
  code: AsukaAssemblyIssueCode;
  severity: 'error' | 'warning';
  message: string;
  componentIds: string[];
  connectionId?: string;
  metadata?: Record<string, unknown>;
}

export interface AsukaAssemblyValidation {
  valid: boolean;
  issues: AsukaAssemblyIssue[];
  collisions: Array<{ componentId: string; otherComponentId: string; clearanceId?: string }>;
}

export interface AsukaAssemblyPlacementInput {
  position?: Partial<AsukaAssemblyVector3>;
  rotation?: Partial<AsukaAssemblyVector3>;
  delta?: Partial<AsukaAssemblyVector3>;
  snap?: boolean;
  snapAnchors?: boolean;
  snapBoundaries?: boolean;
  snapGrid?: boolean;
  snapSupport?: boolean;
  /** Overrides and persists the component's default support after a committed placement. */
  support?: AsukaAssemblySupportMode;
  /**
   * Constrains snapping to the active manipulation plane.
   * Horizontal movement snaps X/Z, vertical movement snaps Y.
   */
  axis?: 'horizontal' | 'vertical' | 'free';
  allowInvalid?: boolean;
  sourceAnchorId?: string;
  targetComponentId?: string;
  targetAnchorId?: string;
  metadata?: Record<string, unknown>;
}

export interface AsukaAssemblySnapResult {
  kind: 'anchor' | 'boundary' | 'corner' | 'grid' | 'support';
  distance: number;
  source?: AsukaAssemblyConnectionEndpoint;
  target?: AsukaAssemblyConnectionEndpoint;
  boundaryIndex?: number;
  position: AsukaAssemblyVector3;
  rotation?: AsukaAssemblyVector3;
}

export interface AsukaAssemblyPlacementResult {
  componentId: string;
  accepted: boolean;
  changed: boolean;
  requested: AsukaAssemblyTransform;
  applied: AsukaAssemblyTransform;
  snapped: boolean;
  snap?: AsukaAssemblySnapResult | null;
  validation: AsukaAssemblyValidation;
  createdConnection?: AsukaAssemblyConnection | null;
  metadata?: Record<string, unknown>;
}

export interface AsukaAssemblyMutationOptions {
  source?: string;
  commitHistory?: boolean;
  emitChange?: boolean;
}

export interface AsukaAssemblyBomLine {
  key: string;
  sku?: string;
  label: string;
  category?: string;
  quantity: number;
  unit: string;
  unitPrice: number;
  total: number;
  componentIds: string[];
  metadata?: Record<string, unknown>;
}

export interface AsukaAssemblyBom {
  currency?: string;
  lines: AsukaAssemblyBomLine[];
  subtotal: number;
  componentCount: number;
  generatedAt: string;
}

export interface AsukaAssemblyQuote {
  bom: AsukaAssemblyBom;
  adjustments: Array<{ id: string; label: string; amount: number }>;
  subtotal: number;
  total: number;
  currency?: string;
  generatedAt: string;
  metadata?: Record<string, unknown>;
}

export interface AsukaAssemblyQuoteOptions {
  currency?: string;
  adjustments?: Array<{ id: string; label: string; amount: number }>;
  metadata?: Record<string, unknown>;
}

export type AsukaAssemblyExportKind = 'bom-json' | 'bom-csv' | 'quote-json' | 'production-json';

export interface AsukaAssemblyExportOptions extends AsukaAssemblyQuoteOptions {
  filename?: string;
}
