import { bboxContainsPoint, bboxForPoints, type BBox } from "./bbox";
import { pointInPolygon } from "./pointInPolygon";
import type { GeoPoint, MapPolygon } from "../model/mapTypes";

export type IndexedPolygon = {
  polygon: MapPolygon;
  bbox: BBox;
};

export type PolygonSpatialIndex = {
  entries: IndexedPolygon[];
};

export function createPolygonSpatialIndex(polygons: MapPolygon[]): PolygonSpatialIndex {
  return {
    entries: polygons.map((polygon) => ({
      polygon,
      bbox: bboxForPoints(polygon.points)
    }))
  };
}

export function hitTestAllPolygons(index: PolygonSpatialIndex, point: GeoPoint): MapPolygon[] {
  const hits: MapPolygon[] = [];

  for (const entry of index.entries) {
    if (entry.polygon.pickable === false) continue; // контур отрисовывается, но из выбора исключён
    const inBbox =
      bboxContainsPoint(entry.bbox, point) ||
      bboxContainsPoint(entry.bbox, { lon: point.lon + 360, lat: point.lat }) ||
      bboxContainsPoint(entry.bbox, { lon: point.lon - 360, lat: point.lat });
    if (!inBbox) continue;

    const normalized = unwrapLon(entry.polygon.points);
    const hit =
      pointInPolygon(point, normalized) ||
      pointInPolygon({ lon: point.lon + 360, lat: point.lat }, normalized) ||
      pointInPolygon({ lon: point.lon - 360, lat: point.lat }, normalized);

    if (hit) hits.push(entry.polygon);
  }

  return hits.sort((a, b) => b.zIndex - a.zIndex);
}

export function hitTestPolygons(index: PolygonSpatialIndex, point: GeoPoint): MapPolygon | undefined {
  let selected: MapPolygon | undefined;

  for (const entry of index.entries) {
    if (entry.polygon.pickable === false) continue; // контур отрисовывается, но из выбора исключён
    // Check bbox allowing ±360° shifts so antimeridian-crossing polygons are not skipped.
    const inBbox =
      bboxContainsPoint(entry.bbox, point) ||
      bboxContainsPoint(entry.bbox, { lon: point.lon + 360, lat: point.lat }) ||
      bboxContainsPoint(entry.bbox, { lon: point.lon - 360, lat: point.lat });
    if (!inBbox) continue;

    // Normalize polygon lons to be continuous (same idea as unwrapX for screen coords).
    const normalized = unwrapLon(entry.polygon.points);
    const hit =
      pointInPolygon(point, normalized) ||
      pointInPolygon({ lon: point.lon + 360, lat: point.lat }, normalized) ||
      pointInPolygon({ lon: point.lon - 360, lat: point.lat }, normalized);

    if (hit && (!selected || entry.polygon.zIndex >= selected.zIndex)) {
      selected = entry.polygon;
    }
  }

  return selected;
}

export function queryPolygonsByBBox(index: PolygonSpatialIndex, bbox: BBox): MapPolygon[] {
  return index.entries
    .filter((entry) => entry.bbox.minLat <= bbox.maxLat && entry.bbox.maxLat >= bbox.minLat)
    // Долготу сверяем с учётом ±360°: контуры из бэкенда могут приходить в долготах 0..360
    // (западное полушарие как ~234° вместо −126°). Без этого они выпадают из набора отрисовки,
    // хотя остаются кликабельными (hit-test уже учитывает сдвиги), — контур «пропадает» на карте.
    .filter((entry) =>
      [0, 360, -360].some(
        (shift) => entry.bbox.minLon + shift <= bbox.maxLon && entry.bbox.maxLon + shift >= bbox.minLon
      )
    )
    .map((entry) => entry.polygon);
}

function unwrapLon(points: GeoPoint[]): GeoPoint[] {
  if (points.length === 0) return points;
  const out: GeoPoint[] = [points[0]];
  let prevLon = points[0].lon;
  for (let i = 1; i < points.length; i++) {
    let lon = points[i].lon;
    while (lon - prevLon > 180) lon -= 360;
    while (prevLon - lon > 180) lon += 360;
    out.push({ ...points[i], lon });
    prevLon = lon;
  }
  return out;
}

