import type { Point } from './types';
import { cellKey } from './grid';

/** Wall/door inputs (image-space px). Doors block sight only while closed. */
export interface VisionWalls {
  walls: { points: Point[] }[];
  doors?: { a: Point; b: Point; open: boolean }[];
}

export type Segment = [Point, Point];

/** Flatten walls (polylines) + closed doors into sight-blocking segments. */
export function blockingSegments({ walls, doors = [] }: VisionWalls): Segment[] {
  const segs: Segment[] = [];
  for (const w of walls) {
    for (let i = 1; i < w.points.length; i++) segs.push([w.points[i - 1]!, w.points[i]!]);
  }
  for (const d of doors) if (!d.open) segs.push([d.a, d.b]);
  return segs;
}

/** Orientation sign of the triplet (a,b,c). */
function cross(ax: number, ay: number, bx: number, by: number): number {
  return ax * by - ay * bx;
}

/** Do segments p1p2 and p3p4 intersect (proper or touching)? */
export function segmentsIntersect(p1: Point, p2: Point, p3: Point, p4: Point): boolean {
  const d1 = cross(p4.x - p3.x, p4.y - p3.y, p1.x - p3.x, p1.y - p3.y);
  const d2 = cross(p4.x - p3.x, p4.y - p3.y, p2.x - p3.x, p2.y - p3.y);
  const d3 = cross(p2.x - p1.x, p2.y - p1.y, p3.x - p1.x, p3.y - p1.y);
  const d4 = cross(p2.x - p1.x, p2.y - p1.y, p4.x - p1.x, p4.y - p1.y);
  if (((d1 > 0 && d2 < 0) || (d1 < 0 && d2 > 0)) && ((d3 > 0 && d4 < 0) || (d3 < 0 && d4 > 0))) return true;
  // collinear/touching cases are treated as non-blocking (avoids walls "leaking"
  // at exact endpoints); good enough for grid fog.
  return false;
}

/** Is the straight path a→b blocked by any segment? */
export function pathBlocked(a: Point, b: Point, segments: Segment[]): boolean {
  for (const [p, q] of segments) if (segmentsIntersect(a, b, p, q)) return true;
  return false;
}

export interface VisionOpts {
  viewers: Point[]; // token centers (image px)
  segments: Segment[];
  gridSize: number;
  cols: number;
  rows: number;
  /** sight radius in px; 0/undefined = unlimited (within bounds) */
  radiusPx?: number;
}

/**
 * Cells (by "col,row" key) visible from any viewer: the cell centre is within the
 * sight radius and the straight line from a viewer to it crosses no wall. Slightly
 * nudge the viewpoint off exact grid lines to avoid corner artefacts.
 */
export function computeVisibleCells({ viewers, segments, gridSize, cols, rows, radiusPx = 0 }: VisionOpts): Set<string> {
  const visible = new Set<string>();
  const r2 = radiusPx > 0 ? radiusPx * radiusPx : Infinity;
  for (const v of viewers) {
    const eye = { x: v.x + 0.01, y: v.y + 0.01 };
    for (let c = 0; c < cols; c++) {
      for (let r = 0; r < rows; r++) {
        const key = cellKey(c, r);
        if (visible.has(key)) continue;
        const center = { x: (c + 0.5) * gridSize, y: (r + 0.5) * gridSize };
        const dx = center.x - eye.x, dy = center.y - eye.y;
        if (dx * dx + dy * dy > r2) continue;
        if (!pathBlocked(eye, center, segments)) visible.add(key);
      }
    }
  }
  return visible;
}
