/* ============================================================
   VKTR TCO — Route map + free-tier KML/KMZ importer (V1.7)
   ------------------------------------------------------------
   Design (Notion: Roadmap — Vehicle Library, Routes & 3-Tier Access):
   - Leaflet.js + OpenStreetMap is the shared map-rendering layer for BOTH
     the free tier (this file) and the gated live-routing tier (next phase)
     -- deliberately NOT Google Maps JS API, which requires a Cloud Billing
     card on file regardless of actual usage (same objection as Firebase
     Blaze, already ruled out elsewhere in this app).
   - Free tier: user uploads a .kml or .kmz exported from Google My Maps
     (My Maps' own "Export to KML" defaults to .kmz -- a zipped .kml --
     so both extensions are handled from this first pass, via JSZip).
     Distance is computed locally (Haversine along the path). Elevation
     gain/loss comes from Open-Elevation, proxied through cf-worker's
     "elevation_lookup" action -- keyless and free, but Worker-proxied to
     put a signed-in + point-cap boundary around the shared public quota.
   - Guests can still upload/preview/measure distance with no sign-in;
     only the live elevation fetch requires sign-in, matching the same
     signed-in-but-not-apiAccess boundary already established by Function
     1's AI-assisted intake mode (needs_intake.jsx).
   - Saved routes live at users/{uid}/routes/{id} (firestore.rules already
     covers this, self-read/self-write only) -- point lists are downsampled
     before saving to keep documents small.
   ============================================================ */

// ---- Geometry helpers ----
function haversineKm(a, b) {
  const R = 6371;
  const dLat = (b.lat - a.lat) * Math.PI / 180;
  const dLng = (b.lng - a.lng) * Math.PI / 180;
  const s = Math.sin(dLat / 2) ** 2 + Math.cos(a.lat * Math.PI / 180) * Math.cos(b.lat * Math.PI / 180) * Math.sin(dLng / 2) ** 2;
  return 2 * R * Math.asin(Math.sqrt(Math.min(1, s)));
}

function totalDistanceKm(coords) {
  let total = 0;
  for (let i = 1; i < coords.length; i++) total += haversineKm(coords[i - 1], coords[i]);
  return total;
}

function simplifyToMaxPoints(coords, max) {
  if (coords.length <= max) return coords;
  const step = (coords.length - 1) / (max - 1);
  const out = [];
  for (let i = 0; i < max; i++) out.push(coords[Math.round(i * step)]);
  return out;
}

function elevationStats(elevations) {
  let gain = 0, loss = 0;
  for (let i = 1; i < elevations.length; i++) {
    const d = elevations[i] - elevations[i - 1];
    if (d > 0) gain += d; else loss += -d;
  }
  const net = elevations.length ? elevations[elevations.length - 1] - elevations[0] : 0;
  return { gain: Math.round(gain), loss: Math.round(loss), net: Math.round(net) };
}

// ---- KML/KMZ parsing ----
function textOf(el) { return ((el && el.textContent) || "").trim(); }

function parseCoordinatesText(text) {
  return text.split(/\s+/).filter(Boolean).map((tuple) => {
    const [lng, lat, ele] = tuple.split(",").map(Number);
    return { lat, lng, ele: Number.isFinite(ele) ? ele : null };
  }).filter((p) => Number.isFinite(p.lat) && Number.isFinite(p.lng));
}

function parseGxTrack(el) {
  const coordEls = Array.from(el.getElementsByTagName("gx:coord"));
  return coordEls.map((c) => {
    const [lng, lat, ele] = textOf(c).split(/\s+/).map(Number);
    return { lat, lng, ele: Number.isFinite(ele) ? ele : null };
  }).filter((p) => Number.isFinite(p.lat) && Number.isFinite(p.lng));
}

function extractPathFromKml(xmlDoc) {
  const lineStrings = Array.from(xmlDoc.getElementsByTagName("LineString"));
  for (const ls of lineStrings) {
    const coordsEl = ls.getElementsByTagName("coordinates")[0];
    if (coordsEl) {
      const pts = parseCoordinatesText(textOf(coordsEl));
      if (pts.length >= 2) return pts;
    }
  }
  const tracks = Array.from(xmlDoc.getElementsByTagName("gx:Track"));
  for (const t of tracks) {
    const pts = parseGxTrack(t);
    if (pts.length >= 2) return pts;
  }
  const rings = Array.from(xmlDoc.getElementsByTagName("LinearRing"));
  for (const r of rings) {
    const coordsEl = r.getElementsByTagName("coordinates")[0];
    if (coordsEl) {
      const pts = parseCoordinatesText(textOf(coordsEl));
      if (pts.length >= 2) return pts;
    }
  }
  return [];
}

