using System.Collections.Concurrent;
using System.Globalization;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using LeafletForBlazor;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using RutasDBUS.Modelos.Planificacion;
using RutasDBUS.Modelos.RedBus;
using RutasDBUS.Servicios;
using RutasDBUS.Servicios.Caminata;
using RutasDBUS.Servicios.Horarios;
using RutasDBUS.Servicios.Planificacion;
using RutasDBUS.Servicios.RedBus;

namespace RutasDBUS.Components.Pages;

public partial class PlanificadorRutas : ComponentBase, IDisposable
{
        private DotNetObjectReference<PlanificadorRutas>? _dotRef;
    
        private enum HorarioOrden
        {
            PorHora = 0,
            PorItinerario = 1
        }
    
        private HorarioOrden _horarioOrden = HorarioOrden.PorHora;
    
        private string HorarioOrdenIcono =>
            _horarioOrden == HorarioOrden.PorHora ? "\u23F0" : "\U0001F68C";
    
        private string HorarioOrdenTooltip =>
            _horarioOrden == HorarioOrden.PorHora
                ? "Orden actual: por hora. Pulsar para ordenar por itinerario"
                : "Orden actual: por itinerario. Pulsar para ordenar por hora";
    
    
        /// <summary>
        /// Gestiona after render.
        /// </summary>
        protected override async Task OnAfterRenderAsync(bool firstRender)
        {
            if (firstRender)
            {
                _dotRef = DotNetObjectReference.Create(this);
    
                try { await JS.InvokeVoidAsync("initBrilloMapa", _dotRef); } catch (Exception ex) { Console.WriteLine("initBrilloMapa: " + ex); }
                try { await JS.InvokeVoidAsync("initAltInspector", _dotRef); } catch (Exception ex) { Console.WriteLine("initAltInspector: " + ex); }
                try { await JS.InvokeVoidAsync("rt.setDotNetHelper", _dotRef); } catch (Exception ex) { Console.WriteLine("rt.setDotNetHelper: " + ex); }
                try { await JS.InvokeVoidAsync("rt.blockLeafletDoubleClick"); } catch (Exception ex) { Console.WriteLine("rt.blockLeafletDoubleClick: " + ex); }
                try { await JS.InvokeVoidAsync("rt.initDraggablePopups"); } catch (Exception ex) { Console.WriteLine("rt.initDraggablePopups: " + ex); }
                await CargarConfiguracionLocalAsync();
                await AsegurarHistorialLocalCargadoAsync();
                await CargarHistorialCompartidoAsync();
                await CargarModelosHorariosAsync();
    
                if (_usarHoraSimulada)
                    await RefrescarHoraUIAsync();
                else
                    StartClock();

                if (_usarHorariosTeoricos)
                    await EnsureHorariosLoadedAsync();
    
                _tareaCargaRed = RedBusServicio.CargarRedAsync();
                StateHasChanged();
                return;
            }
    
            _tareaCargaRed ??= RedBusServicio.CargarRedAsync();
            if (!_tareaCargaRed.IsCompleted) return;
    
            await _tareaCargaRed;
    
            if (RedBusServicio.Paradas.Count > 0)
                EnsureBusIndexBuilt();
    
            if (!_zoomLimitsAplicados && _mapa is not null)
            {
                _zoomLimitsAplicados = true;
                await JS.InvokeVoidAsync("rt.setZoomLimits", 3, 19);
            }
    
            try { await JS.InvokeVoidAsync("rt.addZoomIndicator", new { fakeZoom19 = true }); } catch { }
    
            if (!_paradasPintadas && !_pintandoParadas && _mapa is not null && RedBusServicio.Paradas.Count > 0)
            {
                _pintandoParadas = true;
                try
                {
                    if (_mostrarParadas)
                        await DibujarParadasAsync();
                    else
                        await OcultarParadasAsync();
    
                    _paradasPintadas = true;
                }
                finally
                {
                    _pintandoParadas = false;
                }
    
                StateHasChanged();
    
                if (_itItems.Count == 0 && RedBusServicio.Paradas.Count > 0)
                {
                    BuildItinerariosDropdown();
                    SincronizarSeleccionItinerarioConFiltro();
    
                    StateHasChanged();
                }
            }
    
            // ✅ ESTO VA FUERA
            if (_scrollHorariosPendiente)
            {
                _scrollHorariosPendiente = false;
                try { await JS.InvokeVoidAsync("rt.scrollToHorarioProximo"); } catch { }
            }
        }
        private bool _scrollHorariosPendiente = false;
        // =============================
        // PREVIEWS ITINERARIOS (por sentido real)
        // =============================
    
