window.initBrilloMapa = function (dotNetHelper) {
    document.addEventListener("keydown", function (e) {
        if (e.key && e.key.toLowerCase() === "m") {
            dotNetHelper.invokeMethodAsync("AlternarBrillo");
        }
    });
};

window.initAltInspector = function (dotNetHelper) {
    document.addEventListener("keydown", function (e) {
        if (e.key === "Z" || e.key === "z") dotNetHelper.invokeMethodAsync("SetInspectorActivo", true);
    }, true);

    document.addEventListener("keyup", function (e) {
        if (e.key === "Z" || e.key === "z") dotNetHelper.invokeMethodAsync("SetInspectorActivo", false);
    }, true);

    window.addEventListener("blur", function () {
        dotNetHelper.invokeMethodAsync("SetInspectorActivo", false);
    });
};






// -----------------------------------------------------------------------------
// Leaflet helpers (sin flechas) para RealTimeMap
// -----------------------------------------------------------------------------
(function () {
    function hookLeaflet(L) {
        if (!L || window.__leafletHooked) return;

        // Hook SUPER temprano: constructor del mapa
        const originalInitialize = L.Map && L.Map.prototype && L.Map.prototype.initialize;
        if (typeof originalInitialize === "function") {
            L.Map.prototype.initialize = function (...args) {
                const res = originalInitialize.apply(this, args);
                window.__rtLeafletMap = this; // guardamos el mapa creado
                return res;
            };
        }

        // Hook adicional: factory L.map (por si acaso)
        const originalMapFactory = L.map;
        if (typeof originalMapFactory === "function") {
            L.map = function (...args) {
                const map = originalMapFactory.apply(this, args);
                window.__rtLeafletMap = map;
                return map;
            };
        }

        window.__leafletHooked = true;
        console.log("[rt] Leaflet hook OK");
    }

    // Si Leaflet ya existe:
    if (window.L) {
        hookLeaflet(window.L);
        return;
    }

    // Si Leaflet se asigna más tarde: definimos setter
    let _L = null;
    Object.defineProperty(window, "L", {
        configurable: true,
        enumerable: true,
        get() { return _L; },
        set(v) {
            _L = v;
            // restauramos propiedad normal
            try {
                delete window.L;
                window.L = v;
            } catch { /* ignore */ }

            hookLeaflet(v);
        }
    });
})();


window.rt = window.rt || {};

window.rt.ensureRouteLayer = function () {
    const map = window.__rtLeafletMap;
    if (!map || !window.L) {
        // ayuda para diagnosticar
        // console.log("[rt] no map yet", { hasL: !!window.L, hasMap: !!map });
        return false;
    }

    if (!window.rt._routeLayer) {
        window.rt._routeLayer = window.L.layerGroup().addTo(map);
    }
    if (!window.rt._routeLines) window.rt._routeLines = {};
    if (!window.rt._routeCoords) window.rt._routeCoords = {};
    return true;
};

