(() => {
  /**
   * Planner hooks for Forbidden / Preferred / Unpreferred drive zones (MiR §4.2.6.6).
   * Vector overlay only — consumed by future global planner / path preview.
   */
  const TYPES = {
    forbidden: "forbidden",
    preferred: "preferred",
    unpreferred: "unpreferred",
  };

  const PLANNER_TYPES = new Set([TYPES.forbidden, TYPES.preferred, TYPES.unpreferred]);

  /** Relative traversal cost (forbidden = impassable). */
  const COST = {
    forbidden: Infinity,
    unpreferred: 4,
    preferred: 0.35,
    neutral: 1,
  };

  function pointInPolygon(px, py, points) {
    if (!points?.length) return false;
    let inside = false;
    for (let i = 0, j = points.length - 1; i < points.length; j = i++) {
      const xi = points[i].x;
      const yi = points[i].y;
      const xj = points[j].x;
      const yj = points[j].y;
      const intersect = yi > py !== yj > py && px < ((xj - xi) * (py - yi)) / (yj - yi) + xi;
      if (intersect) inside = !inside;
    }
    return inside;
  }

  function distPointToSegment(px, py, x1, y1, x2, y2) {
    const dx = x2 - x1;
    const dy = y2 - y1;
    const lenSq = dx * dx + dy * dy;
    if (lenSq === 0) return Math.hypot(px - x1, py - y1);
    let t = ((px - x1) * dx + (py - y1) * dy) / lenSq;
    t = Math.max(0, Math.min(1, t));
    return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
  }

  function pointNearPolyline(px, py, points, halfWidth, tolerance = 4) {
    if (!points?.length || points.length < 2) return false;
    const limit = halfWidth + tolerance;
    for (let j = 0; j < points.length - 1; j++) {
      const p1 = points[j];
      const p2 = points[j + 1];
      if (distPointToSegment(px, py, p1.x, p1.y, p2.x, p2.y) <= limit) return true;
    }
    return false;
  }

  function isLineGeometry(z) {
    return z?.geometry === "line";
  }

  function isPlannerZoneType(type) {
    return PLANNER_TYPES.has(type);
  }

  function filterPlannerZones(zones) {
    return (Array.isArray(zones) ? zones : []).filter((z) => isPlannerZoneType(z?.type));
  }

  /** Topmost planner zones containing image pixel (newest wins for overlaps). */
  function zonesAtPoint(zones, px, py, mapMeta = null) {
    const list = Array.isArray(zones) ? zones : [];
    const hits = [];
    const linePx = (z) => window.MapObjects?.zoneLineWidthPx(z, mapMeta) ?? 8;
    for (let i = list.length - 1; i >= 0; i--) {
      const z = list[i];
      if (!isPlannerZoneType(z?.type) || !z.points?.length) continue;
      if (isLineGeometry(z)) {
        const half = linePx(z) / 2;
        if (pointNearPolyline(px, py, z.points, half)) hits.push(z);
      } else if (pointInPolygon(px, py, z.points)) hits.push(z);
    }
    return hits;
  }

  function pixelCost(zones, px, py, mapMeta = null) {
    const hits = zonesAtPoint(zones, px, py, mapMeta);
    if (hits.some((z) => z.type === TYPES.forbidden)) return COST.forbidden;
    if (hits.some((z) => z.type === TYPES.unpreferred)) return COST.unpreferred;
    if (hits.some((z) => z.type === TYPES.preferred)) return COST.preferred;
    return COST.neutral;
  }

  function classifyPoint(zones, px, py, mapMeta = null) {
    const hits = zonesAtPoint(zones, px, py, mapMeta);
    return {
      forbidden: hits.some((z) => z.type === TYPES.forbidden),
      preferred: hits.some((z) => z.type === TYPES.preferred),
      unpreferred: hits.some((z) => z.type === TYPES.unpreferred),
      zones: hits,
      cost: pixelCost(zones, px, py, mapMeta),
    };
  }

  function isPathBlocked(zones, pathPoints, mapMeta = null) {
    const pts = Array.isArray(pathPoints) ? pathPoints : [];
    return pts.some((p) => pixelCost(zones, p.x, p.y, mapMeta) === COST.forbidden);
  }

  function pathCost(zones, pathPoints, mapMeta = null) {
    const pts = Array.isArray(pathPoints) ? pathPoints : [];
    let sum = 0;
    for (const p of pts) {
      const c = pixelCost(zones, p.x, p.y, mapMeta);
      if (!Number.isFinite(c)) return Infinity;
      sum += c;
    }
    return sum;
  }

  window.MapPlannerZones = {
    TYPES,
    PLANNER_TYPES,
    COST,
    isPlannerZoneType,
    filterPlannerZones,
    zonesAtPoint,
    pixelCost,
    classifyPoint,
    isPathBlocked,
    pathCost,
    pointInPolygon,
  };
})();