        // ids reales pintados (subIds): it_xxx_IV_I, it_xxx_IV_V, it_xxx_I_3, etc.
        private readonly HashSet<string> _itPreviewActivos = new(StringComparer.OrdinalIgnoreCase);
    
        // Mapa: clave lógica "26.12-I" -> ids reales pintados (pueden ser 1 o varios)
        private readonly Dictionary<string, HashSet<string>> _itPreviewIdsPorClave =
            new(StringComparer.OrdinalIgnoreCase);
    
        /// <summary>
        /// Gestiona clave.
        /// </summary>
        private static string ItClave(InfoVariante v)
        {
            var linea = v.Linea?.Codigo?.ToString() ?? "?";
            var itin = v.CodigoItinerario.ToString(CultureInfo.InvariantCulture);
            var sentido = NormalizarSentidoCodigo(v.Sentido?.Codigo); // I / V
            return $"{linea}.{itin}-{sentido}";
        }
    
        /// <summary>
        /// Gestiona vista previa.
        /// </summary>
        private void RegistrarPreview(string clave, string subId)
        {
            _itPreviewActivos.Add(subId);
    
            if (!_itPreviewIdsPorClave.TryGetValue(clave, out var set))
            {
                set = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
                _itPreviewIdsPorClave[clave] = set;
            }
            set.Add(subId);
        }
        /// <summary>
        /// Obtiene seleccionada it.
        /// </summary>
        private ElementoItinerario? GetSelectedIt()
            => _itItems.FirstOrDefault(x => x.Id.Equals(_itSeleccionadoId, StringComparison.OrdinalIgnoreCase));
    
    
    /// <summary>
    /// Pinta it seleccionado.
    /// </summary>
    private async Task PintarItSeleccionado()
    {
        var it = GetSelectedIt();
        if (it is null) return;
    
        await PintarItItemAsync(it);
    }
        /// <summary>
        /// Quita it seleccionado.
        /// </summary>
        private async Task QuitarItSeleccionado()
        {
            var it = GetSelectedIt();
            if (it is null) return;
    
            // claves a borrar:
            // - si seleccionas IV => borra I y V
            // - si seleccionas I/V => borra solo ese sentido
            var claves = new List<string>();
    
            if (it.Variantes.Count == 2)
            {
                claves.Add(ItClave(it.Variantes[0])); // I
                claves.Add(ItClave(it.Variantes[1])); // V
            }
            else if (it.Variantes.Count == 1)
            {
                claves.Add(ItClave(it.Variantes[0])); // solo I o solo V
            }
            else
            {
                return;
            }
    
            foreach (var clave in claves)
            {
                if (!_itPreviewIdsPorClave.TryGetValue(clave, out var ids) || ids.Count == 0)
                    continue;
    
                // borrar TODOS los subIds reales asociados a ese sentido (aunque se pintaran desde IV)
                foreach (var id in ids.ToList())
                {
                    await JS.InvokeVoidAsync("rt.drawPreviewLine", id, new List<double[]>(), new { });
                    await JS.InvokeVoidAsync("rt.toggleItStops", id, new List<object>(), new { });
                    QuitarParadasItinerarioVisibles(id);
    
                    _itPreviewActivos.Remove(id);
                    ids.Remove(id);
    
                    // si el popup abierto corresponde a lo borrado, cerrarlo
                    if (!string.IsNullOrWhiteSpace(_itinerarioClicadoSubId) &&
                        string.Equals(_itinerarioClicadoSubId, id, StringComparison.OrdinalIgnoreCase))
                    {
                        _itinerarioClicado = null;
                        _itinerarioClicadoSubId = null;
                    }
                }
    
                if (ids.Count == 0)
                    _itPreviewIdsPorClave.Remove(clave);
            }
        }
    
