/* safety-data.jsx — the REAL seed safety layer + route safety scoring (GSD task 4).

   This is what makes "Safe" routing real instead of a label. Two pieces:

   1) window.VR_SAFETY — a geo-located safety model:
        • hazards[] — avoid-points, each with a position, an influence radius (m) and a
          penalty weight by severity. Sources:
            – the SAME crowdsourced layer shown on the map (window.VR_COMMUNITY: danger
              segments, traffic / pothole / blocked / lighting points), so the community
              data now actually steers routing, and
            – a few CURATED known-dangerous Sofia corridors (busy boulevards with no bike
              infrastructure). This is the "seed" layer the GSD spec calls for — OSM-derived
              + hand-curated until a real dataset exists.
        • calm[] — confirmed-safe segments / bike lanes that REDUCE exposure when followed.

   2) scoreRoute(coords) / routePattern(coords) — pure geometry-vs-safety functions used by
      buildRoute() to (a) pick the genuinely least-exposed corridor as "Safe" and (b) color
      each route segment by its REAL proximity to hazards (replaces the old baked patterns).

   Keyless + offline: pure math over baked data, no API. Coords are [lat, lng]. */

// severity weight (higher = more strongly avoided) + influence radius in meters, per category
const VR_SEV    = { danger: 10, traffic: 9, pothole: 5, blocked: 6, lighting: 2 };
const VR_RADIUS = { danger: 70, traffic: 90, pothole: 50, blocked: 60, lighting: 60 };

// CURATED seed hazards — busy Sofia boulevards with no protected bike infra. These are the
// segments the "Fast" (direct) corridor tends to take and "Safe" should route around.
// Placed on the direct/Fast corridor's busy central boulevards (verified against live OSRM
// geometry to sit ~250–450 m off the calmer alternatives, so Safe genuinely routes around them).
const VR_CURATED = [
  { src: 'c-bulgaria',   cat: 'traffic', at: [42.68346, 23.32080], radius: 85, penalty: 9, label: 'бул. България — натоварено, без велоалея' },
  { src: 'c-levski',     cat: 'traffic', at: [42.68618, 23.32269], radius: 85, penalty: 8, label: 'бул. Васил Левски — пик, тесни ленти' },
  { src: 'c-pevtimii',   cat: 'traffic', at: [42.68796, 23.32302], radius: 85, penalty: 8, label: 'кръстовище Патриарх Евтимий' },
];

// Build VR_SAFETY from the CURRENT window.VR_COMMUNITY + curated seeds. Pure + idempotent, so it
// can be re-run whenever live community reports are merged in (vrRebuildSafety below re-applies KMZ).
function vrBuildSafetyBase() {
  const C = window.VR_COMMUNITY || { points: [], segments: [], cats: {} };
  const hazards = [];

  // point hazards from the community layer (amenities: parking/water/repair are NOT hazards)
  C.points.forEach((p) => {
    if (VR_SEV[p.cat] == null) return;
    hazards.push({ src: p.id, cat: p.cat, at: p.at, radius: VR_RADIUS[p.cat], penalty: VR_SEV[p.cat], label: p.label });
  });

  // danger segments → sampled as a chain of hazard nodes sharing one source id (so a route
  // brushing several vertices of the same segment still counts as ONE avoided hazard)
  C.segments.forEach((s) => {
    if (VR_SEV[s.cat] == null || !Array.isArray(s.coords)) return;
    s.coords.forEach((pt, i) => {
      if (i % 2) return; // subsample — keeps the node count light
      hazards.push({ src: s.id, cat: s.cat, at: pt, radius: VR_RADIUS[s.cat], penalty: VR_SEV[s.cat] * 0.7, label: s.label });
    });
  });

  VR_CURATED.forEach((h) => hazards.push(h));

  // calm bonuses — confirmed-safe community segments reduce exposure where a route follows them
  const calm = [];
  C.segments.forEach((s) => {
    if (s.cat !== 'safe' || !Array.isArray(s.coords)) return;
    s.coords.forEach((pt, i) => { if (i % 2) return; calm.push({ at: pt, radius: 45, bonus: 3 }); });
  });

  window.VR_SAFETY = { hazards, calm };
}

// rebuild the whole safety model from current community data, re-folding the KMZ network back in
// (vrIngestKMZCalm is idempotent and stashes its routes the first time, so this stays correct).
window.vrRebuildSafety = function () {
  vrBuildSafetyBase();
  if (window.__VR_KMZ_ROUTES) window.vrIngestKMZCalm(window.__VR_KMZ_ROUTES);
};

vrBuildSafetyBase();

// — geometry helpers (meters) —
function vrHav(a, b) {
  const R = 6371000, toR = (x) => x * Math.PI / 180;
  const dLat = toR(b[0] - a[0]), dLng = toR(b[1] - a[1]);
  const s = Math.sin(dLat / 2) ** 2 + Math.cos(toR(a[0])) * Math.cos(toR(b[0])) * Math.sin(dLng / 2) ** 2;
  return 2 * R * Math.asin(Math.sqrt(s));
}
// min distance (m) from a point to a polyline, approximated over its vertices (dense enough here)
function vrMinDistToRoute(pt, coords) {
  let best = Infinity;
  for (let i = 0; i < coords.length; i++) {
    const d = vrHav(pt, coords[i]);
    if (d < best) best = d;
  }
  return best;
}

