// ── Wall ──

export const WALL_DIRECTIONS = ['NORTH', 'SOUTH', 'EAST', 'WEST', 'OTHER'] as const;
export type WallDirection = (typeof WALL_DIRECTIONS)[number];

export interface Wall {
  readonly id: string;
  readonly roomId: string;
  readonly startX: number;
  readonly startY: number;
  readonly endX: number;
  readonly endY: number;
  readonly thickness: number;
  readonly direction: WallDirection;
}

export interface CreateWallDto {
  readonly startX: number;
  readonly startY: number;
  readonly endX: number;
  readonly endY: number;
  readonly thickness?: number;
  readonly direction?: WallDirection;
}

// ── WallOpening ──

export const OPENING_TYPES = ['DOOR', 'WINDOW'] as const;
export type OpeningType = (typeof OPENING_TYPES)[number];

export const DOOR_OPEN_DIRECTIONS = ['LEFT', 'RIGHT', 'INWARD', 'OUTWARD'] as const;
export type DoorOpenDirection = (typeof DOOR_OPEN_DIRECTIONS)[number];

export interface WallOpening {
  readonly id: string;
  readonly roomId: string;
  readonly wallId: string;
  readonly type: OpeningType;
  /**
   * Position along the wall (meters from wall start). Interpreted relative to
   * `positionAnchor.horizontal`: `left` = left edge, `middle` = center,
   * `right` = right edge of the opening's bounding box along the wall.
   */
  readonly positionAlongWall: number;
  readonly width: number;
  readonly height: number;
  /**
   * Elevation from the floor (meters). Interpreted relative to
   * `positionAnchor.vertical`: `top` = top edge, `middle` = center,
   * `bottom` = bottom edge of the opening's bounding box.
   */
  readonly elevationFromFloor: number;
  readonly openDirection: DoorOpenDirection;
  /** Defines what (positionAlongWall, elevationFromFloor) refers to on the opening's bounding box. */
  readonly positionAnchor: PositionAnchor;
  /**
   * Window grid subdivision. `gridCols` is the number of vertical panes
   * across the window (so N-1 internal mullions), `gridRows` is the
   * number of horizontal panes stacked vertically. Both default to 2 for
   * newly created windows (classic 2×2 cross). Ignored for doors.
   */
  readonly gridCols: number;
  readonly gridRows: number;
  /**
   * Window reveal (Russian: откос) depth in meters. Defines the depth of
   * the angled jamb/sill panels framing the window inside the wall opening.
   * `0` means no visible reveal — the window appears flush with the wall
   * surface. Ignored for doors. Renderers should clamp the value so it does
   * not exceed half the wall thickness.
   */
  readonly slopeDepth: number;
  /**
   * Frame member thickness in meters (used for both doors and windows).
   * Controls the visual width of the frame posts/bars in the 3D view.
   * Defaults to 0.03 m if unset.
   */
  readonly frameThickness: number;
}

export interface CreateWallOpeningDto {
  readonly wallId: string;
  readonly type: OpeningType;
  readonly positionAlongWall: number;
  readonly width: number;
  readonly height: number;
  readonly elevationFromFloor?: number;
  readonly openDirection?: DoorOpenDirection;
  readonly positionAnchor?: PositionAnchor;
  readonly gridCols?: number;
  readonly gridRows?: number;
  readonly slopeDepth?: number;
  readonly frameThickness?: number;
}

export interface UpdateWallOpeningDto {
  readonly type?: OpeningType;
  readonly positionAlongWall?: number;
  readonly width?: number;
  readonly height?: number;
  readonly elevationFromFloor?: number;
  readonly openDirection?: DoorOpenDirection;
  readonly positionAnchor?: PositionAnchor;
  readonly gridCols?: number;
  readonly gridRows?: number;
  readonly slopeDepth?: number;
  readonly frameThickness?: number;
}

// ── Position anchor ──
//
// Describes which point on an item's bounding box its (x, y) refers to.
// Applies to electrical items and furniture. `middle/middle` is the default
// for newly created items; legacy data may use other anchors (furniture
// historically used `left/top` and electrical historically used `middle/middle`).

