export type LngLat = { lng: number; lat: number };

function normalizeLngLat(a: number, b: number): LngLat | null {
  if (!Number.isFinite(a) || !Number.isFinite(b)) return null;
  const looksLikeLngLat = Math.abs(a) <= 180 && Math.abs(b) <= 90;
  const looksLikeLatLng = Math.abs(a) <= 90 && Math.abs(b) <= 180;
  if (looksLikeLngLat) return { lng: a, lat: b };
  if (looksLikeLatLng) return { lng: b, lat: a };
  return null;
}

function parsePointLike(v: any): LngLat | null {
  if (!v) return null;
  if (Array.isArray(v) && v.length >= 2) {
    return normalizeLngLat(Number(v[0]), Number(v[1]));
  }
  if (typeof v === 'object') {
    // Try common field names from map libs / backends.
    const a = v.lng ?? v.lon ?? v.longitude ?? v.x;
    const b = v.lat ?? v.latitude ?? v.y;
    if (a !== undefined && b !== undefined) {
      return normalizeLngLat(Number(a), Number(b));
    }
  }
  if (typeof v === 'string') {
    return parseLngLatFromText(v);
  }
  return null;
}

export function parseLngLatFromText(raw: string | undefined): LngLat | null {
  const text = (raw || '').trim();
  if (!text) return null;
  const parts = text.split(/[,\s]+/).filter(Boolean);
  if (parts.length < 2) return null;
  const a = parts[0];
  const b = parts[1];
  if (!a || !b) return null;
  return normalizeLngLat(parseFloat(a), parseFloat(b));
}

/**
 * Parse fence "points" into a polygon point list.
 *
 * Supported formats (best-effort):
 * - JSON: [[lng,lat], ...] or [{lng,lat}, ...]
 * - Delimited: "lng,lat;lng,lat;..." or "lng,lat|lng,lat|..."
 * - Flat numbers: "lng,lat,lng,lat,..." (even count)
 */
export function parseFencePoints(pointsRaw: string | undefined): LngLat[] {
  const text = (pointsRaw || '').trim();
  if (!text) return [];

  // 1) JSON-like.
  if (text.startsWith('[') || text.startsWith('{')) {
    try {
      const parsed = JSON.parse(text);
      if (Array.isArray(parsed)) {
        const list = parsed.map(parsePointLike).filter(Boolean) as LngLat[];
        if (list.length) return list;
        // Some systems wrap coordinates like [[[lng,lat],...]].
        if (Array.isArray(parsed[0])) {
          const inner = (parsed[0] as any[]).map(parsePointLike).filter(Boolean) as LngLat[];
          if (inner.length) return inner;
        }
      }
    } catch (_e) {
      // fall through
    }
  }

  // 2) Split by common point separators.
  const segments = text.split(/[;|\n\r]+/).map(s => s.trim()).filter(Boolean);
  if (segments.length > 1) {
    const list = segments.map(seg => {
      const nums = seg.match(/-?\d+(\.\d+)?/g) || [];
      if (nums.length < 2) return null;
      const a = nums[0];
      const b = nums[1];
      if (!a || !b) return null;
      return normalizeLngLat(parseFloat(a), parseFloat(b));
    }).filter(Boolean) as LngLat[];
    if (list.length) return list;
  }

  // 3) Fallback: grab all numbers and pair them.
  const nums = text.match(/-?\d+(\.\d+)?/g) || [];
  if (nums.length >= 6 && nums.length % 2 === 0) {
    const list: LngLat[] = [];
    for (let i = 0; i < nums.length; i += 2) {
      const a = nums[i];
      const b = nums[i + 1];
      if (!a || !b) continue;
      const p = normalizeLngLat(parseFloat(a), parseFloat(b));
      if (p) list.push(p);
    }
    if (list.length) return list;
  }

  return [];
}

function pointOnSegment(p: LngLat, a: LngLat, b: LngLat, eps = 1e-9): boolean {
  // Cross product must be near 0 and dot product within [0, |ab|^2]
  const cross = (b.lat - a.lat) * (p.lng - a.lng) - (b.lng - a.lng) * (p.lat - a.lat);
  if (Math.abs(cross) > eps) return false;
  const dot = (p.lng - a.lng) * (b.lng - a.lng) + (p.lat - a.lat) * (b.lat - a.lat);
  if (dot < -eps) return false;
  const lenSq = (b.lng - a.lng) ** 2 + (b.lat - a.lat) ** 2;
  return dot <= lenSq + eps;
}

// Ray-casting with boundary check; treats points on edges as inside.
export function pointInPolygon(p: LngLat, polygon: LngLat[]): boolean {
  if (!polygon || polygon.length < 3) return false;

  for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
    const a = polygon[j];
    const b = polygon[i];
    if (pointOnSegment(p, a, b)) return true;
  }

  let inside = false;
  const x = p.lng;
  const y = p.lat;
  for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
    const xi = polygon[i].lng;
    const yi = polygon[i].lat;
    const xj = polygon[j].lng;
    const yj = polygon[j].lat;

    const intersect = (yi > y) !== (yj > y) && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi;
    if (intersect) inside = !inside;
  }
  return inside;
}

export function pointInAnyPolygon(p: LngLat, polygons: LngLat[][]): boolean {
  for (const poly of polygons) {
    if (pointInPolygon(p, poly)) return true;
  }
  return false;
}
