import type { Point, Wall, WallOpening, ElectricalItem, FurnitureItem } from '@house-plan-maker/shared';
import { rotatedAnchorOffsetToCenter } from '@house-plan-maker/shared';
import type { DragState } from '../types';
import { distance } from '../utils/geometry';
import { findNearestWall } from '../utils/wallUtils';

function furnitureCenter(item: FurnitureItem): { cx: number; cy: number } {
  const offset = rotatedAnchorOffsetToCenter(
    item.positionAnchor,
    item.width,
    item.depth,
    item.rotation,
  );
  return { cx: item.x + offset.dx, cy: item.y + offset.dy };
}

/** Hit-test radius in meters for selecting elements. */
const HIT_RADIUS = 0.15;

export interface SelectToolState {
  readonly drag: DragState | null;
}

export function createInitialSelectState(): SelectToolState {
  return { drag: null };
}

/**
 * Find which element (if any) was clicked.
 * Returns the element ID and type, or null.
 */
export function hitTest(
  worldPoint: Point,
  openings: readonly WallOpening[],
  walls: readonly Wall[],
  electricalItems: readonly ElectricalItem[],
  furnitureItems: readonly FurnitureItem[],
): { id: string; type: 'opening' | 'wall' | 'electrical' | 'furniture' } | null {
  // Check openings first (they're on walls)
  for (const opening of openings) {
    const wall = walls.find((w) => w.id === opening.wallId);
    if (!wall) continue;

    const wallLen = distance(
      { x: wall.startX, y: wall.startY },
      { x: wall.endX, y: wall.endY },
    );
    if (wallLen === 0) continue;

    const dx = (wall.endX - wall.startX) / wallLen;
    const dy = (wall.endY - wall.startY) / wallLen;

    const centerX = wall.startX + dx * opening.positionAlongWall;
    const centerY = wall.startY + dy * opening.positionAlongWall;

    const dist = distance(worldPoint, { x: centerX, y: centerY });
    if (dist < Math.max(opening.width / 2, HIT_RADIUS)) {
      return { id: opening.id, type: 'opening' };
    }
  }

  // Check electrical items
  for (const item of electricalItems) {
    const dist = distance(worldPoint, { x: item.x, y: item.y });
    if (dist < HIT_RADIUS) {
      return { id: item.id, type: 'electrical' };
    }
  }

  // Check furniture items (rotation-aware: transform point into item's local space)
  for (const item of furnitureItems) {
    const { cx, cy } = furnitureCenter(item);
    // Translate point relative to item center
    const dx = worldPoint.x - cx;
    const dy = worldPoint.y - cy;
    // Rotate point by -rotation to get local coordinates
    const rad = -(item.rotation * Math.PI) / 180;
    const cos = Math.cos(rad);
    const sin = Math.sin(rad);
    const localX = dx * cos - dy * sin;
    const localY = dx * sin + dy * cos;
    // Check if local point is within the unrotated rectangle
    if (
      Math.abs(localX) <= item.width / 2 &&
      Math.abs(localY) <= item.depth / 2
    ) {
      return { id: item.id, type: 'furniture' };
    }
  }

  // Check walls
  const nearestWall = findNearestWall(worldPoint, walls);
  if (nearestWall && nearestWall.distance < Math.max(nearestWall.wall.thickness, HIT_RADIUS)) {
    return { id: nearestWall.wall.id, type: 'wall' };
  }

  return null;
}

/**
 * Compute the selection rectangle from drag start to current position.
 * Returns { x, y, width, height } in world coordinates, normalized so width/height are positive.
 */
export function selectionRect(
  start: Point,
  current: Point,
): { x: number; y: number; width: number; height: number } {
  const x = Math.min(start.x, current.x);
  const y = Math.min(start.y, current.y);
  const width = Math.abs(current.x - start.x);
  const height = Math.abs(current.y - start.y);
  return { x, y, width, height };
}

/**
 * Find all element IDs inside a selection rectangle.
 */