export const HORIZONTAL_ANCHORS = ['left', 'middle', 'right'] as const;
export type HorizontalAnchor = (typeof HORIZONTAL_ANCHORS)[number];

export const VERTICAL_ANCHORS = ['top', 'middle', 'bottom'] as const;
export type VerticalAnchor = (typeof VERTICAL_ANCHORS)[number];

export interface PositionAnchor {
  readonly horizontal: HorizontalAnchor;
  readonly vertical: VerticalAnchor;
}

export const DEFAULT_POSITION_ANCHOR: PositionAnchor = {
  horizontal: 'middle',
  vertical: 'middle',
};

/**
 * Computes the offset (in local item coordinates, before rotation) from the
 * anchored point to the bounding-box center, given the box dimensions.
 *
 * Convention: local +x = right, local +y = down (top-left origin).
 * Returns the vector that, when added to the anchored (x, y), yields the
 * geometric center of the bounding box.
 *
 * Note: this helper IGNORES rotation. For items where the anchor should
 * track the rotated visual (e.g. "left" should always mean the leftmost
 * edge of the rendered shape), use {@link rotatedAnchorOffsetToCenter}.
 */
export function anchorOffsetToCenter(
  anchor: PositionAnchor,
  width: number,
  height: number,
): { readonly dx: number; readonly dy: number } {
  const dx =
    anchor.horizontal === 'left'
      ? width / 2
      : anchor.horizontal === 'right'
        ? -width / 2
        : 0;
  const dy =
    anchor.vertical === 'top'
      ? height / 2
      : anchor.vertical === 'bottom'
        ? -height / 2
        : 0;
  return { dx, dy };
}

/**
 * Computes the offset from the anchored point to the bounding-box center,
 * **respecting rotation**. The horizontal/vertical anchor labels refer to
 * the rotated visual's world axes — e.g. with `anchor.horizontal = 'left'`
 * the (x, y) point is the leftmost edge of the rotated rectangle as seen
 * in world space, regardless of how the underlying width/depth are oriented
 * in the item's local frame.
 *
 * Math: a `width × height` rectangle rotated by `rotationDeg` produces a
 * world-axis-aligned bounding box with half-extents
 *   `halfX = |w/2 · cos r| + |d/2 · sin r|`
 *   `halfY = |w/2 · sin r| + |d/2 · cos r|`
 * For each anchor, the center sits at ±halfX or ±halfY from the anchored
 * point along the world axis.
 *
 * Reduces to {@link anchorOffsetToCenter} when `rotationDeg = 0`. For the
 * `middle/middle` anchor (the default for newly placed items) the offset is
 * always `(0, 0)` regardless of rotation.
 */
export function rotatedAnchorOffsetToCenter(
  anchor: PositionAnchor,
  width: number,
  height: number,
  rotationDeg: number,
): { readonly dx: number; readonly dy: number } {
  const r = (rotationDeg * Math.PI) / 180;
  const cos = Math.cos(r);
  const sin = Math.sin(r);
  const halfW = width / 2;
  const halfH = height / 2;
  // World-axis half-extents of the rotated rectangle. Both terms get
  // absolute-valued so the result is the support along the +x / +y axes
  // regardless of rotation quadrant.
  const halfX = Math.abs(halfW * cos) + Math.abs(halfH * sin);
  const halfY = Math.abs(halfW * sin) + Math.abs(halfH * cos);

  const dx =
    anchor.horizontal === 'left'
      ? halfX
      : anchor.horizontal === 'right'
        ? -halfX
        : 0;
  const dy =
    anchor.vertical === 'top'
      ? halfY
      : anchor.vertical === 'bottom'
        ? -halfY
        : 0;
  return { dx, dy };
}

// ── ElectricalItem ──

export const ELECTRICAL_TYPES = [
  'OUTLET',
  'SWITCH',
  'JUNCTION_BOX',
  'LIGHT_CEILING',
  'LIGHT_WALL',
  'CABLE_ROUTE',
  'WALL_CABLE',
] as const;
export type ElectricalType = (typeof ELECTRICAL_TYPES)[number];