async function readKmlTextFromFile(file) {
  const isKmz = /\.kmz$/i.test(file.name) || file.type === "application/vnd.google-earth.kmz";
  if (!isKmz) return await file.text();
  if (typeof JSZip === "undefined") throw new Error("KMZ support library failed to load — try a .kml file instead, or reload the page.");
  const zip = await JSZip.loadAsync(file);
  const kmlEntry = Object.values(zip.files).find((f) => !f.dir && /\.kml$/i.test(f.name));
  if (!kmlEntry) throw new Error("No .kml file found inside this .kmz archive.");
  return await kmlEntry.async("text");
}

async function parseKmlOrKmzFile(file) {
  const text = await readKmlTextFromFile(file);
  const xmlDoc = new DOMParser().parseFromString(text, "text/xml");
  if (xmlDoc.getElementsByTagName("parsererror").length) throw new Error("This file isn't valid KML/XML.");
  const coords = extractPathFromKml(xmlDoc);
  if (coords.length < 2) throw new Error("No route path found — expected a line or track in this KML (a single point or filled shape alone isn't a route).");
  const nameEl = xmlDoc.getElementsByTagName("name")[0];
  const name = nameEl ? textOf(nameEl) : file.name.replace(/\.(kml|kmz)$/i, "");
  return { name, coords };
}

// ---- Elevation, via cf-worker (never calls Open-Elevation directly) ----
async function fetchElevations(points) {
  if (typeof firebase === "undefined" || !firebase.auth().currentUser) throw new Error("not-signed-in");
  if (!window.API_PROXY_URL || window.API_PROXY_URL === "REPLACE_ME") throw new Error("API proxy not configured yet");
  const idToken = await firebase.auth().currentUser.getIdToken();
  const res = await fetch(window.API_PROXY_URL, {
    method: "POST",
    headers: { "Authorization": `Bearer ${idToken}`, "Content-Type": "application/json" },
    body: JSON.stringify({ action: "elevation_lookup", points }),
  });
  const data = await res.json();
  if (!res.ok) throw new Error(data.error || `Elevation lookup failed (${res.status})`);
  return data.results || [];
}