window.rt.clearRoutes = function () {
    if (!window.rt.ensureRouteLayer()) return;
    window.rt._routeLayer.clearLayers();
    window.rt._routeLines = {};
    window.rt._routeCoords = {};

    if (window.rt._routeStopsLayer) {
        window.rt._routeStopsLayer.clearLayers();
        window.rt._routeStopGroups = {};
    }
};
window.rt.drawRoute = function (id, coords, options) {
    if (!window.rt.ensureRouteLayer()) return;

    if (window.rt._routeLines[id]) {
        window.rt._routeLayer.removeLayer(window.rt._routeLines[id]);
        delete window.rt._routeLines[id];
    }

    const arrowId = id + "__arrow";
    if (window.rt._routeLines[arrowId]) {
        window.rt._routeLayer.removeLayer(window.rt._routeLines[arrowId]);
        delete window.rt._routeLines[arrowId];
    }

    const routeCoords = Array.isArray(coords)
        ? coords
            .map(function (point) {
                return [Number(point && point[0]), Number(point && point[1])];
            })
            .filter(function (point) {
                return Number.isFinite(point[0]) && Number.isFinite(point[1]);
            })
        : [];

    if (routeCoords.length > 0) window.rt._routeCoords[id] = routeCoords;
    else delete window.rt._routeCoords[id];

    const color = (options && options.color) || "#000";
    const weight = (options && options.weight) || 4;
    const opacity = (options && options.opacity) || 0.9;
    const dashArray = options && options.dashArray;
    const lineCap = options && options.lineCap;
    const arrowEnd = !!(options && options.arrowEnd);
    const elevationSegments = Array.isArray(options && options.elevationSegments)
        ? options.elevationSegments
        : [];

    const attachRouteClick = function (line) {
        line.on("click", function (e) {
            try {
                if (e && e.originalEvent) {
                    if (e.originalEvent.preventDefault) e.originalEvent.preventDefault();
                    if (e.originalEvent.stopPropagation) e.originalEvent.stopPropagation();
                    if (e.originalEvent.stopImmediatePropagation) e.originalEvent.stopImmediatePropagation();
                }

                if (window.rt._dotNetHelper) {
                    window.rt._dotNetHelper.invokeMethodAsync("OnPreviewItinerarioClick", id);
                }
            } catch (e) { }
        });
    };

    const buildLine = function (lineCoords, lineOpts) {
        const line = window.L.polyline(lineCoords, lineOpts);
        attachRouteClick(line);
        return line;
    };

    let arrowColor = color;
    let hasElevationPaint = false;

    if (elevationSegments.length > 0 && coords && coords.length >= 2) {
        const group = window.L.layerGroup();

        const progressAlongSegment = function (segmentCoords, latlng) {
            if (!latlng || !Array.isArray(segmentCoords) || segmentCoords.length < 2) return 0.5;

            const start = segmentCoords[0];
            const end = segmentCoords[segmentCoords.length - 1];
            const startLat = Number(start && start[0]);
            const startLon = Number(start && start[1]);
            const endLat = Number(end && end[0]);
            const endLon = Number(end && end[1]);

            if (![startLat, startLon, endLat, endLon, latlng.lat, latlng.lng].every(Number.isFinite))
                return 0.5;

            const lonScale = Math.cos(((startLat + endLat) / 2) * Math.PI / 180);
            const dx = (endLon - startLon) * lonScale;
            const dy = endLat - startLat;
            const px = (latlng.lng - startLon) * lonScale;
            const py = latlng.lat - startLat;
            const lengthSquared = dx * dx + dy * dy;

            if (lengthSquared <= Number.EPSILON) return 0.5;

            const progress = (px * dx + py * dy) / lengthSquared;
            return Math.max(0, Math.min(1, progress));
        };

        const elevationTooltip = function (segment, progress) {
            const t = Number.isFinite(progress) ? Math.max(0, Math.min(1, progress)) : 0.5;
            const slope = segment && typeof segment.slope === "number" ? segment.slope : null;
            const distance = segment && typeof segment.distance === "number" ? segment.distance : null;
            const elevationStart = segment && typeof segment.elevationStart === "number" ? segment.elevationStart : null;
            const elevationEnd = segment && typeof segment.elevationEnd === "number" ? segment.elevationEnd : null;
            const slopeText = slope !== null && Number.isFinite(slope)
                ? ((slope > 0 ? "+" : "") + slope.toFixed(1) + " %")
                : (segment && segment.label ? String(segment.label) : "-");
            const distanceText = distance !== null && Number.isFinite(distance)
                ? (distance * t).toFixed(2) + " m"
                : "-";
            const estimatedElevation = elevationStart !== null && Number.isFinite(elevationStart) &&
                elevationEnd !== null && Number.isFinite(elevationEnd)
                ? elevationStart + (elevationEnd - elevationStart) * t
                : null;
            const elevationText = estimatedElevation !== null
                ? estimatedElevation.toFixed(2) + " m"
                : "-";

            return [
                "Pendiente: " + slopeText,
                "Dist. anterior: " + distanceText,
                "Altura: " + elevationText
            ].join("<br>");
        };

        elevationSegments.forEach(function (segment) {
            const segmentCoords = segment && segment.coords;
            if (!Array.isArray(segmentCoords) || segmentCoords.length < 2) return;

            const segmentColor = (segment && segment.color) || color;
            arrowColor = segmentColor;

            const segmentOpts = {
                color: segmentColor,
                weight,
                opacity,
                lineCap: lineCap || "round",
                bubblingMouseEvents: false
            };
            if (dashArray) segmentOpts.dashArray = dashArray;

            const segmentLine = buildLine(segmentCoords, segmentOpts);
            const label = elevationTooltip(segment, 0.5);

            if (label) {
                segmentLine.bindTooltip(label, {
                    sticky: true,
                    direction: "top",
                    opacity: 0.92,
                    className: "rt-elev-tooltip"
                });
                const updateTooltipAtCursor = function (e) {
                    const progress = progressAlongSegment(segmentCoords, e && e.latlng);
                    segmentLine.setTooltipContent(elevationTooltip(segment, progress));
                };
                segmentLine.on("mouseover", updateTooltipAtCursor);
                segmentLine.on("mousemove", updateTooltipAtCursor);
                segmentLine.on("click", function (e) {
                    if (!window.rt.isMobileLayout || !window.rt.isMobileLayout()) return;
                    updateTooltipAtCursor(e);
                    try { segmentLine.openTooltip(e && e.latlng); } catch { }
                });
            }

            segmentLine.addTo(group);
            hasElevationPaint = true;
        });

        if (hasElevationPaint) {
            group.addTo(window.rt._routeLayer);
            window.rt._routeLines[id] = group;
        }
    }

    if (!hasElevationPaint) {
        const lineOpts = { color, weight, opacity };
        if (dashArray) lineOpts.dashArray = dashArray;
        if (lineCap) lineOpts.lineCap = lineCap;

        const line = buildLine(coords, lineOpts);
        line.addTo(window.rt._routeLayer);
        window.rt._routeLines[id] = line;
    }

    if (arrowEnd && coords && coords.length >= 2) {
        const a = coords[coords.length - 2];
        const b = coords[coords.length - 1];

        // bearing aprox (en grados)
        const lat1 = a[0] * Math.PI / 180, lon1 = a[1] * Math.PI / 180;
        const lat2 = b[0] * Math.PI / 180, lon2 = b[1] * Math.PI / 180;
        const y = Math.sin(lon2 - lon1) * Math.cos(lat2);
        const x = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(lon2 - lon1);
        let brng = Math.atan2(y, x) * 180 / Math.PI;
        brng = (brng + 360) % 360;

        const size = Math.max(14, Math.min(28, weight * 4)); // tamaño ligado al grosor

        const arrowClass = dashArray ? "rt-arrow rt-arrow--dashed" : "rt-arrow";
        const arrowInnerHtml = dashArray ? '<span class="rt-arrow-dash-head"></span>' : '';

        const icon = window.L.divIcon({
            className: "rt-arrow-icon",
            html: `<div class="${arrowClass}" style="
         width:${size}px;height:${size}px;
         transform: rotate(${brng}deg);
         --arrow-color:${arrowColor};
       ">${arrowInnerHtml}</div>`,
            iconSize: [size, size],
            iconAnchor: [size / 2, size / 2]
        });

        const marker = window.L.marker(b, { icon, interactive: false });
        marker.addTo(window.rt._routeLayer);
        window.rt._routeLines[arrowId] = marker;
    }
};

window.rt.fitRoutes = function (coords, preferredCenter) {
    const map = window.__rtLeafletMap;
    if (!map || !window.L) return false;

    const routePoints = [];
    if (Array.isArray(coords)) {
        coords.forEach(function (point) {
            routePoints.push(point);
        });
    } else {
        if (!window.rt._routeCoords) return false;
        Object.values(window.rt._routeCoords).forEach(function (routeCoords) {
            if (!Array.isArray(routeCoords)) return;
            routeCoords.forEach(function (point) {
                routePoints.push(point);
            });
        });
    }

    const points = routePoints
        .filter(function (point) {
            return Array.isArray(point)
                && point.length >= 2
                && Number.isFinite(Number(point[0]))
                && Number.isFinite(Number(point[1]));
        })
        .map(function (point) {
            return [Number(point[0]), Number(point[1])];
        });

    if (points.length === 0) return false;

    const visibleBounds = map.getBounds();
    const algunPuntoVisible = visibleBounds && points.some(function (point) {
        return visibleBounds.contains(window.L.latLng(point[0], point[1]));
    });

    if (algunPuntoVisible) return false;

    const geometryBounds = window.L.latLngBounds(points);
    if (!geometryBounds.isValid()) return false;

    const center = Array.isArray(preferredCenter)
        && preferredCenter.length >= 2
        && Number.isFinite(Number(preferredCenter[0]))
        && Number.isFinite(Number(preferredCenter[1]))
        ? window.L.latLng(Number(preferredCenter[0]), Number(preferredCenter[1]))
        : geometryBounds.getCenter();

    map.panTo(center, {
        animate: true,
        duration: 0.35
    });
    return true;
};

window.rt = window.rt || {};