export const OUTLET_DIRECTIONS = ['horizontal', 'vertical'] as const;
export type OutletDirection = (typeof OUTLET_DIRECTIONS)[number];

export const AC_UNIT_STYLES = ['generic', 'lg'] as const;
export type AcUnitStyle = (typeof AC_UNIT_STYLES)[number];

export const WALL_LIGHT_STYLES = [
  'classic',
  'pendant-globe',
  'sconce-up',
  'sconce-down',
  'wood-cube',
] as const;
export type WallLightStyle = (typeof WALL_LIGHT_STYLES)[number];

export interface ElectricalItem {
  readonly id: string;
  readonly roomId: string;
  readonly type: ElectricalType;
  readonly x: number;
  readonly y: number;
  readonly wallId: string | null;
  readonly elevationFromFloor: number | null;
  readonly rotation: number;
  /**
   * For OUTLET: number of individual outlets in the group (>= 1). Renderers
   * should draw `count` adjacent outlet boundaries sized by the room's
   * `outletWidth`/`outletHeight`. Ignored for other electrical types.
   */
  readonly count: number;
  /** Defines what the (x, y) coordinates refer to on the bounding box. */
  readonly positionAnchor: PositionAnchor;
  /**
   * User-supplied display name for this item (e.g. "Behind TV", "Counter"),
   * used in the projection view and properties panel as an override for the
   * generic type code. `null` means "use the default label from the symbol
   * definition" — UI components should fall back to that, not write it back.
   */
  readonly label: string | null;
  readonly metadata: Record<string, unknown> | null;
}

export interface CreateElectricalItemDto {
  readonly type: ElectricalType;
  readonly x: number;
  readonly y: number;
  readonly wallId?: string | null;
  readonly elevationFromFloor?: number | null;
  readonly rotation?: number;
  readonly count?: number;
  readonly positionAnchor?: PositionAnchor;
  readonly label?: string | null;
  readonly metadata?: Record<string, unknown> | null;
}

export interface UpdateElectricalItemDto {
  readonly type?: ElectricalType;
  readonly x?: number;
  readonly y?: number;
  readonly wallId?: string | null;
  readonly elevationFromFloor?: number | null;
  readonly rotation?: number;
  readonly count?: number;
  readonly positionAnchor?: PositionAnchor;
  readonly label?: string | null;
  readonly metadata?: Record<string, unknown> | null;
}

// ── FurnitureTexture ──

export const FURNITURE_TEXTURES = [
  'NONE',
  'WOOD_LIGHT',
  'WOOD_MEDIUM',
  'WOOD_DARK',
  'WOOD_HERRINGBONE',
  'OAK_NATURAL',
  'LAMINATE',
  'CONCRETE',
] as const;
export type FurnitureTexture = (typeof FURNITURE_TEXTURES)[number];

/** Furniture types that support surface texture selection. */
export const TEXTURABLE_FURNITURE: readonly FurnitureType[] = [
  'DESK',
  'TABLE',
  'SHELF',
  'NIGHTSTAND',
  'DRESSER',
  'DRESSING_TABLE',
  'BOOKCASE',
  'WARDROBE',
  'CHAIR',
  'BED',
  'CRIB',
];

// ── FurnitureItem ──

export const FURNITURE_TYPES = [
  'BED',
  'CRIB',
  'DESK',
  'WARDROBE',
  'SOFA',
  'TABLE',
  'CHAIR',
  'OFFICE_CHAIR',
  'SHELF',
  'NIGHTSTAND',
  'DRESSER',
  'DRESSING_TABLE',
  'BOOKCASE',
  'TV',
  'PC_TOWER',
  'AC_UNIT',
  'RADIATOR',
  'WALL_COLLAGE',
  'CURTAIN',
  'PLANT',
  'MIRROR',
  'DIGITAL_PIANO',
  'SPEAKER',
  'SCRATCHING_POST',
  'OTHER',
] as const;
export type FurnitureType = (typeof FURNITURE_TYPES)[number];