// ---- Saved routes (users/{uid}/routes/{id}) ----
function newRouteId() {
  return `rt_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
}

function useSavedRoutes(user) {
  const [routes, setRoutes] = React.useState({});
  React.useEffect(() => {
    if (!user || typeof firebase === "undefined" || !firebase.apps.length) { setRoutes({}); return; }
    const unsub = firebase.firestore().collection("users").doc(user.uid).collection("routes")
      .onSnapshot((snap) => {
        const next = {};
        snap.forEach((doc) => { next[doc.id] = doc.data(); });
        setRoutes(next);
      });
    return unsub;
  }, [user]);
  return routes;
}

function saveRouteToLibrary(user, route, name) {
  if (!user || typeof firebase === "undefined" || !firebase.apps.length) return;
  const coords = simplifyToMaxPoints(route.coords, 300);
  return firebase.firestore().collection("users").doc(user.uid).collection("routes").doc(newRouteId()).set({
    name, coords,
    distanceKm: route.distanceKm, elevGainM: route.elevGainM, elevLossM: route.elevLossM, netElevDeltaM: route.netElevDeltaM,
    savedAt: Date.now(),
  });
}

// v1.9.9 (2026-07-21) audit: these two, plus saveRouteToLibrary above,
// previously didn't return their Firestore promise (rename/delete) or
// weren't awaited by any caller -- every write in this pair failed
// completely silently. Both now return the promise so callers (library.jsx)
// can await + surface a failure.
function renameRouteDoc(user, id, name) {
  if (!user) return Promise.resolve();
  return firebase.firestore().collection("users").doc(user.uid).collection("routes").doc(id).update({ name });
}

function deleteRouteDoc(user, id) {
  if (!user) return Promise.resolve();
  return firebase.firestore().collection("users").doc(user.uid).collection("routes").doc(id).delete();
}

// ---- Leaflet map (shared visualization layer, free + gated tiers) ----
function RouteMap({ coordinates, markers, onClick, height = 220 }) {
  const mapElRef = React.useRef(null);
  const mapRef = React.useRef(null);
  const layerRef = React.useRef(null);
  const markersLayerRef = React.useRef(null);

  React.useEffect(() => {
    if (!mapElRef.current || typeof L === "undefined") return;
    const map = L.map(mapElRef.current, { attributionControl: true, zoomControl: true }).setView([-2.5, 118], 4);
    L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
      maxZoom: 19,
      attribution: "&copy; OpenStreetMap contributors",
    }).addTo(map);
    mapRef.current = map;
    return () => { map.remove(); mapRef.current = null; };
  }, []);

  React.useEffect(() => {
    const map = mapRef.current;
    if (!map) return;
    if (layerRef.current) { layerRef.current.remove(); layerRef.current = null; }
    if (!coordinates || coordinates.length < 2) return;
    const latlngs = coordinates.map((c) => [c.lat, c.lng]);
    const line = L.polyline(latlngs, { color: "#2f6feb", weight: 4 }).addTo(map);
    layerRef.current = line;
    map.fitBounds(line.getBounds(), { padding: [16, 16] });
  }, [coordinates]);

  React.useEffect(() => {
    const map = mapRef.current;
    if (!map) return;
    if (markersLayerRef.current) { markersLayerRef.current.remove(); markersLayerRef.current = null; }
    if (!markers || !markers.length) return;
    const group = L.layerGroup(markers.map((m) => L.marker([m.lat, m.lng]).bindTooltip(m.label || "", { permanent: false })));
    group.addTo(map);
    markersLayerRef.current = group;
  }, [markers]);

  React.useEffect(() => {
    const map = mapRef.current;
    if (!map || !onClick) return;
    const handler = (e) => onClick({ lat: e.latlng.lat, lng: e.latlng.lng });
    map.on("click", handler);
    return () => map.off("click", handler);
  }, [onClick]);

  return <div ref={mapElRef} style={{ height, borderRadius: 10, overflow: "hidden" }} />;
}

// ---- Free-tier import panel: file -> distance + elevation -> apply/save ----
function RouteImportPanel({ onApply }) {
  const { lang } = useLang();
  const { user } = useAuth();
  const [status, setStatus] = React.useState("idle"); // idle | parsing | fetching-elevation | ready | error
  const [errorMsg, setErrorMsg] = React.useState("");
  const [route, setRoute] = React.useState(null);
  const fileInputRef = React.useRef(null);

  const handleFile = async (file) => {
    if (!file) return;
    setStatus("parsing"); setErrorMsg(""); setRoute(null);
    try {
      const { name, coords } = await parseKmlOrKmzFile(file);
      const distanceKm = totalDistanceKm(coords);
      let elevGainM = 0, elevLossM = 0, netElevDeltaM = 0;
      if (user) {
        setStatus("fetching-elevation");
        const sampled = simplifyToMaxPoints(coords, 100);
        try {
          const results = await fetchElevations(sampled.map((p) => ({ lat: p.lat, lng: p.lng })));
          if (results.length >= 2) {
            const stats = elevationStats(results.map((r) => r.elevation));
            elevGainM = stats.gain; elevLossM = stats.loss; netElevDeltaM = stats.net;
          }
        } catch (elevErr) {
          // Elevation is a best-effort enhancement -- a failed lookup shouldn't
          // block the distance-only result the user already has.
        }
      }
      setRoute({ name, coords, distanceKm, elevGainM, elevLossM, netElevDeltaM });
      setStatus("ready");
    } catch (err) {
      setErrorMsg(err.message || String(err));
      setStatus("error");
    }
  };

  const saveAs = async () => {
    const name = window.prompt(tr(lang, "Save this route as", "Simpan rute ini sebagai"), route.name);
    if (!name) return;
    try {
      await saveRouteToLibrary(user, route, name);
    } catch (e) {
      alert(tr(lang, "Couldn't save — check your connection and try again.", "Gagal menyimpan — periksa koneksi Anda dan coba lagi.") + (e && e.message ? `\n(${e.message})` : ""));
    }
  };

  return (
    <div className="route-import-panel">
      <div className="route-import-toolbar">
        <input ref={fileInputRef} type="file" accept=".kml,.kmz" style={{ display: "none" }}
          onChange={(e) => { handleFile(e.target.files[0]); e.target.value = ""; }} />
        <button type="button" className="btn btn-ghost" style={{ fontSize: 12 }} onClick={() => fileInputRef.current.click()}>
          🗺 <Tr en="Import route (KML/KMZ)" id="Impor rute (KML/KMZ)" />
        </button>
        {status === "parsing" && <span className="route-import-status"><Tr en="Reading file…" id="Membaca file…" /></span>}
        {status === "fetching-elevation" && <span className="route-import-status"><Tr en="Fetching elevation profile…" id="Mengambil profil elevasi…" /></span>}
        {status === "error" && <span className="route-import-error">{errorMsg}</span>}
      </div>

      {route && (
        <div className="route-import-preview">
          <RouteMap coordinates={route.coords} />
          <div className="route-import-stats">
            <div><span className="sk"><Tr en="Distance" id="Jarak" /></span><span className="sv">{fmt.num(route.distanceKm)} km</span></div>
            <div><span className="sk"><Tr en="Elevation gain" id="Kenaikan elevasi" /></span><span className="sv">{fmt.num(route.elevGainM)} m</span></div>
            <div><span className="sk"><Tr en="Elevation loss" id="Penurunan elevasi" /></span><span className="sv">{fmt.num(route.elevLossM)} m</span></div>
          </div>
          {!user && (
            <div className="route-import-guest-note">
              <Tr en="Sign in to fetch a real elevation profile from Open-Elevation — distance-only for now."
                  id="Masuk untuk mengambil profil elevasi nyata dari Open-Elevation — untuk saat ini hanya jarak." />
            </div>
          )}
          <div style={{ display: "flex", gap: 8, marginTop: 10 }}>
            <button type="button" className="btn btn-primary" style={{ fontSize: 13 }}
              onClick={() => onApply({ distanceKm: route.distanceKm, elevGainM: route.elevGainM, elevLossM: route.elevLossM, netElevDeltaM: route.netElevDeltaM, enabled: true })}>
              <Tr en="Apply to track profile" id="Terapkan ke profil rute" />
            </button>
            {user && (
              <button type="button" className="btn btn-ghost" style={{ fontSize: 13 }} onClick={saveAs}>
                💾 <Tr en="Save to my library" id="Simpan ke perpustakaan saya" />
              </button>
            )}
          </div>
        </div>
      )}
    </div>
  );
}

// ---- Signed-in-tier live routing (v1.9.8: downgraded from the
//      AI/apiAccess-gated tier -- OpenRouteService has its own free-tier
//      request cap independent of Anthropic spend, same reasoning already
//      applied to elevation_lookup/nominatim_search, see cf-worker's
//      ors_route action, requiresApiAccess: false): click points on the
//      map (or search by name) to build an A-B-C-... multi-stop route,
//      optionally mirrored back-and-forth for a round-trip ritase
//      (A-B-C-B-A). OpenRouteService (driving-hgv profile) returns the
//      real road path, distance, and elevation profile via cf-worker's
//      "ors_route" action, which already accepts any number of waypoints
//      -- no Worker change needed here.
async function fetchOrsRoute(waypoints) {
  const idToken = await firebase.auth().currentUser.getIdToken();
  const res = await fetch(window.API_PROXY_URL, {
    method: "POST",
    headers: { "Authorization": `Bearer ${idToken}`, "Content-Type": "application/json" },
    body: JSON.stringify({ action: "ors_route", waypoints }),
  });
  const data = await res.json();
  if (!res.ok) throw new Error(data.error || `Request failed (${res.status})`);
  return data;
}

// Place-name search, provider-abstracted on purpose: Nominatim is the free
// "for now" provider (see cf-worker's nominatim_search action); swapping to
// Google Places once there's budget for the Cloud Billing card it requires
// is meant to be a change in this one function, not a UI rewrite.
async function geocodeSearch(query, provider) {
  if ((provider || "nominatim") !== "nominatim") throw new Error(`Unknown geocode provider: ${provider}`);
  const idToken = await firebase.auth().currentUser.getIdToken();
  const res = await fetch(window.API_PROXY_URL, {
    method: "POST",
    headers: { "Authorization": `Bearer ${idToken}`, "Content-Type": "application/json" },
    body: JSON.stringify({ action: "nominatim_search", query }),
  });
  const data = await res.json();
  if (!res.ok) throw new Error(data.error || `Request failed (${res.status})`);
  return data.results || [];
}

// A-B-C round trip mirrors the forward stops in reverse, excluding the
// last point (already the turnaround) so it isn't duplicated: A-B-C -> A-B-C-B-A.
function buildRitaseWaypoints(waypoints, roundTrip) {
  if (!roundTrip || waypoints.length < 2) return waypoints;
  const back = waypoints.slice(0, -1).reverse();
  return [...waypoints, ...back];
}

const MAX_ROUTE_WAYPOINTS = 20;

function RouteLiveLookup({ onApply }) {
  const { lang } = useLang();
  const { user, profile } = useAuth();
  // v1.9.8 (2026-07-21): was gated on canUseGatedApi (the AI/Anthropic
  // tier) even though OpenRouteService and Nominatim are both free,
  // keyless services with their own independent free-tier request caps --
  // nothing here spends VKTR's Anthropic budget. Per Rija: no reason to
  // gate a free API behind admin-granted AI access. Now just requires
  // sign-in, matching cf-worker's ors_route/nominatim_search actions
  // (requiresApiAccess: false) and the free KML/KMZ importer above.
  const gated = !!user;
  const [waypoints, setWaypoints] = React.useState([]);
  const [roundTrip, setRoundTrip] = React.useState(false);
  const [status, setStatus] = React.useState("idle"); // idle | loading | ready | error
  const [errorMsg, setErrorMsg] = React.useState("");
  const [route, setRoute] = React.useState(null);
  const [query, setQuery] = React.useState("");
  const [searchResults, setSearchResults] = React.useState(null);
  const [searching, setSearching] = React.useState(false);

  if (!gated) {
    return (
      <div className="route-import-guest-note">
        <Tr en="Live routing (turn-by-turn distance and elevation from OpenRouteService) needs a signed-in account — no AI/admin approval required. The free KML/KMZ import above works without signing in at all."
            id="Rute langsung (jarak dan elevasi belokan-per-belokan dari OpenRouteService) memerlukan akun yang sudah masuk — tidak perlu persetujuan AI/admin. Impor KML/KMZ gratis di atas berfungsi tanpa perlu masuk sama sekali." />
      </div>
    );
  }

  const resetResult = () => { setRoute(null); setStatus("idle"); setErrorMsg(""); };

  const addWaypoint = (wp) => {
    if (waypoints.length >= MAX_ROUTE_WAYPOINTS) return;
    setWaypoints((prev) => [...prev, wp]);
    resetResult();
  };
  const handleMapClick = (latlng) => addWaypoint(latlng);
  const undo = () => { setWaypoints((prev) => prev.slice(0, -1)); resetResult(); };
  const clear = () => { setWaypoints([]); resetResult(); };

  const runSearch = async (e) => {
    e.preventDefault();
    if (!query.trim() || searching) return;
    setSearching(true); setSearchResults(null);
    try {
      const results = await geocodeSearch(query.trim());
      setSearchResults(results);
    } catch (err) {
      // v1.9.10 (2026-07-21) audit: was setSearchResults([]) on ANY failure
      // (network error, Nominatim rate-limited) -- indistinguishable from a
      // genuine "no matches for this query" zero-result search, misleading
      // the user about why nothing showed up. Surface it instead.
      setSearchResults([]);
      alert(tr(lang, "Search failed — check your connection and try again.", "Pencarian gagal — periksa koneksi Anda dan coba lagi.") + (err && err.message ? `\n(${err.message})` : ""));
    } finally {
      setSearching(false);
    }
  };
  const pickSearchResult = (r) => {
    addWaypoint({ lat: r.lat, lng: r.lng });
    setSearchResults(null);
    setQuery("");
  };

  const lookupRoute = async () => {
    if (waypoints.length < 2) return;
    setStatus("loading"); setErrorMsg("");
    try {
      const data = await fetchOrsRoute(buildRitaseWaypoints(waypoints, roundTrip));
      setRoute(data);
      setStatus("ready");
    } catch (err) {
      setErrorMsg(err.message || String(err));
      setStatus("error");
    }
  };

  const saveAs = async () => {
    const name = window.prompt(tr(lang, "Save this route as", "Simpan rute ini sebagai"), tr(lang, "Live route", "Rute langsung"));
    if (!name) return;
    try {
      await saveRouteToLibrary(user, route, name);
    } catch (e) {
      alert(tr(lang, "Couldn't save — check your connection and try again.", "Gagal menyimpan — periksa koneksi Anda dan coba lagi.") + (e && e.message ? `\n(${e.message})` : ""));
    }
  };

  return (
    <div className="route-import-panel">
      <form className="route-search-row" onSubmit={runSearch}>
        <input type="text" className="input" style={{ flex: 1, fontSize: 12.5 }} value={query}
          placeholder={tr(lang, "Search a place to add as a stop…", "Cari lokasi untuk ditambahkan sebagai titik…")}
          onChange={(e) => setQuery(e.target.value)} />
        <button type="submit" className="btn btn-ghost" style={{ fontSize: 12 }} disabled={searching || !query.trim()}>
          {searching ? "…" : "🔍"}
        </button>
      </form>
      {searchResults && (
        <div className="route-search-results">
          {searchResults.length === 0 && (
            <div className="route-search-empty"><Tr en="No results." id="Tidak ada hasil." /></div>
          )}
          {searchResults.map((r, i) => (
            <div key={i} className="route-search-result" onClick={() => pickSearchResult(r)}>{r.label}</div>
          ))}
        </div>
      )}

      <div className="route-import-toolbar">
        <span className="route-import-status">
          {waypoints.length === 0
            ? tr(lang, "Click points on the map or search above: origin, then any stops, then destination.", "Klik titik di peta atau cari di atas: asal, lalu titik singgah, lalu tujuan.")
            : tr(lang, `${waypoints.length} point(s) set.`, `${waypoints.length} titik ditetapkan.`)}
        </span>
        {waypoints.length >= 2 && (
          <button type="button" className="btn btn-ghost" style={{ fontSize: 12 }} onClick={lookupRoute}>
            🧭 <Tr en="Find route" id="Cari rute" />
          </button>
        )}
        {waypoints.length > 0 && (
          <>
            <button type="button" className="btn btn-ghost" style={{ fontSize: 12 }} onClick={undo}>
              ↩ <Tr en="Undo" id="Urungkan" />
            </button>
            <button type="button" className="btn btn-ghost" style={{ fontSize: 12 }} onClick={clear}>
              <Tr en="Clear" id="Hapus" />
            </button>
          </>
        )}
        {status === "loading" && <span className="route-import-status"><Tr en="Fetching live route…" id="Mengambil rute langsung…" /></span>}
        {status === "error" && <span className="route-import-error">{errorMsg}</span>}
      </div>

      <label className="route-roundtrip-toggle">
        <input type="checkbox" checked={roundTrip} onChange={(e) => { setRoundTrip(e.target.checked); resetResult(); }} />
        <Tr en="Round trip (return via the same stops in reverse, e.g. A-B-C-B-A)" id="Pulang-pergi (kembali lewat titik yang sama secara terbalik, mis. A-B-C-B-A)" />
      </label>

      <div style={{ marginTop: 10 }}>
        <RouteMap
          coordinates={route ? route.coords : null}
          markers={waypoints.map((w, i) => ({ ...w, label: `${i + 1}. ` + (i === 0 ? tr(lang, "Origin", "Asal") : i === waypoints.length - 1 ? tr(lang, "Destination", "Tujuan") : tr(lang, "Stop", "Singgah")) }))}
          onClick={handleMapClick}
        />
      </div>

      {route && (
        <div className="route-import-preview">
          <div className="route-import-stats">
            <div><span className="sk"><Tr en="Distance" id="Jarak" /></span><span className="sv">{fmt.num(route.distanceKm)} km</span></div>
            <div><span className="sk"><Tr en="Elevation gain" id="Kenaikan elevasi" /></span><span className="sv">{fmt.num(route.elevGainM)} m</span></div>
            <div><span className="sk"><Tr en="Elevation loss" id="Penurunan elevasi" /></span><span className="sv">{fmt.num(route.elevLossM)} m</span></div>
          </div>
          <div style={{ display: "flex", gap: 8, marginTop: 10 }}>
            <button type="button" className="btn btn-primary" style={{ fontSize: 13 }}
              onClick={() => onApply({ distanceKm: route.distanceKm, elevGainM: route.elevGainM, elevLossM: route.elevLossM, netElevDeltaM: route.netElevDeltaM, enabled: true })}>
              <Tr en="Apply to track profile" id="Terapkan ke profil rute" />
            </button>
            <button type="button" className="btn btn-ghost" style={{ fontSize: 13 }} onClick={saveAs}>
              💾 <Tr en="Save to my library" id="Simpan ke perpustakaan saya" />
            </button>
          </div>
        </div>
      )}
    </div>
  );
}

Object.assign(window, {
  haversineKm, totalDistanceKm, simplifyToMaxPoints, elevationStats,
  parseKmlOrKmzFile, fetchElevations,
  newRouteId, useSavedRoutes, saveRouteToLibrary, renameRouteDoc, deleteRouteDoc,
  RouteMap, RouteImportPanel, RouteLiveLookup, fetchOrsRoute, geocodeSearch, buildRitaseWaypoints,
});