        /// <summary>
        /// Gestiona todas las vistas previas.
        /// </summary>
        private async Task CerrarTodasLasPreviewsAsync()
        {
            try { await JS.InvokeVoidAsync("rt.clearPreview"); } catch { }
    
            _itPreviewActivos.Clear();
            _itPreviewIdsPorClave.Clear();
            LimpiarParadasItinerarioVisibles();
    
            _previewLineasActivas.Clear(); // <- esto ya lo usas para los "full_..."
        }
    
    
    
    
        private bool _inspectorActivo = false;
    
        private bool _popupInspectorVisible = false;
        private double _inspectorLat, _inspectorLon;
        private double? _inspectorElev;
        private double? _inspectorElevIdee;
    
    
        /// <summary>
        /// Gestiona vista previa itinerario clic.
        /// </summary>
        [JSInvokable]
        public async Task OnPreviewItinerarioClick(string previewId)
        {
            if (string.IsNullOrWhiteSpace(previewId))
                return;
    
            try
            {
                // Cerrar popup de parada si está abierto
    
    
                // Guardamos el subId real pulsado (ej: it_xxx_I o full_...)
                _itinerarioClicadoSubId = previewId;
    
                // 1) Si viene de previews del desplegable/filtro (it_...)
                //    Puede venir:
                //    - id directo: "it_26_12_I_3"
                //    - subId combinado: "it_26_12_IV_I" / "it_26_12_IV_V"
                ElementoItinerario? encontrado = null;
                
                // coincidencia exacta
                encontrado = _itItems.FirstOrDefault(x =>
                    x.Id.Equals(previewId, StringComparison.OrdinalIgnoreCase));
    
                // si no, probar quitando sufijo _I / _V de subId combinado
                if (encontrado is null)
                {
                    if (previewId.EndsWith("_I", StringComparison.OrdinalIgnoreCase) ||
                        previewId.EndsWith("_V", StringComparison.OrdinalIgnoreCase))
                    {
                        var baseId = previewId.Substring(0, previewId.Length - 2);
                        encontrado = _itItems.FirstOrDefault(x =>
                            x.Id.Equals(baseId, StringComparison.OrdinalIgnoreCase));
                    }
                }
    
                // 2) Si no se encuentra, puede ser preview azul de tramo de ruta: "full_..."
                //    En ese caso intentamos construir info simple desde el id.
                if (encontrado is null && previewId.StartsWith("full_", StringComparison.OrdinalIgnoreCase))
                {
                    // formato: full_{linea}_{itin}_{sentido}_{variante}
                    var parts = previewId.Split('_', StringSplitOptions.RemoveEmptyEntries);
    
                    if (parts.Length >= 5)
                    {
                        var linea = parts[1];
                        var itinTxt = parts[2];
                        var sentidoRaw = parts[3]; // "Ida"/"Vuelta" o código
                        var varTxt = parts[4];
    
                        int.TryParse(itinTxt, out var itinNum);
    
                        var sentido = NormalizarSentidoCodigo(sentidoRaw);
    
                        var info = _infoVariantes.FirstOrDefault(v =>
                            string.Equals(v.Linea.Codigo?.ToString(), linea, StringComparison.OrdinalIgnoreCase) &&
                            v.CodigoItinerario == itinNum &&
                            string.Equals(NormalizarSentidoCodigo(v.Sentido?.Codigo), sentido, StringComparison.OrdinalIgnoreCase) &&
                            string.Equals(v.Variante.Codigo.ToString(), varTxt, StringComparison.OrdinalIgnoreCase));
    
                        if (info is not null)
                        {
                            encontrado = new ElementoItinerario
                            {
                                Id = previewId,
                                Text = $"{linea}.{itinNum}-{sentido} — {info.Linea.Nombre}",
                                Variantes = new List<InfoVariante> { info }
                            };
                        }
                    }
                }
    
                _itinerarioClicado = encontrado;
    
                await InvokeAsync(StateHasChanged);
            }
            catch
            {
                // silencioso
            }
        }

        /// <summary>
        /// Abre la información de una parada destacada pulsada directamente sobre la solución o el itinerario.
        /// </summary>
        [JSInvokable]
        public async Task OnHighlightedStopClick(string codigoParada)
        {
            if (string.IsNullOrWhiteSpace(codigoParada))
                return;

            if (!_paradaByCode.TryGetValue(codigoParada, out var parada))
                return;

            await MostrarPopupParadaAsync(parada);
        }
    