window.rt.setZoomLimits = function (minZoom, maxZoom) {
    const map = window.__rtLeafletMap;
    if (!map || !window.L) return;

    map.options.minZoom = minZoom;
    map.options.maxZoom = maxZoom;
    map.setMinZoom(minZoom);
    map.setMaxZoom(maxZoom);

    map.eachLayer(l => {
        if (!l) return;

        // límites generales
        if (typeof l.setMaxZoom === "function") l.setMaxZoom(maxZoom);
        if (typeof l.setMinZoom === "function") l.setMinZoom(minZoom);

        // ✅ SOLO TileLayer
        if (l instanceof window.L.TileLayer) {
            l.options.maxNativeZoom = 18;  // tiles reales hasta 18
            l.options.maxZoom = maxZoom;  // permitimos 19
            try { l.redraw(); } catch (e) { }
        }
    });

    try { map.invalidateSize(); } catch (e) { }

    if (window.rt.addZoomIndicator) window.rt.addZoomIndicator();
};


window.rt = window.rt || {};

// guardar referencia a Blazor para callbacks JS -> .NET
window.rt.setDotNetHelper = function (dotNetHelper) {
    window.rt._dotNetHelper = dotNetHelper;
};




// -----------------------------------------------------------------------------
// Elev points layer (puntos de elevación + número)
// -----------------------------------------------------------------------------
window.rt = window.rt || {};

window.rt.ensureElevLayer = function () {
    const map = window.__rtLeafletMap;
    if (!map || !window.L) return false;

    if (!window.rt._elevLayer) {
        window.rt._elevLayer = window.L.layerGroup().addTo(map);
    }
    if (!window.rt._elevGroups) window.rt._elevGroups = {};
    return true;
};


window.rt.clearElevPoints = function (id) {
    if (!window.rt.ensureElevLayer()) return;

    if (id) {
        const g = window.rt._elevGroups && window.rt._elevGroups[id];
        if (g) {
            window.rt._elevLayer.removeLayer(g);
            delete window.rt._elevGroups[id];
        }
        return;
    }

    // sin id => limpiar todo
    window.rt._elevLayer.clearLayers();
    window.rt._elevGroups = {};
};

// points: [{ lat, lon, label }]
// options: { radius, opacity, fillOpacity, showLabels, labelEvery }
window.rt.drawElevPoints = function (id, points, options) {
    if (!window.rt.ensureElevLayer()) return;

    // borrar lo anterior con ese id
    if (window.rt._elevGroups[id]) {
        window.rt._elevLayer.removeLayer(window.rt._elevGroups[id]);
        delete window.rt._elevGroups[id];
    }

    const radius = (options && options.radius) || 3;
    const opacity = (options && options.opacity) || 0.9;
    const fillOpacity = (options && options.fillOpacity) || 0.9;

    const showLabels = options && options.showLabels !== false; // default true
    const every = (options && options.labelEvery) || 1;         // default 1
    const showSlopeTooltips = !!(options && options.showSlopeTooltips);
    const baseClassName = (options && options.className) || "";
    const defaultColor = (options && options.color) || "#facc15";

    const group = window.L.layerGroup();

    if (points && points.length) {
        for (let i = 0; i < points.length; i++) {
            const p = points[i];
            const pointColor = (p && p.color) || defaultColor;
            const category = (p && p.category) ? String(p.category) : "";
            const categoryClass = category ? `${baseClassName}--${category}` : "";
            const className = [baseClassName, categoryClass, p && p.className].filter(Boolean).join(" ");

            const m = window.L.circleMarker([p.lat, p.lon], {
                radius: radius,
                weight: 1,
                opacity: opacity,
                fillOpacity: fillOpacity,
                color: pointColor,
                fillColor: pointColor,
                className: className,
                bubblingMouseEvents: false
            });

            if (showLabels && every > 0 && (i % every === 0)) {
                const txt = (p.label != null ? String(p.label) : String(i));

                // -------------------------------------------------------
                // ✅ Evitar que el número pise la ruta:
                // si el tramo local es "vertical" -> etiqueta a un lado
                // si es "horizontal" -> arriba
                // -------------------------------------------------------
                let dir = "top";
                let off = [0, -3];

                const prev = (i > 0) ? points[i - 1] : null;
                const next = (i < points.length - 1) ? points[i + 1] : null;

                const a = prev || p;
                const b = next || p;

                const dLat = Math.abs((b.lat) - (a.lat));
                const dLon = Math.abs((b.lon) - (a.lon));

                // tramo más vertical => poner a la derecha
                if (dLat > dLon) {
                    dir = "right"; // puedes cambiar a "left" si prefieres
                    off = [5, 0]; // separación lateral
                }

                m.bindTooltip(txt, {
                    permanent: true,
                    direction: dir,
                    offset: off,
                    opacity: 0.95,
                    className: "rt-elev-label"
                });
            }
            else if (showSlopeTooltips && p && typeof p.slope === "number" && Number.isFinite(p.slope)) {
                const label = (p.slope > 0 ? "+" : "") + p.slope.toFixed(1) + "%";
                m.bindTooltip(label, {
                    sticky: true,
                    direction: "top",
                    opacity: 0.92,
                    className: "rt-elev-tooltip"
                });
                m.on("click", function (e) {
                    if (!window.rt.isMobileLayout || !window.rt.isMobileLayout()) return;
                    try { m.openTooltip(e && e.latlng); } catch { }
                });
            }

            m.addTo(group);
        }
    }

    group.addTo(window.rt._elevLayer);
    window.rt._elevGroups[id] = group;
};
window.rt = window.rt || {};

window.rt = window.rt || {};

window.rt.addZoomIndicator = function () {
    const map = window.__rtLeafletMap;
    if (!map || !window.L) return;

    // evitar duplicados
    if (window.__rtZoomIndicator) return;

    const zoomContainer = map.zoomControl && map.zoomControl._container;
    if (!zoomContainer) return;

    // botones nativos
    const btnIn = zoomContainer.querySelector(".leaflet-control-zoom-in");
    const btnOut = zoomContainer.querySelector(".leaflet-control-zoom-out");
    if (!btnIn || !btnOut) return;

    // indicador
    const div = window.L.DomUtil.create("div", "leaflet-control-zoom-indicator-inset");
    div.innerText = map.getZoom();

    // que no robe clicks
    window.L.DomEvent.disableClickPropagation(div);
    window.L.DomEvent.disableScrollPropagation(div);

    // ✅ insertarlo ENTRE + y -
    zoomContainer.insertBefore(div, btnOut);

    map.on("zoomend", () => {
        div.innerText = map.getZoom();
    });

    window.__rtZoomIndicator = div;
};







window.rt = window.rt || {};

window.rt.blockLeafletDoubleClick = function () {
    if (window.__rtDblClickBlocked) return; // evitar duplicados
    window.__rtDblClickBlocked = true;

    document.addEventListener("dblclick", function (e) {
        // si el doble click ocurre dentro de un mapa Leaflet, lo anulamos
        const isOnMap = e.target && e.target.closest && e.target.closest(".leaflet-container");
        if (!isOnMap) return;

        e.preventDefault();
        e.stopPropagation();
        // clave: corta el evento incluso para listeners “antes”
        if (e.stopImmediatePropagation) e.stopImmediatePropagation();
        return false;
    }, true); // CAPTURING: lo pillamos antes que Leaflet
};

