import type { Point, Wall, WallOpening } from '@house-plan-maker/shared';

/**
 * Coordinate conversion: 2D editor (X-right, Y-down) -> Three.js (X-right, Y-up, Z-forward).
 * 2D (x, y) -> 3D (x, 0, y) for floor plane; Y axis is height.
 */
export function toThreeCoords(point: Point): [number, number, number] {
  return [point.x, 0, point.y];
}

/** Wall direction vector and length in 2D. */
export function wallVector(wall: Wall): { dx: number; dy: number; length: number } {
  const dx = wall.endX - wall.startX;
  const dy = wall.endY - wall.startY;
  const length = Math.sqrt(dx * dx + dy * dy);
  return { dx, dy, length };
}

/** Wall perpendicular normal (unit vector pointing "left" of wall direction). */
export function wallNormal(wall: Wall): { nx: number; ny: number } {
  const { dx, dy, length } = wallVector(wall);
  if (length === 0) return { nx: 0, ny: 0 };
  // Perpendicular: rotate 90 degrees CCW
  return { nx: -dy / length, ny: dx / length };
}

/** Opening position on a wall: start and end distances along wall from wall start. */
export interface OpeningSlice {
  readonly opening: WallOpening;
  readonly startAlongWall: number;
  readonly endAlongWall: number;
}

/**
 * Sort openings on a wall by position and compute their start/end distances.
 */
export function getOpeningSlices(
  wall: Wall,
  openings: readonly WallOpening[],
): readonly OpeningSlice[] {
  const wallOpenings = openings.filter((o) => o.wallId === wall.id);

  return wallOpenings
    .map((opening) => ({
      opening,
      startAlongWall: opening.positionAlongWall - opening.width / 2,
      endAlongWall: opening.positionAlongWall + opening.width / 2,
    }))
    .sort((a, b) => a.startAlongWall - b.startAlongWall);
}

/**
 * A solid segment of a wall (between openings or at wall ends).
 * Heights define the vertical range of this segment.
 */
export interface WallSegment {
  readonly startAlongWall: number;
  readonly endAlongWall: number;
  readonly bottomY: number;
  readonly topY: number;
}

/**
 * Split a wall into solid segments around openings.
 * Returns segments that should be rendered as solid wall geometry.
 *
 * For each opening, we create:
 * - A segment above the opening (from opening top to wall top)
 * - A segment below the opening (from floor to opening bottom) — only if elevationFromFloor > 0
 * - Solid wall to the left and right of the opening
 *
 * The segments that touch the physical wall ends (start=0 or end=length) are
 * extended along the wall's own axis by the full `thickness`. Combined with
 * the outward perpendicular shift applied in WallMesh, this is the exact
 * amount needed for two perpendicular walls' boxes to share their corner
 * cube without leaving a vertical gap at the inside or outside corner.
 *
 * For non-90 corners this creates a small overlap rather than a gap, which
 * is the right tradeoff: overlap is invisible (same material, no z-fighting
 * artifacts because the meshes are coplanar at the corner), gap is not.
 *
 * Inner segments (those bordering an opening) are not extended — extending
 * them would close the opening.
 */
export function splitWallAroundOpenings(
  wall: Wall,
  openings: readonly WallOpening[],
  wallHeight: number,
): readonly WallSegment[] {
  const slices = getOpeningSlices(wall, openings);
  const { length } = wallVector(wall);
  const ext = wall.thickness;

  if (slices.length === 0) {
    return [{ startAlongWall: -ext, endAlongWall: length + ext, bottomY: 0, topY: wallHeight }];
  }

  const segments: WallSegment[] = [];

  // Full-height segments between openings
  let cursor = 0;
  for (let i = 0; i < slices.length; i++) {
    const slice = slices[i];
    // Solid wall before this opening
    if (slice.startAlongWall > cursor) {
      segments.push({
        // Extend the start outward only if this segment touches the wall start.
        startAlongWall: cursor === 0 ? -ext : cursor,
        endAlongWall: slice.startAlongWall,
        bottomY: 0,
        topY: wallHeight,
      });
    }

    // Segment below the opening (sill)
    if (slice.opening.elevationFromFloor > 0) {
      segments.push({
        startAlongWall: slice.startAlongWall,
        endAlongWall: slice.endAlongWall,
        bottomY: 0,
        topY: slice.opening.elevationFromFloor,
      });
    }

    // Segment above the opening (lintel to ceiling)
    const openingTop = slice.opening.elevationFromFloor + slice.opening.height;
    if (openingTop < wallHeight) {
      segments.push({
        startAlongWall: slice.startAlongWall,
        endAlongWall: slice.endAlongWall,
        bottomY: openingTop,
        topY: wallHeight,
      });
    }

    cursor = slice.endAlongWall;
  }

  // Solid wall after last opening
  if (cursor < length) {
    segments.push({
      startAlongWall: cursor,
      // Extend the end outward — this segment touches the wall end.
      endAlongWall: length + ext,
      bottomY: 0,
      topY: wallHeight,
    });
  }

  return segments;
}

/**
 * Compute 3D position along a wall at a given distance from wall start.
 * Returns [x, z] in Three.js floor plane coords.
 */
export function positionAlongWall3D(
  wall: Wall,
  distanceAlongWall: number,
): [number, number] {
  const { dx, dy, length } = wallVector(wall);
  if (length === 0) return [wall.startX, wall.startY];
  const t = distanceAlongWall / length;
  return [wall.startX + dx * t, wall.startY + dy * t];
}

/**
 * Compute the center position of a wall segment in 3D space.
 */
export function wallSegmentCenter3D(
  wall: Wall,
  segment: WallSegment,
): [number, number, number] {
  const midAlongWall = (segment.startAlongWall + segment.endAlongWall) / 2;
  const [x, z] = positionAlongWall3D(wall, midAlongWall);
  const y = (segment.bottomY + segment.topY) / 2;
  return [x, y, z];
}

/**
 * Get the rotation angle of a wall around the Y axis (for Three.js).
 * Returns angle in radians.
 */
export function wallRotationY(wall: Wall): number {
  const { dx, dy } = wallVector(wall);
  return -Math.atan2(dy, dx);
}