        /// <summary>
        /// Gestiona inspector activo.
        /// </summary>
        [JSInvokable]
        public void SetInspectorActivo(bool activo)
        {
            _inspectorActivo = activo;
            StateHasChanged();
        }
    
    
    
        /// <summary>
        /// Gestiona raton move mapa.
        /// </summary>
        private void OnMouseMoveMapa(RealTimeMap.ClicksMapArgs args)
        {
            _coordsMouse = (args.location.latitude, args.location.longitude);
            StateHasChanged();
        }
    
        /// <summary>
        /// Gestiona brillo.
        /// </summary>
        [JSInvokable]
        public async Task AlternarBrillo()
        {
            _nivelBrilloMapa++;
            if (_nivelBrilloMapa > 4) _nivelBrilloMapa = 0;
            await PersistirConfiguracionAsync();
            StateHasChanged();
        }
    
    /// <summary>
    /// Dibuja paradas.
    /// </summary>
    private async Task DibujarParadasAsync()
    {
        if (_mapa is null) return;
    
        await _mapa.Geometric.DisplayPointsFromArray.deleteAllPoints();
    
    
        var simboloParada = new RealTimeMap.PointSymbol
        {
            color = "black",
            fillColor = "yellow",
            radius = 5,
            opacity = 0.9,
            fillOpacity = 0.8,
            weight = 1
        };
    
    
    
    
        foreach (var parada in RedBusServicio.Paradas)
        {
            await _mapa.Geometric.DisplayPointsFromArray.addPoint(
                new[] { parada.Latitud, parada.Longitud },
                simboloParada);
        }

        await SincronizarMarcadoresOrigenDestinoJsAsync();
    }
        // =========================================================
        // Walking caché (OSRM/Valhalla)
        // Nota: el "dur" devuelto NO se usa para tiempos finales.
        //       El tiempo a pie SIEMPRE sale de EstimarDuracionPieSegundos(dist)
        // =========================================================
    
        private async Task<(double[,]? line, double dist, double dur)> WalkOsrmCached(double lat1, double lon1, double lat2, double lon2)
        {
            var key = WalkKey("osrm", lat1, lon1, lat2, lon2);
            if (_walkCache.TryGetValue(key, out var v))
                return v;
    
            try
            {
                var (line, dist, dur) = await EsrmCliente.ObtenerRutaAPieAsync(lat1, lon1, lat2, lon2);
    
                // ✅ Solo cachear si es un resultado "bueno"
                if (line is not null && dist > 0)
                {
                    v = (line, dist, dur);
                    _walkCache[key] = v;
                    return v;
                }
    
                // ❌ si viene mal, NO lo cacheamos
                return (null, 0, 0);
            }
            catch
            {
                // ❌ no cachear fallos
                return (null, 0, 0);
            }
        }
    
        private async Task<(double[,]? line, double dist, double dur)> WalkValhallaCached(double lat1, double lon1, double lat2, double lon2)
        {
            var key = WalkKey("valhalla", lat1, lon1, lat2, lon2);
            if (_walkCache.TryGetValue(key, out var v))
                return v;
    
            try
            {
                var (line, dist, dur) = await ClienteValhalla.ObtenerRutaAsync(lat1, lon1, lat2, lon2, costing: "pedestrian");
    
                // ✅ Solo cachear si es un resultado "bueno"
                if (line is not null && dist > 0)
                {
                    v = (line, dist, dur);
                    _walkCache[key] = v;
                    return v;
                }
    
                return (null, 0, 0);
            }
            catch
            {
                return (null, 0, 0);
            }
        }
    
    
        /// <summary>
        /// Gestiona linea recta origen destino.
        /// </summary>
        private double DistanciaLineaRectaOrigenDestino()
        {
            if (_coordenadasOrigen is null || _coordenadasDestino is null) return 0;
    
            return CalcularDistanciaMetros(
                _coordenadasOrigen[0], _coordenadasOrigen[1],
                _coordenadasDestino[0], _coordenadasDestino[1]);
        }
    
}