window.rt.isMobileLayout = function () {
    return !!(window.matchMedia && window.matchMedia("(max-width: 780px)").matches);
};

window.rt.initMobileMapControls = function () {
    if (window.rt._mobileMapControlsReady) return;
    window.rt._mobileMapControlsReady = true;

    const holdMilliseconds = 620;
    const movementTolerance = 12;
    let holdTimer = null;
    let press = null;
    let suppressClickUntil = 0;

    const cancelHold = function () {
        if (holdTimer !== null) {
            window.clearTimeout(holdTimer);
            holdTimer = null;
        }
        press = null;
    };

    document.addEventListener("pointerdown", function (event) {
        if (!window.rt.isMobileLayout())
            return;
        if (event.isPrimary === false) {
            cancelHold();
            return;
        }

        const mapContainer = event.target && event.target.closest
            ? event.target.closest(".leaflet-container")
            : null;
        if (!mapContainer) return;

        if (event.target.closest(".leaflet-control, .leaflet-marker-icon, .leaflet-popup"))
            return;

        cancelHold();
        press = {
            pointerId: event.pointerId,
            clientX: event.clientX,
            clientY: event.clientY,
            mapContainer
        };

        holdTimer = window.setTimeout(function () {
            const currentPress = press;
            holdTimer = null;
            if (!currentPress || !currentPress.mapContainer.isConnected)
                return;

            const map = window.__rtLeafletMap;
            if (!map || !window.L || !window.rt._dotNetHelper)
                return;

            const rect = currentPress.mapContainer.getBoundingClientRect();
            const point = window.L.point(
                currentPress.clientX - rect.left,
                currentPress.clientY - rect.top);
            const latLng = map.containerPointToLatLng(point);

            suppressClickUntil = Date.now() + 900;
            try { navigator.vibrate?.(18); } catch { }
            window.rt._dotNetHelper.invokeMethodAsync("OnMobileMapLongPress", latLng.lat, latLng.lng);
            press = null;
        }, holdMilliseconds);
    }, true);

    document.addEventListener("pointermove", function (event) {
        if (!press || event.pointerId !== press.pointerId) return;
        if (Math.abs(event.clientX - press.clientX) > movementTolerance
            || Math.abs(event.clientY - press.clientY) > movementTolerance) {
            cancelHold();
        }
    }, true);

    document.addEventListener("pointerup", cancelHold, true);
    document.addEventListener("pointercancel", cancelHold, true);

    document.addEventListener("click", function (event) {
        if (Date.now() >= suppressClickUntil) return;
        const isOnMap = event.target && event.target.closest
            ? event.target.closest(".leaflet-container")
            : null;
        if (!isOnMap) return;

        event.preventDefault();
        event.stopPropagation();
        if (event.stopImmediatePropagation) event.stopImmediatePropagation();
    }, true);

    document.addEventListener("contextmenu", function (event) {
        if (!window.rt.isMobileLayout()) return;
        const isOnMap = event.target && event.target.closest
            ? event.target.closest(".leaflet-container")
            : null;
        if (isOnMap) event.preventDefault();
    }, true);
};




// -----------------------------------------------------------------------------
// Preview layer (línea completa) - NO toca la ruta actual
// -----------------------------------------------------------------------------
window.rt = window.rt || {};

window.rt.ensurePreviewLayer = function () {
    const map = window.__rtLeafletMap;
    if (!map || !window.L) return false;

    if (!window.rt._previewLayer) {
        window.rt._previewLayer = window.L.layerGroup().addTo(map);
    }
    if (!window.rt._previewLines) window.rt._previewLines = {};
    return true;
};

window.rt.clearPreview = function () {
    if (!window.rt.ensurePreviewLayer()) return;
    window.rt._previewLayer.clearLayers();
    window.rt._previewLines = {};

    // ✅ también limpiar paradas destacadas
    if (window.rt._itStopsLayer) {
        window.rt._itStopsLayer.clearLayers();
        window.rt._itStopGroups = {};
    }
};

window.rt.drawPreviewLine = function (id, coords, options) {
    if (!window.rt.ensurePreviewLayer()) return;

    const empty = !coords || coords.length === 0;
    const arrowId = id + "__arrow";

    // toggle: si ya existe, lo quitamos (línea + flecha(s))
    if (window.rt._previewLines[id]) {
        window.rt._previewLayer.removeLayer(window.rt._previewLines[id]);
        delete window.rt._previewLines[id];

        if (window.rt._previewLines[arrowId]) {
            window.rt._previewLayer.removeLayer(window.rt._previewLines[arrowId]);
            delete window.rt._previewLines[arrowId];
        }

        return;
    }

    if (empty) return;

    const map = window.__rtLeafletMap;

    const color = (options && options.color) || "#60a5fa";
    const weight = (options && options.weight) || 4;
    const opacity = (options && options.opacity) || 0.6;
    const dashArray = options && options.dashArray;
    const arrowEnd = !!(options && options.arrowEnd);
    const arrowClass = dashArray ? "rt-arrow rt-arrow--dashed" : "rt-arrow";
    const arrowInnerHtml = dashArray ? '<span class="rt-arrow-dash-head"></span>' : '';

    const lineOpts = { color, weight, opacity };
    if (dashArray) lineOpts.dashArray = dashArray;

    const line = window.L.polyline(coords, lineOpts);

    // ✅ Hacer la línea clicable y avisar a Blazor
    line.on("click", function (e) {
        try {
            if (e && e.originalEvent) {
                if (e.originalEvent.preventDefault) e.originalEvent.preventDefault();
                if (e.originalEvent.stopPropagation) e.originalEvent.stopPropagation();
                if (e.originalEvent.stopImmediatePropagation) e.originalEvent.stopImmediatePropagation();
            }

            if (window.rt._dotNetHelper) {
                window.rt._dotNetHelper.invokeMethodAsync("OnPreviewItinerarioClick", id);
            }
        } catch (e) { }
    });

    line.addTo(window.rt._previewLayer);
    window.rt._previewLines[id] = line;

    // ✅ Flecha al FINAL
    if (arrowEnd && coords.length >= 2 && map) {
        // pane por encima de las paradas
        if (!map.getPane("rtArrowPane")) {
            const pane = map.createPane("rtArrowPane");
            pane.style.zIndex = 760;            // > rtStopsPane (750)
            pane.style.pointerEvents = "none";  // no roba clicks
        }

        const size = Math.max(14, Math.min(28, weight * 4));

        function addArrow(from, to, storeId) {
            const lat1 = from[0] * Math.PI / 180, lon1 = from[1] * Math.PI / 180;
            const lat2 = to[0] * Math.PI / 180, lon2 = to[1] * Math.PI / 180;

            const y = Math.sin(lon2 - lon1) * Math.cos(lat2);
            const x = Math.cos(lat1) * Math.sin(lat2) -
                Math.sin(lat1) * Math.cos(lat2) * Math.cos(lon2 - lon1);

            let brng = Math.atan2(y, x) * 180 / Math.PI;
            brng = (brng + 360) % 360;

            const icon = window.L.divIcon({
                className: "rt-arrow-icon",
                html: `<div class="${arrowClass}" style="
                    width:${size}px;height:${size}px;
                    transform: rotate(${brng}deg);
                    --arrow-color:${color};
                ">${arrowInnerHtml}</div>`,
                iconSize: [size, size],
                iconAnchor: [size / 2, size / 2]
            });

            const marker = window.L.marker(to, {
                icon,
                pane: "rtArrowPane",
                interactive: false,
                zIndexOffset: 1000
            });

            marker.addTo(window.rt._previewLayer);
            window.rt._previewLines[storeId] = marker;
        }

        addArrow(coords[coords.length - 2], coords[coords.length - 1], arrowId);
    }
};



