(() => {
  /**
   * Runtime hooks for Speed / Sound map zones (MiR §4.2.6.8–9).
   * Vector overlay only — consumed by motion controller / mission runner.
   */
  const TYPES = {
    speed: "speed",
    sound: "sound",
  };

  const BEHAVIOR_TYPES = new Set([TYPES.speed, TYPES.sound]);

  const SPEED_MIN = 0.1;
  const SPEED_MAX = 1.5;
  const DEFAULT_SPEED_MPS = 0.8;

  function pointInPolygon(px, py, points) {
    return window.MapPlannerZones?.pointInPolygon(px, py, points) || false;
  }

  function clampSpeed(value) {
    const n = Number(value);
    if (!Number.isFinite(n)) return DEFAULT_SPEED_MPS;
    return Math.min(SPEED_MAX, Math.max(SPEED_MIN, n));
  }

  function isBehaviorZoneType(type) {
    return BEHAVIOR_TYPES.has(type);
  }

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

  /** Topmost behavior zones containing image pixel (newest wins for overlaps). */
  function zonesAtPoint(zones, px, py) {
    const list = Array.isArray(zones) ? zones : [];
    const hits = [];
    for (let i = list.length - 1; i >= 0; i--) {
      const z = list[i];
      if (!isBehaviorZoneType(z?.type) || !z.points?.length) continue;
      if (pointInPolygon(px, py, z.points)) hits.push(z);
    }
    return hits;
  }

  function getSpeedLimit(zones, px, py) {
    const hit = zonesAtPoint(zones, px, py).find((z) => z.type === TYPES.speed);
    if (!hit) return null;
    return clampSpeed(hit.speed_mps);
  }

  function getSoundAtPoint(zones, px, py) {
    const hit = zonesAtPoint(zones, px, py).find((z) => z.type === TYPES.sound);
    if (!hit?.sound_id) return null;
    return {
      sound_id: hit.sound_id,
      zone_id: hit.id,
    };
  }

  function classifyPoint(zones, px, py) {
    const hits = zonesAtPoint(zones, px, py);
    const speedZone = hits.find((z) => z.type === TYPES.speed);
    const soundZone = hits.find((z) => z.type === TYPES.sound);
    return {
      speed_mps: speedZone ? clampSpeed(speedZone.speed_mps) : null,
      sound_id: soundZone?.sound_id || null,
      speed_zone: speedZone || null,
      sound_zone: soundZone || null,
      zones: hits,
    };
  }

  window.MapBehaviorZones = {
    TYPES,
    BEHAVIOR_TYPES,
    SPEED_MIN,
    SPEED_MAX,
    DEFAULT_SPEED_MPS,
    clampSpeed,
    isBehaviorZoneType,
    filterBehaviorZones,
    zonesAtPoint,
    getSpeedLimit,
    getSoundAtPoint,
    classifyPoint,
  };
})();
