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

/** Distance between two points. */
export function distance(a: Point, b: Point): number {
  const dx = b.x - a.x;
  const dy = b.y - a.y;
  return Math.sqrt(dx * dx + dy * dy);
}

/** Snap a value to the nearest multiple of `grid`. */
export function snapToGrid(value: number, grid: number): number {
  return Math.round(value / grid) * grid;
}

/** Snap a point to the nearest grid intersection. */
export function snapPointToGrid(point: Point, grid: number): Point {
  return {
    x: snapToGrid(point.x, grid),
    y: snapToGrid(point.y, grid),
  };
}

/** Clamp a number between min and max. */
export function clamp(value: number, min: number, max: number): number {
  return Math.max(min, Math.min(max, value));
}

/**
 * Project a point onto a line segment and return the closest point on the segment
 * plus the parameter t (0..1) along the segment.
 */
export function projectPointOntoSegment(
  point: Point,
  segStart: Point,
  segEnd: Point,
): { projected: Point; t: number; distance: number } {
  const dx = segEnd.x - segStart.x;
  const dy = segEnd.y - segStart.y;
  const lengthSq = dx * dx + dy * dy;

  if (lengthSq === 0) {
    return { projected: segStart, t: 0, distance: distance(point, segStart) };
  }

  const t = clamp(
    ((point.x - segStart.x) * dx + (point.y - segStart.y) * dy) / lengthSq,
    0,
    1,
  );

  const projected: Point = {
    x: segStart.x + t * dx,
    y: segStart.y + t * dy,
  };

  return { projected, t, distance: distance(point, projected) };
}

/** Compute the angle (in radians) of a segment from start to end. */
export function segmentAngle(start: Point, end: Point): number {
  return Math.atan2(end.y - start.y, end.x - start.x);
}

/** Check if a point is inside a polygon (ray casting). */
export function pointInPolygon(point: Point, polygon: readonly Point[]): boolean {
  let inside = false;
  const n = polygon.length;
  for (let i = 0, j = n - 1; i < n; j = i++) {
    const xi = polygon[i].x;
    const yi = polygon[i].y;
    const xj = polygon[j].x;
    const yj = polygon[j].y;

    const intersect =
      yi > point.y !== yj > point.y &&
      point.x < ((xj - xi) * (point.y - yi)) / (yj - yi) + xi;

    if (intersect) {
      inside = !inside;
    }
  }
  return inside;
}

/**
 * Compute bounding box of a set of points.
 * Returns { minX, minY, maxX, maxY }.
 */
export function boundingBox(points: readonly Point[]): {
  minX: number;
  minY: number;
  maxX: number;
  maxY: number;
} {
  if (points.length === 0) {
    return { minX: 0, minY: 0, maxX: 0, maxY: 0 };
  }
  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 { minX, minY, maxX, maxY };
}

/** Check if two axis-aligned rectangles overlap. */
export function rectsOverlap(
  a: { x: number; y: number; width: number; height: number },
  b: { x: number; y: number; width: number; height: number },
): boolean {
  return (
    a.x < b.x + b.width &&
    a.x + a.width > b.x &&
    a.y < b.y + b.height &&
    a.y + a.height > b.y
  );
}

/**
 * Compute the area of a polygon using the Shoelace formula.
 * Points should be ordered (CW or CCW); the result is always positive.
 */
export function polygonArea(points: readonly Point[]): number {
  const n = points.length;
  if (n < 3) return 0;

  let sum = 0;
  for (let i = 0; i < n; i++) {
    const current = points[i];
    const next = points[(i + 1) % n];
    sum += current.x * next.y - next.x * current.y;
  }
  return Math.abs(sum) / 2;
}

/**
 * Compute the perimeter of a polygon (sum of all edge lengths).
 */
export function polygonPerimeter(points: readonly Point[]): number {
  const n = points.length;
  if (n < 2) return 0;

  let perimeter = 0;
  for (let i = 0; i < n; i++) {
    perimeter += distance(points[i], points[(i + 1) % n]);
  }
  return perimeter;
}

/**
 * Compute the centroid of a polygon.
 * Returns the geometric center (average of vertices for simple polygons).
 */
export function polygonCentroid(points: readonly Point[]): Point {
  const n = points.length;
  if (n === 0) return { x: 0, y: 0 };
  if (n === 1) return points[0];
  if (n === 2) return { x: (points[0].x + points[1].x) / 2, y: (points[0].y + points[1].y) / 2 };

  let cx = 0;
  let cy = 0;
  let areaSum = 0;

  for (let i = 0; i < n; i++) {
    const current = points[i];
    const next = points[(i + 1) % n];
    const cross = current.x * next.y - next.x * current.y;
    cx += (current.x + next.x) * cross;
    cy += (current.y + next.y) * cross;
    areaSum += cross;
  }

  const area6 = 3 * areaSum; // 6A / 2 = 3 * sum
  if (Math.abs(area6) < 1e-10) {
    // Degenerate polygon — fall back to average of vertices
    let sx = 0;
    let sy = 0;
    for (const p of points) {
      sx += p.x;
      sy += p.y;
    }
    return { x: sx / n, y: sy / n };
  }

  return { x: cx / area6, y: cy / area6 };
}

/** Generate a simple unique ID for local use. */
export function generateLocalId(): string {
  return `local-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
}