window.rt = window.rt || {};

window.rt.getZoom = function () {
    const map = window.__rtLeafletMap;
    return map ? map.getZoom() : 14;
};




window.rt = window.rt || {};

// Devuelve true si el click está dentro del círculo (en píxeles)
window.rt.isClickInsideCirclePx = function (clickLat, clickLon, targetLat, targetLon, radiusPx) {
    const map = window.__rtLeafletMap;
    if (!map || !window.L) return false;

    const pClick = map.latLngToContainerPoint([clickLat, clickLon]);
    const pTarget = map.latLngToContainerPoint([targetLat, targetLon]);

    const dx = pClick.x - pTarget.x;
    const dy = pClick.y - pTarget.y;

    return (dx * dx + dy * dy) <= (radiusPx * radiusPx);
};





// -----------------------------------------------------------------------------
// Itinerario stops layer (paradas destacadas) - toggle por id
// -----------------------------------------------------------------------------
window.rt = window.rt || {};

window.rt.ensureItStopsLayer = function () {
    const map = window.__rtLeafletMap;
    if (!map || !window.L) return false;

    // pane por encima para que queden SOBRE las líneas
    if (!map.getPane("rtStopsPane")) {
        const pane = map.createPane("rtStopsPane");
        pane.style.zIndex = 750;
    }

    if (!window.rt._itStopsLayer) {
        window.rt._itStopsLayer = window.L.layerGroup().addTo(map);
    }
    if (!window.rt._itStopGroups) window.rt._itStopGroups = {};
    return true;
};

window.rt.createHighlightedStopMarker = function (stop, pane, options) {
    if (!stop || !window.L) return null;

    const lat = Number(stop.lat);
    const lon = Number(stop.lon);
    if (!Number.isFinite(lat) || !Number.isFinite(lon)) return null;

    const toPositiveNumber = function (value, fallback) {
        const parsed = Number(value);
        return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
    };
    const toOpacity = function (value, fallback) {
        const parsed = Number(value);
        return Number.isFinite(parsed) ? Math.min(1, Math.max(0, parsed)) : fallback;
    };

    const radius = toPositiveNumber(options && options.radius, 7);
    const weight = toPositiveNumber(options && options.weight, 2);
    const color = String((options && options.color) || "#0f172a");
    const fillColor = String((options && options.fillColor) || "#22c55e");
    const opacity = toOpacity(options && options.opacity, 1);
    const fillOpacity = toOpacity(options && options.fillOpacity, 1);
    const size = Math.max(14, Math.ceil((radius * 2) + weight));

    const markerElement = document.createElement("span");
    markerElement.className = "rt-highlighted-stop-marker";

    const disc = document.createElement("span");
    disc.className = "rt-highlighted-stop-marker__disc";
    disc.style.inset = `${weight}px`;
    disc.style.backgroundColor = fillColor;
    disc.style.opacity = String(fillOpacity);
    markerElement.appendChild(disc);

    const ring = document.createElement("span");
    ring.className = "rt-highlighted-stop-marker__ring";
    ring.style.borderColor = color;
    ring.style.borderWidth = `${weight}px`;
    ring.style.opacity = String(opacity);
    markerElement.appendChild(ring);

    const rawBearing = stop.bearing;
    const bearing = rawBearing === null || rawBearing === undefined || rawBearing === ""
        ? null
        : Number(rawBearing);
    if (Number.isFinite(bearing)) {
        const arrow = document.createElement("span");
        arrow.className = "rt-stop-direction-arrow";
        arrow.style.setProperty("--stop-bearing", `${bearing}deg`);
        markerElement.appendChild(arrow);
    }

    const icon = window.L.divIcon({
        className: "rt-highlighted-stop-icon",
        html: markerElement,
        iconSize: [size, size],
        iconAnchor: [size / 2, size / 2]
    });

    const marker = window.L.marker([lat, lon], {
        pane,
        icon,
        interactive: true,
        keyboard: false,
        bubblingMouseEvents: false,
        zIndexOffset: 1000
    });

    if (stop.code && window.rt._dotNetHelper) {
        marker.on("click", function (event) {
            try {
                if (event && event.originalEvent) {
                    if (event.originalEvent.preventDefault) event.originalEvent.preventDefault();
                    if (event.originalEvent.stopPropagation) event.originalEvent.stopPropagation();
                    if (event.originalEvent.stopImmediatePropagation) event.originalEvent.stopImmediatePropagation();
                }

                window.rt._dotNetHelper.invokeMethodAsync("OnHighlightedStopClick", stop.code);
            } catch (error) { }
        });
    }

    return marker;
};

// stops: [{lat, lon, code, bearing}]
window.rt.toggleItStops = function (id, stops, options) {
    if (!window.rt.ensureItStopsLayer()) return;

    // toggle: si existe => quitar
    if (window.rt._itStopGroups[id]) {
        window.rt._itStopsLayer.removeLayer(window.rt._itStopGroups[id]);
        delete window.rt._itStopGroups[id];
        return;
    }

    const radius = (options && options.radius) || 7;
    const color = (options && options.color) || "#0f172a";
    const fillColor = (options && options.fillColor) || "#22c55e";
    const weight = (options && options.weight) || 2;
    const opacity = (options && options.opacity) || 1.0;
    const fillOpacity = (options && options.fillOpacity) || 1.0;

    const group = window.L.layerGroup();

    if (stops && stops.length) {
        for (let i = 0; i < stops.length; i++) {
            const s = stops[i];
            const marker = window.rt.createHighlightedStopMarker(s, "rtStopsPane", {
                radius,
                color,
                weight,
                opacity,
                fillColor,
                fillOpacity
            });
            if (marker) marker.addTo(group);
        }
    }

    group.addTo(window.rt._itStopsLayer);
    window.rt._itStopGroups[id] = group;
};

