import type { GridSpec, Point } from './types';
import { cellCenter, cellKey, inBounds } from './grid';

// ---- Reveal/hide authoring tools → cell keys ----

/** Square brush of radius `r` cells centered on (col,row): a (2r+1)² block. */
export function brushCells(col: number, row: number, r: number, cols: number, rows: number): string[] {
  const out: string[] = [];
  const rr = Math.max(0, Math.floor(r));
  for (let c = col - rr; c <= col + rr; c++) {
    for (let rw = row - rr; rw <= row + rr; rw++) {
      if (inBounds(c, rw, cols, rows)) out.push(cellKey(c, rw));
    }
  }
  return out;
}

/** Inclusive rectangle between two cells. */
export function rectCells(a: { col: number; row: number }, b: { col: number; row: number }, cols: number, rows: number): string[] {
  const out: string[] = [];
  const c0 = Math.min(a.col, b.col), c1 = Math.max(a.col, b.col);
  const r0 = Math.min(a.row, b.row), r1 = Math.max(a.row, b.row);
  for (let c = c0; c <= c1; c++) for (let r = r0; r <= r1; r++) if (inBounds(c, r, cols, rows)) out.push(cellKey(c, r));
  return out;
}

/** Ray-casting point-in-polygon (verts in any units consistent with `pt`). */
export function pointInPolygon(pt: Point, verts: Point[]): boolean {
  let inside = false;
  for (let i = 0, j = verts.length - 1; i < verts.length; j = i++) {
    const a = verts[i]!, b = verts[j]!;
    const intersect = a.y > pt.y !== b.y > pt.y && pt.x < ((b.x - a.x) * (pt.y - a.y)) / (b.y - a.y) + a.x;
    if (intersect) inside = !inside;
  }
  return inside;
}

/** Rasterize a polygon (image-space px verts) to covered cell keys. */
export function polygonCells(verts: Point[], grid: GridSpec): string[] {
  if (verts.length < 3) return [];
  const out: string[] = [];
  for (let c = 0; c < grid.cols; c++) {
    for (let r = 0; r < grid.rows; r++) {
      if (pointInPolygon(cellCenter(c, r, grid.gridSize), verts)) out.push(cellKey(c, r));
    }
  }
  return out;
}

// ---- AoE templates (image-space px) → covered cell keys ----

function feetToPx(feet: number, grid: GridSpec): number {
  return (feet / grid.feetPerCell) * grid.gridSize;
}

function forEachCellCenter(grid: GridSpec, fn: (c: number, r: number, center: Point) => void): void {
  for (let c = 0; c < grid.cols; c++) for (let r = 0; r < grid.rows; r++) fn(c, r, cellCenter(c, r, grid.gridSize));
}

export function circleCells(center: Point, radiusFeet: number, grid: GridSpec): string[] {
  const rPx = feetToPx(radiusFeet, grid);
  const out: string[] = [];
  forEachCellCenter(grid, (c, r, p) => { if (Math.hypot(p.x - center.x, p.y - center.y) <= rPx) out.push(cellKey(c, r)); });
  return out;
}

export function squareCells(center: Point, sideFeet: number, grid: GridSpec): string[] {
  const half = feetToPx(sideFeet, grid) / 2;
  const out: string[] = [];
  forEachCellCenter(grid, (c, r, p) => {
    if (Math.abs(p.x - center.x) <= half && Math.abs(p.y - center.y) <= half) out.push(cellKey(c, r));
  });
  return out;
}

export function coneCells(origin: Point, target: Point, lengthFeet: number, angleDeg: number, grid: GridSpec): string[] {
  const lenPx = feetToPx(lengthFeet, grid);
  const dir = Math.atan2(target.y - origin.y, target.x - origin.x);
  const halfAngle = (angleDeg * Math.PI) / 180 / 2;
  const out: string[] = [];
  forEachCellCenter(grid, (c, r, p) => {
    const vx = p.x - origin.x, vy = p.y - origin.y;
    const dist = Math.hypot(vx, vy);
    if (dist === 0 || dist > lenPx) return;
    let da = Math.abs(Math.atan2(vy, vx) - dir);
    if (da > Math.PI) da = 2 * Math.PI - da;
    if (da <= halfAngle) out.push(cellKey(c, r));
  });
  return out;
}

export function lineCells(a: Point, b: Point, widthFeet: number, grid: GridSpec): string[] {
  const halfW = feetToPx(widthFeet, grid) / 2;
  const out: string[] = [];
  const dx = b.x - a.x, dy = b.y - a.y;
  const len2 = dx * dx + dy * dy;
  forEachCellCenter(grid, (c, r, p) => {
    const t = len2 === 0 ? 0 : Math.max(0, Math.min(1, ((p.x - a.x) * dx + (p.y - a.y) * dy) / len2));
    const px = a.x + t * dx, py = a.y + t * dy;
    if (Math.hypot(p.x - px, p.y - py) <= halfW) out.push(cellKey(c, r));
  });
  return out;
}