export function elementsInRect(
  rect: { x: number; y: number; width: number; height: number },
  openings: readonly WallOpening[],
  walls: readonly Wall[],
  electricalItems: readonly ElectricalItem[],
  furnitureItems: readonly FurnitureItem[],
): ReadonlySet<string> {
  const ids = new Set<string>();

  for (const opening of openings) {
    const wall = walls.find((w) => w.id === opening.wallId);
    if (!wall) continue;

    const wallLen = distance(
      { x: wall.startX, y: wall.startY },
      { x: wall.endX, y: wall.endY },
    );
    if (wallLen === 0) continue;

    const dx = (wall.endX - wall.startX) / wallLen;
    const dy = (wall.endY - wall.startY) / wallLen;
    const cx = wall.startX + dx * opening.positionAlongWall;
    const cy = wall.startY + dy * opening.positionAlongWall;

    if (
      cx >= rect.x &&
      cx <= rect.x + rect.width &&
      cy >= rect.y &&
      cy <= rect.y + rect.height
    ) {
      ids.add(opening.id);
    }
  }

  for (const item of electricalItems) {
    if (
      item.x >= rect.x &&
      item.x <= rect.x + rect.width &&
      item.y >= rect.y &&
      item.y <= rect.y + rect.height
    ) {
      ids.add(item.id);
    }
  }

  for (const item of furnitureItems) {
    // Use the bounding-box centre (anchor-aware) for selection-rect containment
    const { cx, cy } = furnitureCenter(item);
    if (
      cx >= rect.x &&
      cx <= rect.x + rect.width &&
      cy >= rect.y &&
      cy <= rect.y + rect.height
    ) {
      ids.add(item.id);
    }
  }

  return ids;
}

/**
 * Compute the bounding box of all selected elements (for resize handles).
 */
export function selectedBoundingBox(
  selectedIds: ReadonlySet<string>,
  openings: readonly WallOpening[],
  walls: readonly Wall[],
  electricalItems: readonly ElectricalItem[],
  furnitureItems: readonly FurnitureItem[],
): { x: number; y: number; width: number; height: number } | null {
  const points: Point[] = [];

  for (const id of selectedIds) {
    const opening = openings.find((o) => o.id === id);
    if (opening) {
      const wall = walls.find((w) => w.id === opening.wallId);
      if (wall) {
        const wallLen = distance(
          { x: wall.startX, y: wall.startY },
          { x: wall.endX, y: wall.endY },
        );
        if (wallLen > 0) {
          const dx = (wall.endX - wall.startX) / wallLen;
          const dy = (wall.endY - wall.startY) / wallLen;
          const cx = wall.startX + dx * opening.positionAlongWall;
          const cy = wall.startY + dy * opening.positionAlongWall;
          // Compute the four corners of the oriented opening rectangle
          // (along-wall = opening.width, perpendicular = wall.thickness),
          // then push them all so the resulting AABB is the correct
          // world-axis box for the rotated symbol. The previous version
          // used `opening.width/2` on both axes, producing a square that
          // ballooned out from the wall on long windows.
          const halfW = opening.width / 2;
          const halfT = wall.thickness / 2;
          // Wall normal (perpendicular to direction).
          const nx = -dy;
          const ny = dx;
          for (const su of [-halfW, halfW]) {
            for (const sv of [-halfT, halfT]) {
              points.push({
                x: cx + dx * su + nx * sv,
                y: cy + dy * su + ny * sv,
              });
            }
          }
        }
      }
      continue;
    }

    const elec = electricalItems.find((e) => e.id === id);
    if (elec) {
      points.push({ x: elec.x - 0.05, y: elec.y - 0.05 });
      points.push({ x: elec.x + 0.05, y: elec.y + 0.05 });
      continue;
    }

    const furn = furnitureItems.find((f) => f.id === id);
    if (furn) {
      // Selection overlay must enclose the ROTATED visual — otherwise a
      // thin rotated item (e.g. a curtain rotated 90°) would show a
      // horizontal dashed box while the visible rectangle is vertical.
      // Compute the world-axis AABB of the rotated `width × depth`
      // rectangle centred on the item's (rotated) centre.
      const { cx, cy } = furnitureCenter(furn);
      const rad = (furn.rotation * Math.PI) / 180;
      const cos = Math.abs(Math.cos(rad));
      const sin = Math.abs(Math.sin(rad));
      const halfW = (furn.width * cos + furn.depth * sin) / 2;
      const halfD = (furn.width * sin + furn.depth * cos) / 2;
      points.push({ x: cx - halfW, y: cy - halfD });
      points.push({ x: cx + halfW, y: cy + halfD });
    }
  }

  if (points.length === 0) return null;

  let minX = Infinity;
  let minY = Infinity;
  let maxX = -Infinity;
  let maxY = -Infinity;

  for (const p of points) {
    if (p.x < minX) minX = p.x;
    if (p.y < minY) minY = p.y;
    if (p.x > maxX) maxX = p.x;
    if (p.y > maxY) maxY = p.y;
  }

  return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
}