// -----------------------------------------------------------------------------
// Route solution stops layer (paradas destacadas de la solución seleccionada)
// -----------------------------------------------------------------------------
window.rt = window.rt || {};

window.rt.ensureRouteStopsLayer = function () {
    const map = window.__rtLeafletMap;
    if (!map || !window.L) return false;

    if (!map.getPane("rtRouteStopsPane")) {
        const pane = map.createPane("rtRouteStopsPane");
        pane.style.zIndex = 752;
        pane.style.pointerEvents = "none";
    }

    if (!window.rt._routeStopsLayer) {
        window.rt._routeStopsLayer = window.L.layerGroup().addTo(map);
    }
    if (!window.rt._routeStopGroups) window.rt._routeStopGroups = {};
    return true;
};

window.rt.drawRouteStops = function (id, stops, options) {
    if (!window.rt.ensureRouteStopsLayer()) return;

    if (window.rt._routeStopGroups[id]) {
        window.rt._routeStopsLayer.removeLayer(window.rt._routeStopGroups[id]);
        delete window.rt._routeStopGroups[id];
    }

    if (!stops || !stops.length) return;

    const radius = (options && options.radius) || 8;
    const color = (options && options.color) || "#0f172a";
    const fillColor = (options && options.fillColor) || "#22c55e";
    const weight = (options && options.weight) || 2;
    const opacity = (options && options.opacity) || 1.0;
    const fillOpacity = (options && options.fillOpacity) || 1.0;

    const group = window.L.layerGroup();

    for (let i = 0; i < stops.length; i++) {
        const s = stops[i];
        const marker = window.rt.createHighlightedStopMarker(s, "rtRouteStopsPane", {
            radius,
            color,
            weight,
            opacity,
            fillColor,
            fillOpacity
        });
        if (marker) marker.addTo(group);
    }

    group.addTo(window.rt._routeStopsLayer);
    window.rt._routeStopGroups[id] = group;
};

window.rt.clearRouteStops = function (id) {
    if (!window.rt.ensureRouteStopsLayer()) return;

    if (id) {
        const group = window.rt._routeStopGroups && window.rt._routeStopGroups[id];
        if (group) {
            window.rt._routeStopsLayer.removeLayer(group);
            delete window.rt._routeStopGroups[id];
        }
        return;
    }

    window.rt._routeStopsLayer.clearLayers();
    window.rt._routeStopGroups = {};
};


// -----------------------------------------------------------------------------
// Origin / destination draggable markers
// -----------------------------------------------------------------------------
window.rt = window.rt || {};

window.rt.ensureEndpointLayer = function () {
    const map = window.__rtLeafletMap;
    if (!map || !window.L) return false;

    if (!map.getPane("rtEndpointPane")) {
        const pane = map.createPane("rtEndpointPane");
        pane.style.zIndex = 780;
    }

    if (!window.rt._endpointLayer) {
        window.rt._endpointLayer = window.L.layerGroup().addTo(map);
    }
    if (!window.rt._endpointMarkers) window.rt._endpointMarkers = {};
    return true;
};

window.rt.setEndpointMarker = function (kind, lat, lon, options) {
    if (!window.rt.ensureEndpointLayer()) return;

    if (window.rt._endpointMarkers[kind]) {
        window.rt._endpointLayer.removeLayer(window.rt._endpointMarkers[kind]);
        delete window.rt._endpointMarkers[kind];
    }

    if (typeof lat !== "number" || typeof lon !== "number") return;

    const color = (options && options.color) || "#22c55e";
    const title = (options && options.title) || "";
    const cssKind = kind === "destination" ? "destination" : "origin";
    const requestedSize = (options && options.size) || 18;
    const size = window.rt.isMobileLayout && window.rt.isMobileLayout()
        ? Math.max(24, requestedSize)
        : requestedSize;

    const icon = window.L.divIcon({
        className: "rt-endpoint-icon",
        html: `<div class="rt-endpoint-marker rt-endpoint-marker--${cssKind}" style="--endpoint-color:${color}; width:${size}px; height:${size}px;"></div>`,
        iconSize: [size, size],
        iconAnchor: [size / 2, size / 2]
    });

    const marker = window.L.marker([lat, lon], {
        pane: "rtEndpointPane",
        draggable: true,
        autoPan: true,
        icon,
        title
    });

    const stopEvent = function (e) {
        if (!e || !e.originalEvent) return;
        if (e.originalEvent.preventDefault) e.originalEvent.preventDefault();
        if (e.originalEvent.stopPropagation) e.originalEvent.stopPropagation();
        if (e.originalEvent.stopImmediatePropagation) e.originalEvent.stopImmediatePropagation();
    };

    marker.on("click", stopEvent);
    marker.on("mousedown", stopEvent);

    marker.on("dragend", function (e) {
        try {
            const pos = e.target.getLatLng();
            if (window.rt._dotNetHelper) {
                window.rt._dotNetHelper.invokeMethodAsync("OnEndpointDragged", kind, pos.lat, pos.lng);
            }
        } catch (err) { }
    });

    marker.addTo(window.rt._endpointLayer);
    window.rt._endpointMarkers[kind] = marker;
};

window.rt.clearEndpointMarkers = function (kind) {
    if (!window.rt.ensureEndpointLayer()) return;

    if (kind) {
        const marker = window.rt._endpointMarkers && window.rt._endpointMarkers[kind];
        if (marker) {
            window.rt._endpointLayer.removeLayer(marker);
            delete window.rt._endpointMarkers[kind];
        }
        return;
    }

    window.rt._endpointLayer.clearLayers();
    window.rt._endpointMarkers = {};
};


// -----------------------------------------------------------------------------
// Punto Z y lugares cercanos del callejero
// -----------------------------------------------------------------------------
window.rt.ensureInspectorLayer = function () {
    const map = window.__rtLeafletMap;
    if (!map || !window.L) return false;

    if (!map.getPane("rtInspectorPane")) {
        const pane = map.createPane("rtInspectorPane");
        pane.style.zIndex = 790;
    }

    const inspectorTooltipPane = map.getPane("rtInspectorTooltipPane")
        || map.createPane("rtInspectorTooltipPane");
    inspectorTooltipPane.style.zIndex = 10000;
    inspectorTooltipPane.style.pointerEvents = "none";

    if (!window.rt._inspectorLayer) {
        window.rt._inspectorLayer = window.L.layerGroup().addTo(map);
    }

    return true;
};