export interface FurnitureItem {
  readonly id: string;
  readonly roomId: string;
  readonly type: FurnitureType;
  readonly x: number;
  readonly y: number;
  readonly width: number;
  readonly depth: number;
  readonly height: number;
  readonly rotation: number;
  readonly elevationFromFloor: number;
  readonly label: string | null;
  /** Defines what the (x, y) coordinates refer to on the bounding box. */
  readonly positionAnchor: PositionAnchor;
  /** When true, the wall projection view shows dimension/offset overlays for this item. */
  readonly showProjection?: boolean;
  /** Render opacity in the [0, 1] range (1 = fully opaque). */
  readonly opacity?: number;
  /**
   * Type-specific extension bag for properties that don't deserve their own
   * column. Keys are domain-specific — e.g. CURTAIN uses `openAmount: number`
   * (0=closed, 1=fully open) and `fabricColor: string` (hex). Persisted as
   * JSON in the database.
   */
  readonly metadata?: Record<string, unknown> | null;
}

export interface CreateFurnitureItemDto {
  readonly type: FurnitureType;
  readonly x: number;
  readonly y: number;
  readonly width: number;
  readonly depth: number;
  readonly height: number;
  readonly rotation?: number;
  readonly elevationFromFloor?: number;
  readonly label?: string | null;
  readonly positionAnchor?: PositionAnchor;
  readonly showProjection?: boolean;
  readonly opacity?: number;
  readonly metadata?: Record<string, unknown> | null;
}

export interface UpdateFurnitureItemDto {
  readonly type?: FurnitureType;
  readonly x?: number;
  readonly y?: number;
  readonly width?: number;
  readonly depth?: number;
  readonly height?: number;
  readonly rotation?: number;
  readonly elevationFromFloor?: number;
  readonly label?: string | null;
  readonly positionAnchor?: PositionAnchor;
  readonly showProjection?: boolean;
  readonly opacity?: number;
  readonly metadata?: Record<string, unknown> | null;
}

// ── Annotation ──

export interface Annotation {
  readonly id: string;
  readonly roomId: string;
  readonly x: number;
  readonly y: number;
  readonly text: string;
  readonly fontSize?: number;
  readonly color?: string;
  /** If set, this annotation follows the item with this ID. x,y become offsets from the item. */
  readonly attachedToId?: string;
  /**
   * Optional offset in projection (wall-elevation) space, in meters.
   * `projectionOffsetX` is along the wall (positive = right of anchor) and
   * `projectionOffsetY` is in projection-pixel direction (positive = down).
   * Stored separately from `x`/`y` so dragging in the elevation view does not
   * corrupt the floor-plan offset.
   */
  readonly projectionOffsetX?: number;
  readonly projectionOffsetY?: number;
}

// ── Batch sync DTOs ──

export interface BatchSyncOpeningsDto {
  readonly create: readonly CreateWallOpeningDto[];
  readonly update: readonly { readonly id: string; readonly data: UpdateWallOpeningDto }[];
  readonly delete: readonly string[];
}

export interface BatchSyncElectricalDto {
  readonly create: readonly CreateElectricalItemDto[];
  readonly update: readonly { readonly id: string; readonly data: UpdateElectricalItemDto }[];
  readonly delete: readonly string[];
}

export interface BatchSyncFurnitureDto {
  readonly create: readonly CreateFurnitureItemDto[];
  readonly update: readonly { readonly id: string; readonly data: UpdateFurnitureItemDto }[];
  readonly delete: readonly string[];
}

export interface CreateAnnotationDto {
  readonly x: number;
  readonly y: number;
  readonly text: string;
  readonly fontSize?: number;
  readonly color?: string;
  readonly attachedToId?: string | null;
  readonly projectionOffsetX?: number | null;
  readonly projectionOffsetY?: number | null;
}

export interface UpdateAnnotationDto {
  readonly x?: number;
  readonly y?: number;
  readonly text?: string;
  readonly fontSize?: number;
  readonly color?: string;
  readonly attachedToId?: string | null;
  readonly projectionOffsetX?: number | null;
  readonly projectionOffsetY?: number | null;
}

export interface BatchSyncAnnotationsDto {
  readonly create: readonly CreateAnnotationDto[];
  readonly update: readonly { readonly id: string; readonly data: UpdateAnnotationDto }[];
  readonly delete: readonly string[];
}