/* vrIngestKMZCalm(routes) — fold the official VELOSOFIZE network into the safety model.
   Real human-curated bike infrastructure is exactly the "calm" data the cold-start lacks:
     • bike lanes (cat 1/2) + comfortable streets (cat 3) → calm bonuses (route hugs them ⇒ safer)
     • unpaved / rough (cat 4)                            → mild hazards (gently avoided)
   Mutates window.VR_SAFETY in place (scoreRoute/routePattern read it live). Idempotent.
   Coords are [lat, lng], matching VR_SAFETY. Subsampled to keep node counts light. */
window.vrIngestKMZCalm = function (routes) {
  const S = window.VR_SAFETY;
  if (!S || S.__kmzIngested || !Array.isArray(routes)) return;
  window.__VR_KMZ_ROUTES = routes; // stash so vrRebuildSafety() can re-fold the network after a rebuild
  const CALM_BONUS = { bikelane: 3.5, bikelane_shared: 2.5, comfort: 2 };
  for (const r of routes) {
    const bonus = CALM_BONUS[r.cat];
    if (bonus != null) {
      r.coords.forEach((pt, i) => { if (i % 3 === 0) S.calm.push({ at: pt, radius: 40, bonus }); });
    } else if (r.cat === 'rough') {
      r.coords.forEach((pt, i) => { if (i % 3 === 0) S.hazards.push({ src: r.id, cat: 'rough', at: pt, radius: 35, penalty: 3, label: 'Неасфалтиран участък' }); });
    }
  }
  S.__kmzIngested = true;
  if (window.vrResetCatGrid) window.vrResetCatGrid();   // rebuild the route-coloring grid from the new network
};

/* scoreRoute(coords) → real safety appraisal of one corridor:
     { exposure, score(0–100), level, srcIds:Set, dangerCount }
   exposure = Σ penalty·(1 − d/radius) over hazards the route enters, minus calm bonuses. */
window.scoreRoute = function (coords) {
  const S = window.VR_SAFETY || { hazards: [], calm: [] };
  // dedupe by source: a multi-node danger segment counts ONCE (its strongest node), so it
  // doesn't out-weigh a single-point hazard just by having more vertices.
  const bySrc = {};
  for (const h of S.hazards) {
    const d = vrMinDistToRoute(h.at, coords);
    if (d < h.radius) {
      const c = h.penalty * (1 - d / h.radius);
      if (!(h.src in bySrc) || c > bySrc[h.src]) bySrc[h.src] = c;
    }
  }
  const srcIds = new Set(Object.keys(bySrc));
  let hazardExp = 0; for (const k in bySrc) hazardExp += bySrc[k];
  // calm comfort can NUDGE the score but never erase real hazard exposure (capped + halved)
  let calm = 0;
  for (const c of S.calm) { const d = vrMinDistToRoute(c.at, coords); if (d < c.radius) calm += c.bonus * (1 - d / c.radius); }
  calm = Math.min(calm, 8);
  const exposure = Math.max(0, hazardExp - calm * 0.5);
  const score = Math.max(20, Math.min(99, Math.round(100 - exposure * 1.9)));
  const level = score >= 78 ? 'safe' : score >= 55 ? 'moderate' : 'danger';
  return { exposure, score, level, srcIds, dangerCount: srcIds.size };
};

/* routePattern(coords) → [{frac, level}] colored by REAL hazard proximity along the route,
   consumed unchanged by segByPattern() in real-map.jsx. */
window.routePattern = function (coords) {
  const S = window.VR_SAFETY || { hazards: [] };
  const levels = coords.map((p) => {
    let worst = 0;
    for (const h of S.hazards) {
      const d = vrHav(p, h.at);
      if (d < h.radius) { const intensity = h.penalty * (1 - d / h.radius); if (intensity > worst) worst = intensity; }
    }
    return worst >= 5 ? 'danger' : worst >= 1.8 ? 'moderate' : 'safe';
  });
  const runs = [];
  levels.forEach((lv) => { const last = runs[runs.length - 1]; if (last && last.level === lv) last.n++; else runs.push({ level: lv, n: 1 }); });
  const total = levels.length || 1;
  return runs.map((r) => ({ frac: r.n / total, level: r.level }));
};

/* routeCatPattern(coords) → [{frac, cat}] classifying each segment by the VELOSOFIZE infrastructure
   it rides on (protected bike lane / shared lane / comfort street / rough / wide sidewalk), or
   'street' where it follows the plain road network. Lets the map draw the route in the SAME colors
   as the filter legend, so the rider sees the composition before they go. Consumed by segByPattern()
   in real-map.jsx (same shape as routePattern, with `cat` instead of `level`).

   Backed by the KMZ network stashed in window.__VR_KMZ_ROUTES (vrIngestKMZCalm). A coarse grid keeps
   the nearest-infra lookup cheap. Returns all-'street' until the KMZ network is loaded. */