window.rt.setInspectorMarkers = function (lat, lon, places) {
    if (!window.rt.ensureInspectorLayer()) return;

    window.rt._inspectorLayer.clearLayers();

    const zLat = Number(lat);
    const zLon = Number(lon);
    if (!Number.isFinite(zLat) || !Number.isFinite(zLon)) return;

    const puntoZ = window.L.circleMarker([zLat, zLon], {
        pane: "rtInspectorPane",
        radius: 12,
        color: "#713f12",
        weight: 3,
        opacity: 1,
        fillColor: "#fde047",
        fillOpacity: 0.42,
        bubblingMouseEvents: false
    });
    puntoZ.bindTooltip("Punto Z", {
        pane: "rtInspectorTooltipPane",
        direction: "top",
        offset: [0, -16],
        opacity: 0.95,
        className: "rt-inspector-tooltip"
    });
    puntoZ.addTo(window.rt._inspectorLayer);

    const lugares = Array.isArray(places) ? places.slice(0, 3) : [];
    const coincidencias = {};
    lugares.forEach(function (place) {
        const placeLat = Number(place && place.latitud);
        const placeLon = Number(place && place.longitud);
        if (!Number.isFinite(placeLat) || !Number.isFinite(placeLon)) return;

        const clave = `${placeLat.toFixed(6)}|${placeLon.toFixed(6)}`;
        coincidencias[clave] = (coincidencias[clave] || 0) + 1;
    });

    const posicionCoincidente = {};
    lugares.forEach(function (place, index) {
        const placeLat = Number(place && place.latitud);
        const placeLon = Number(place && place.longitud);
        if (!Number.isFinite(placeLat) || !Number.isFinite(placeLon)) return;

        const clave = `${placeLat.toFixed(6)}|${placeLon.toFixed(6)}`;
        const totalCoincidentes = coincidencias[clave] || 1;
        const posicion = posicionCoincidente[clave] || 0;
        posicionCoincidente[clave] = posicion + 1;

        let offsetX = 0;
        if (totalCoincidentes === 2)
            offsetX = posicion === 0 ? -14 : 14;
        else if (totalCoincidentes >= 3)
            offsetX = [-26, 0, 26][Math.min(posicion, 2)];

        window.L.polyline(
            [[zLat, zLon], [placeLat, placeLon]],
            {
                pane: "rtInspectorPane",
                color: "#0f766e",
                weight: 1.5,
                opacity: 0.75,
                dashArray: "4 5",
                interactive: false
            })
            .addTo(window.rt._inspectorLayer);

        const numero = index + 1;
        const size = 24;
        const icon = window.L.divIcon({
            className: "rt-inspector-place-icon",
            html: `<span class="rt-inspector-place-marker">${numero}</span>`,
            iconSize: [size, size],
            iconAnchor: [(size / 2) - offsetX, size / 2]
        });

        const texto = place && typeof place.texto === "string"
            ? place.texto
            : `Lugar ${numero}`;
        const marker = window.L.marker([placeLat, placeLon], {
            pane: "rtInspectorPane",
            icon,
            title: texto,
            keyboard: false,
            bubblingMouseEvents: false,
            zIndexOffset: 20 + numero
        });

        const tooltip = document.createElement("span");
        tooltip.textContent = texto;
        marker.bindTooltip(tooltip, {
            pane: "rtInspectorTooltipPane",
            direction: "top",
            offset: [offsetX, -24],
            opacity: 0.95,
            className: "rt-inspector-tooltip"
        });
        marker.addTo(window.rt._inspectorLayer);
    });
};

window.rt.clearInspectorMarkers = function () {
    if (window.rt._inspectorLayer) {
        window.rt._inspectorLayer.clearLayers();
    }
};


window.rt = window.rt || {};

window.rt.scrollToHorarioProximo = function () {
    const el = document.getElementById("horario-proximo-anchor");
    if (!el) return;

    el.scrollIntoView({
        block: "center",
        behavior: "auto"
    });
};

