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
{
        /// <summary>
        /// Gestiona hmm.
        /// </summary>
        private static string HHmm(DateTime dt) => dt.ToString("HH:mm", CultureInfo.InvariantCulture);
    
        /// <summary>
        /// Añade seconds safe.
        /// </summary>
        private static DateTime AddSecondsSafe(DateTime dt, double seconds)
        {
            if (seconds <= 0) return dt;
            return dt.AddSeconds(seconds);
        }
    
        // Para sacar de InfoVariante lo que necesitamos para horarios
        /// <summary>
        /// Gestiona codigo str.
        /// </summary>
        private static string LineaCodigoStr(InfoVariante v) => v?.Linea?.Codigo?.ToString() ?? "?";
    
        // =======================
        // HORARIOS (ZIP)
        // =======================
        private bool _usarHorariosTeoricos = true;
    
        private bool _horariosOk = false;
        private string? _horariosError = null;

        /// <summary>
        /// Sincroniza la penalizacion manual por parada con el modo horario activo.
        /// </summary>
        private void SincronizarSegundosPorParadaConHorarios()
        {
            if (_usarHorariosTeoricos)
            {
                _segundosPorParadaBus = 0;
                return;
            }

            if (_segundosPorParadaBus == 0 && _segundosPorParadaBusManual >= 0)
                _segundosPorParadaBus = _segundosPorParadaBusManual;
        }
    
        /// <summary>
        /// Gestiona lista horarios con actual.
        /// </summary>
        private List<ProximaSalidaVm> ConstruirListaHorariosConActual(
            List<(TimeOnly dep, string expId, string rutaId)> deps,
            TimeOnly now)
        {
            var ordenados = deps
                .OrderBy(x => x.dep)
                .ToList();
    
            int idxProximo = ordenados.FindIndex(x => x.dep >= now);
    
            var lista = new List<ProximaSalidaVm>(ordenados.Count);
    
            for (int i = 0; i < ordenados.Count; i++)
            {
                var d = ordenados[i];
    
                var itiClave = Horarios.GetLineaItinerarioSentidoText(d.expId, d.rutaId);
    
                lista.Add(new ProximaSalidaVm(
                    Hora: d.dep.ToString("HH:mm"),
                    LineaTxt: Horarios.GetLineaItinerarioSentidoNombreText(d.expId, d.rutaId),
                    RutaId: d.rutaId,
                    ExpId: d.expId,
                    EsProximo: i == idxProximo,
                    AnchorId: i == idxProximo ? "horario-proximo-anchor" : "",
                    ItinerarioClave: itiClave));
            }
    
            return lista;
        }
    
    
        /// <summary>
        /// Calcula el orden lógico de una línea visible del popup, respetando TB y enviando las líneas B al bloque 100.
        /// </summary>
        private static int ResolverOrdenLineaHorario(string? lineaVisible, string? rutaId)
        {
            var texto = (lineaVisible ?? "").Trim().ToUpperInvariant();

            var matchTb = Regex.Match(texto, @"^TB(\d+)$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
            if (matchTb.Success &&
                int.TryParse(matchTb.Groups[1].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var tb))
            {
                return tb;
            }

            var matchBuho = Regex.Match(texto, @"^B(\d+)$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
            if (matchBuho.Success &&
                int.TryParse(matchBuho.Groups[1].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var buho))
            {
                return 100 + buho;
            }

            if (int.TryParse(texto, NumberStyles.Integer, CultureInfo.InvariantCulture, out var numeroDirecto))
                return numeroDirecto;

            if (int.TryParse(rutaId, NumberStyles.Integer, CultureInfo.InvariantCulture, out var idRuta))
                return idRuta;

            var numeroExtraido = UtilidadesTransitoDbus.ExtraerNumeroLinea(texto);
            return numeroExtraido ?? int.MaxValue;
        }

        /// <summary>
        /// Extrae el orden lógico visible de línea, itinerario y sentido para los horarios del popup de parada.
        /// </summary>
        private static (int lineaOrden, string lineaVisible, int itinerarioOrden, string sentidoOrden) ParseClaveOrdenHorario(string? rutaId, string? lineaTxt)
        {
            if (string.IsNullOrWhiteSpace(lineaTxt))
                return (ResolverOrdenLineaHorario(null, rutaId), "", int.MaxValue, "?");

            var txt = lineaTxt.Trim();
            var separadorDestino = txt.IndexOf(" - ", StringComparison.Ordinal);
            if (separadorDestino > 0)
                txt = txt[..separadorDestino].Trim();

            var match = Regex.Match(
                txt,
                @"^(?<linea>[^.\s]+)\.(?<itin>\d+)-(?<sentido>[A-Za-z?]+)$",
                RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);

            if (!match.Success)
                return (ResolverOrdenLineaHorario(null, rutaId), "", int.MaxValue, "?");

            var lineaVisible = match.Groups["linea"].Value.Trim().ToUpperInvariant();
            var sentido = match.Groups["sentido"].Value.Trim().ToUpperInvariant();

            int itinerario = int.MaxValue;
            int.TryParse(match.Groups["itin"].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out itinerario);

            return (ResolverOrdenLineaHorario(lineaVisible, rutaId), lineaVisible, itinerario, sentido);
        }
    
        private IEnumerable<ProximaSalidaVm> NextDepsOrdenados
        {
            get
            {
                var ordenadosPorClave = _nextDeps
                    .Select(x => new
                    {
                        Item = x,
                        Orden = ParseClaveOrdenHorario(x.RutaId, x.LineaTxt)
                    });

                if (_horarioOrden == HorarioOrden.PorHora)
                {
                    return ordenadosPorClave
                        .OrderBy(x => x.Item.Hora, StringComparer.Ordinal)
                        .ThenBy(x => x.Orden.lineaOrden)
                        .ThenBy(x => x.Orden.lineaVisible, StringComparer.OrdinalIgnoreCase)
                        .ThenBy(x => x.Orden.itinerarioOrden)
                        .ThenBy(x => x.Orden.sentidoOrden, StringComparer.OrdinalIgnoreCase)
                        .Select(x => x.Item);
                }

                return ordenadosPorClave
                    .OrderBy(x => x.Orden.lineaOrden)
                    .ThenBy(x => x.Orden.lineaVisible, StringComparer.OrdinalIgnoreCase)
                    .ThenBy(x => x.Orden.itinerarioOrden)
                    .ThenBy(x => x.Orden.sentidoOrden, StringComparer.OrdinalIgnoreCase)
                    .ThenBy(x => x.Item.Hora, StringComparer.Ordinal)
                    .Select(x => x.Item);
            }
        }
    
        /// <summary>
        /// Alterna orden horarios.
        /// </summary>
        private void ToggleOrdenHorarios()
        {
            _horarioOrden = _horarioOrden == HorarioOrden.PorHora
                ? HorarioOrden.PorItinerario
                : HorarioOrden.PorHora;

            if (_horarioOrden == HorarioOrden.PorHora)
                _scrollHorariosPendiente = true;
        }
    
    
    
        // Próximas salidas para el popup de parada
        /// <summary>
        /// Inicializa una nueva instancia de la clase ProximaSalidaVm.
        /// </summary>
        private sealed record ProximaSalidaVm(
            string Hora,
            string LineaTxt,
            string RutaId,
            string ExpId,
            bool EsProximo,
            string AnchorId,
            string ItinerarioClave);
    
        private List<ProximaSalidaVm> _nextDeps = new();
        /// <summary>
        /// Gestiona toggle hora simulada.
        /// </summary>
        private async Task OnToggleHoraSimulada(ChangeEventArgs e)
        {
            _usarHoraSimulada = e?.Value is bool b && b;
    
            if (_usarHoraSimulada)
            {
                var ahora = DateTime.Now;
                _fechaSimulada = DateOnly.FromDateTime(ahora);
                _horaSimulada = TimeOnly.FromDateTime(ahora);
                StopClock();
            }
            else
            {
                StartClock();
            }
    
            await RefrescarHoraUIAsync();
            await PersistirConfiguracionAsync();
        }
    
        // ====== HORA (real / simulada) ======
        private bool _usarHoraSimulada = true;
        private TimeOnly _horaSimulada = TimeOnly.FromDateTime(DateTime.Now);
        private DateOnly _fechaSimulada = DateOnly.FromDateTime(DateTime.Now);
    
    
        /// <summary>
        /// Gestiona local.
        /// </summary>
        private DateTime AhoraLocal()
        {
            if (_usarHoraSimulada)
                return _fechaSimulada.ToDateTime(_horaSimulada);
    
            return DateTime.Now;
        }

        /// <summary>
        /// Inicializa la hora simulada con la hora real actual y la deja parada.
        /// </summary>
        protected override void OnInitialized()
        {
            SincronizarSegundosPorParadaConHorarios();

            if (!_usarHoraSimulada)
                return;

            var ahora = DateTime.Now;
            _fechaSimulada = DateOnly.FromDateTime(ahora);
            _horaSimulada = TimeOnly.FromDateTime(ahora);

            var es = new CultureInfo("es-ES");
            _horaAhoraTxt = es.TextInfo.ToTitleCase(ahora.ToString("ddd dd/MM HH:mm:ss", es));
        }
    
        /// <summary>
        /// Gestiona change fecha simulada.
        /// </summary>
        private async Task OnChangeFechaSimulada(ChangeEventArgs e)
        {
            var s = e?.Value?.ToString();
            if (!string.IsNullOrWhiteSpace(s) && DateOnly.TryParse(s, out var d))
                _fechaSimulada = d;
    
            if (_usarHoraSimulada)
                await RefrescarHoraUIAsync();

            await PersistirConfiguracionAsync();
        }
    
        /// <summary>
        /// Gestiona change hora simulada.
        /// </summary>
        private async Task OnChangeHoraSimulada(ChangeEventArgs e)
        {
            var s = e?.Value?.ToString();
            if (!string.IsNullOrWhiteSpace(s) && TimeOnly.TryParse(s, out var t))
                _horaSimulada = t;
    
            if (_usarHoraSimulada)
                await RefrescarHoraUIAsync();

            await PersistirConfiguracionAsync();
        }

        /// <summary>
        /// Ajusta la fecha y la hora simuladas al momento actual.
        /// </summary>
        private async Task IrAAhoraSimuladoAsync()
        {
            var ahora = DateTime.Now;
            _fechaSimulada = DateOnly.FromDateTime(ahora);
            _horaSimulada = TimeOnly.FromDateTime(ahora);

            if (_usarHoraSimulada)
                await RefrescarHoraUIAsync();

            await PersistirConfiguracionAsync();
            StateHasChanged();
        }
    
    
    
        private string _horaAhoraTxt = "";
    private PeriodicTimer? _clockTimer;
    private CancellationTokenSource? _clockCts;
    
    
        /// <summary>
        /// Gestiona clock.
        /// </summary>
        private void StartClock()
        {
            if (_clockTimer is not null) return;
    
            _clockCts = new CancellationTokenSource();
            _clockTimer = new PeriodicTimer(TimeSpan.FromSeconds(1));
    
            _ = Task.Run(async () =>
            {
                try
                {
                    while (await _clockTimer.WaitForNextTickAsync(_clockCts.Token))
                    {
                        // ✅ Solo actualizamos si NO estamos en simulada
                        if (_usarHoraSimulada) continue;
    
                        var now = DateTime.Now;
                        var es = new CultureInfo("es-ES");
                        _horaAhoraTxt = es.TextInfo.ToTitleCase(now.ToString("ddd dd/MM HH:mm:ss", es));
    
                        await InvokeAsync(StateHasChanged); // ✅ como antes
                    }
                }
                catch { /* cancel */ }
            });
        }
    
    
        /// <summary>
        /// Gestiona clock.
        /// </summary>
        private void StopClock()
        {
            _clockCts?.Cancel();
            _clockTimer?.Dispose();
            _clockCts?.Dispose();
    
            _clockTimer = null;
            _clockCts = null;
        }
    
        /// <summary>
        /// Actualiza hora ui.
        /// </summary>
        private async Task RefrescarHoraUIAsync()
        {
            // pinta la hora (real o simulada) y refresca UI
            var es = new CultureInfo("es-ES");
    
            DateTime now;
            if (_usarHoraSimulada)
                now = _fechaSimulada.ToDateTime(_horaSimulada);
            else
                now = DateTime.Now;
    
            _horaAhoraTxt = es.TextInfo.ToTitleCase(now.ToString("ddd dd/MM HH:mm:ss", es));
            await InvokeAsync(StateHasChanged);
        }
    
    /// <summary>
    /// 
    /// </summary>
    public void Dispose()
    {
        _clockCts?.Cancel();
        _clockTimer?.Dispose();
        _clockCts?.Dispose();
        StopRefrescoHistorialCompartido();
    
        _dotRef?.Dispose();
    }
    
        /// <summary>
        /// 
        /// </summary>
        private static IEnumerable<int> Range(int from, int to, int step = 1)
    {
        if (step <= 0) step = 1;
    
        if (from <= to)
        {
            for (int i = from; i <= to; i += step) yield return i;
        }
        else
        {
            for (int i = from; i >= to; i -= step) yield return i;
        }
    }
    
        private Task<(double[,]? line, double dist, double dur)> WalkCached(double lat1, double lon1, double lat2, double lon2)
        {
            return _walkProvider == ProveedorCaminata.Valhalla
                ? WalkValhallaCached(lat1, lon1, lat2, lon2)
                : WalkOsrmCached(lat1, lon1, lat2, lon2);
        }
    
}