const VR_CAT_GRID = 0.0025;                                   // ~250 m cells
const VR_CAT_SET = { bikelane: 1, bikelane_shared: 1, comfort: 1, rough: 1, sidewalk: 1 };
let _vrCatGrid = null;
window.vrResetCatGrid = function () { _vrCatGrid = null; };   // called when the KMZ network changes
function vrBuildCatGrid() {
  _vrCatGrid = new Map();
  const routes = window.__VR_KMZ_ROUTES || [];
  for (const r of routes) {
    if (!VR_CAT_SET[r.cat] || !Array.isArray(r.coords)) continue;
    r.coords.forEach((pt, i) => {
      if (i % 2) return;                                       // subsample — keep it light
      const key = Math.round(pt[0] / VR_CAT_GRID) + ':' + Math.round(pt[1] / VR_CAT_GRID);
      let arr = _vrCatGrid.get(key); if (!arr) { arr = []; _vrCatGrid.set(key, arr); }
      arr.push({ at: pt, cat: r.cat });
    });
  }
}
function vrNearestCat(pt, maxM) {
  if (!_vrCatGrid) vrBuildCatGrid();
  const ci = Math.round(pt[0] / VR_CAT_GRID), cj = Math.round(pt[1] / VR_CAT_GRID);
  let bestCat = null, bestD = maxM;
  for (let di = -1; di <= 1; di++) for (let dj = -1; dj <= 1; dj++) {
    const arr = _vrCatGrid.get((ci + di) + ':' + (cj + dj)); if (!arr) continue;
    for (const s of arr) { const d = vrHav(pt, s.at); if (d < bestD) { bestD = d; bestCat = s.cat; } }
  }
  return bestCat;
}
window.routeCatPattern = function (coords) {
  if (!coords || coords.length < 2) return [{ frac: 1, cat: 'street' }];
  const cats = coords.map((p) => vrNearestCat(p, 22) || 'street');
  const runs = [];
  cats.forEach((c) => { const last = runs[runs.length - 1]; if (last && last.cat === c) last.n++; else runs.push({ cat: c, n: 1 }); });
  const total = cats.length || 1;
  return runs.map((r) => ({ frac: r.n / total, cat: r.cat }));
};

/* vrBikelaneVia(O, D, maxN) → [[pO, pD], ...] via-waypoint pairs that route the rider ONTO the
   official VELOSOFIZE bike network when a lane forms a sensible corridor between origin and dest.
   Feeding OSRM `O → pO → pD → D` nudges it to follow the lane between pO and pD instead of detouring
   around it on plain streets — which is exactly the "take the velo road, not around the park" ask.
   Conservative: only lanes that bridge a real chunk of O→D with a small approach + no wild detour.
   Coords are [lat, lng]. */
window.vrBikelaneVia = function (O, D, maxN) {
  const routes = window.__VR_KMZ_ROUTES || [];
  if (!O || !D || !routes.length) return [];
  const OD = vrHav(O, D) || 1;
  if (OD < 350) return [];                                   // trip too short to reroute via a lane
  const USE = { bikelane: 1, bikelane_shared: 1, comfort: 0.7 };
  const cands = [];
  for (const r of routes) {
    if (!USE[r.cat] || !Array.isArray(r.coords) || r.coords.length < 2) continue;
    let iO = -1, dO = Infinity, iD = -1, dD = Infinity;
    for (let i = 0; i < r.coords.length; i++) {
      const a = vrHav(O, r.coords[i]); if (a < dO) { dO = a; iO = i; }
      const b = vrHav(D, r.coords[i]); if (b < dD) { dD = b; iD = i; }
    }
    if (iO < 0 || iD < 0 || iO === iD) continue;
    const pO = r.coords[iO], pD = r.coords[iD];
    let seg = 0; const lo = Math.min(iO, iD), hi = Math.max(iO, iD);
    for (let i = lo + 1; i <= hi; i++) seg += vrHav(r.coords[i - 1], r.coords[i]);
    const approach = dO + dD;
    const total = dO + seg + dD;                              // rough O→pO→lane→pD→D length
    if (dO > 1300 || dD > 1300) continue;                     // lane too far to be worth it
    if (seg < 0.25 * OD) continue;                            // must cover a real chunk of the trip
    if (total > 1.7 * OD) continue;                           // not a wild detour
    if (vrHav(O, pD) <= vrHav(O, pO)) continue;               // pO→pD must head toward D
    cands.push({ pO, pD, score: seg * USE[r.cat] - approach * 0.6 });
  }
  cands.sort((a, b) => b.score - a.score);
  const out = [], seen = [];
  for (const c of cands) {
    if (c.score <= 0) continue;
    if (seen.some((s) => vrHav(s.pO, c.pO) < 150 && vrHav(s.pD, c.pD) < 150)) continue;
    seen.push(c); out.push([c.pO, c.pD]);
    if (out.length >= (maxN || 2)) break;
  }
  return out;
};