window.rt.initDraggablePopups = function () {
    if (window.rt._draggablePopupsReady) return;

    window.rt._draggablePopupsReady = true;
    window.rt._draggablePopupPositions = window.rt._draggablePopupPositions || {};
    window.rt._draggablePopupZCounter = Number.isFinite(window.rt._draggablePopupZCounter)
        ? window.rt._draggablePopupZCounter
        : 1200;

    const padding = 8;
    const edgePeekX = 28;
    const edgePeekY = 42;

    function dragKey(popup) {
        return popup && (popup.dataset.dragKey || popup.id || "");
    }

    function clamp(value, min, max) {
        if (max < min) return min;
        return Math.min(Math.max(value, min), max);
    }

    function parseZ(value) {
        const parsed = Number.parseInt(value, 10);
        return Number.isFinite(parsed) ? parsed : null;
    }

    function ensurePopupZIndex(popup) {
        if (!popup) return;

        const inlineZ = parseZ(popup.style.zIndex);
        if (inlineZ != null) {
            window.rt._draggablePopupZCounter = Math.max(window.rt._draggablePopupZCounter, inlineZ);
            return;
        }

        const computedZ = parseZ(window.getComputedStyle(popup).zIndex);
        if (computedZ != null) {
            window.rt._draggablePopupZCounter = Math.max(window.rt._draggablePopupZCounter, computedZ);
        }

        window.rt._draggablePopupZCounter += 1;
        popup.style.zIndex = String(window.rt._draggablePopupZCounter);
    }

    function bringPopupToFront(popup) {
        if (!popup) return;

        ensurePopupZIndex(popup);
        window.rt._draggablePopupZCounter += 1;
        popup.style.zIndex = String(window.rt._draggablePopupZCounter);
    }

    function clampPosition(left, top, width, height) {
        return {
            left: clamp(left, edgePeekX - width, window.innerWidth - edgePeekX),
            top: clamp(top, edgePeekY - height, window.innerHeight - edgePeekY)
        };
    }

    function constrainFreeHeight(popup, height) {
        if (!popup || !popup.classList.contains("route-panel-overlay")) return null;

        const maxHeight = Math.max(260, window.innerHeight - (padding * 2));
        return Math.min(height || maxHeight, maxHeight);
    }

    function isHandleRecoverablyHidden(popup) {
        if (!popup) return false;

        const handle = popup.querySelector(".draggable-popup-handle");
        if (!handle) return false;

        const rect = handle.getBoundingClientRect();
        const visibleTop = Math.max(rect.top, 0);
        const visibleBottom = Math.min(rect.bottom, window.innerHeight);
        const visibleHeight = Math.max(0, visibleBottom - visibleTop);

        return visibleHeight < 18;
    }

    function setFreePosition(popup, left, top, width, height) {
        popup.classList.add("draggable-popup--free");
        popup.style.setProperty("--drag-left", `${left}px`);
        popup.style.setProperty("--drag-top", `${top}px`);
        popup.style.right = "auto";
        popup.style.bottom = "auto";
        popup.style.width = `${width}px`;

        const constrainedHeight = constrainFreeHeight(popup, height);
        if (constrainedHeight != null) {
            popup.style.height = `${constrainedHeight}px`;
            popup.style.maxHeight = `${constrainedHeight}px`;
        } else {
            popup.style.removeProperty("height");
            popup.style.removeProperty("max-height");
        }
    }

    function savePosition(popup) {
        const key = dragKey(popup);
        if (!key) return;

        const rect = popup.getBoundingClientRect();
        window.rt._draggablePopupPositions[key] = {
            left: rect.left,
            top: rect.top,
            width: rect.width,
            height: rect.height
        };
    }

    function applySavedPositions() {
        document.querySelectorAll(".draggable-popup").forEach((popup) => {
            const key = dragKey(popup);
            const saved = key ? window.rt._draggablePopupPositions[key] : null;

            if (saved && !popup.classList.contains("draggable-popup--free")) {
                const rect = popup.getBoundingClientRect();
                const width = saved.width || rect.width;
                const height = saved.height || rect.height;
                const pos = clampPosition(saved.left, saved.top, width, height);
                setFreePosition(popup, pos.left, pos.top, width, height);
            }

            ensurePopupZIndex(popup);
        });
    }

    document.addEventListener("pointerdown", function (event) {
        const popup = event.target && event.target.closest
            ? event.target.closest(".draggable-popup")
            : null;
        if (!popup) return;

        bringPopupToFront(popup);

        if (window.rt.isMobileLayout && window.rt.isMobileLayout()) return;

        if (event.button !== 0) return;
        if (event.target.closest("button,a,input,textarea,select,label")) return;

        const handle = event.target && event.target.closest
            ? event.target.closest(".draggable-popup-handle")
            : null;

        const fallbackWholePopup =
            !handle &&
            popup.classList.contains("draggable-popup--free") &&
            isHandleRecoverablyHidden(popup);

        if (!handle && !fallbackWholePopup) return;

        const dragSurface = handle || popup;

        event.preventDefault();

        const rect = popup.getBoundingClientRect();
        const width = rect.width;
        const height = rect.height;
        const startX = event.clientX;
        const startY = event.clientY;
        const startLeft = rect.left;
        const startTop = rect.top;

        const initial = clampPosition(startLeft, startTop, width, height);
        setFreePosition(popup, initial.left, initial.top, width, height);
        popup.classList.add("draggable-popup--dragging");

        try { dragSurface.setPointerCapture(event.pointerId); } catch { }

        function onMove(moveEvent) {
            const pos = clampPosition(
                startLeft + moveEvent.clientX - startX,
                startTop + moveEvent.clientY - startY,
                width,
                height);

            popup.style.setProperty("--drag-left", `${pos.left}px`);
            popup.style.setProperty("--drag-top", `${pos.top}px`);
        }

        function onUp(upEvent) {
            document.removeEventListener("pointermove", onMove, true);
            document.removeEventListener("pointerup", onUp, true);
            document.removeEventListener("pointercancel", onUp, true);

            popup.classList.remove("draggable-popup--dragging");
            savePosition(popup);

            try { dragSurface.releasePointerCapture(upEvent.pointerId); } catch { }
        }

        document.addEventListener("pointermove", onMove, true);
        document.addEventListener("pointerup", onUp, true);
        document.addEventListener("pointercancel", onUp, true);
    }, true);

    document.addEventListener("focusin", function (event) {
        const popup = event.target && event.target.closest
            ? event.target.closest(".draggable-popup")
            : null;
        if (!popup) return;
        bringPopupToFront(popup);
    }, true);

    window.addEventListener("resize", function () {
        document.querySelectorAll(".draggable-popup--free").forEach((popup) => {
            const rect = popup.getBoundingClientRect();
            const pos = clampPosition(rect.left, rect.top, rect.width, rect.height);
            setFreePosition(popup, pos.left, pos.top, rect.width, rect.height);
            savePosition(popup);
        });
    });

    const observer = new MutationObserver(applySavedPositions);
    observer.observe(document.body, { childList: true, subtree: true });
    applySavedPositions();
};

window.rt.saveLocalValueResult = function (key, value) {
    try {
        const storage = window.localStorage;
        if (!storage) {
            return {
                ok: false,
                errorName: "StorageUnavailable",
                errorMessage: "El navegador no expone localStorage."
            };
        }

        storage.setItem(key, value ?? "");
        return { ok: true };
    } catch (error) {
        return {
            ok: false,
            errorName: error?.name ? String(error.name) : "StorageError",
            errorMessage: error?.message ? String(error.message) : String(error ?? "Error desconocido al guardar en localStorage.")
        };
    }
};

window.rt.loadLocalValueResult = function (key) {
    try {
        const storage = window.localStorage;
        if (!storage) {
            return {
                ok: false,
                found: false,
                value: null,
                errorName: "StorageUnavailable",
                errorMessage: "El navegador no expone localStorage."
            };
        }

        const value = storage.getItem(key);
        return {
            ok: true,
            found: value !== null,
            value
        };
    } catch (error) {
        return {
            ok: false,
            found: false,
            value: null,
            errorName: error?.name ? String(error.name) : "StorageError",
            errorMessage: error?.message ? String(error.message) : String(error ?? "Error desconocido al leer localStorage.")
        };
    }
};

window.rt.saveLocalValue = function (key, value) {
    window.rt.saveLocalValueResult(key, value);
};

window.rt.loadLocalValue = function (key) {
    const result = window.rt.loadLocalValueResult(key);
    return result.ok ? result.value : null;
};

window.rt.clearLocalBrowserData = async function () {
    const deleteIndexedDb = function (name) {
        return new Promise((resolve) => {
            try {
                const req = window.indexedDB.deleteDatabase(name);
                req.onsuccess = resolve;
                req.onerror = resolve;
                req.onblocked = resolve;
            } catch {
                resolve();
            }
        });
    };

    try { window.localStorage?.clear(); } catch { }
    try { window.sessionStorage?.clear(); } catch { }

    try {
        if (window.caches?.keys) {
            const keys = await window.caches.keys();
            await Promise.all(keys.map((key) => window.caches.delete(key)));
        }
    } catch { }

    try {
        if (navigator.serviceWorker?.getRegistrations) {
            const registrations = await navigator.serviceWorker.getRegistrations();
            await Promise.all(registrations.map((registration) => registration.unregister()));
        }
    } catch { }

    try {
        if (window.indexedDB?.databases) {
            const databases = await window.indexedDB.databases();
            await Promise.all((databases || [])
                .filter((database) => database && database.name)
                .map((database) => deleteIndexedDb(database.name)));
        }
    } catch { }
};

window.rt.reloadPage = function () {
    window.location.reload();
};
