using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.JSInterop;
using RutasDBUS.Modelos.Historial;

namespace RutasDBUS.Components.Pages;

public partial class PlanificadorRutas
{
    private const string ClaveHistorialLocal = "rutasdbus.planificador.routeHistory.v1";
    private const string ClaveRespaldoHistorialLocal = "rutasdbus.planificador.routeHistory.recovery.v1";
    private const int MaxItemsHistorialLocal = 100;
    private const int IntervaloRefrescoHistorialCompartidoSegundos = 3;
    private const int TiempoMaxOperacionHistorialLocalSegundos = 8;
    private static readonly JsonSerializerOptions OpcionesLecturaHistorialLocal = new()
    {
        PropertyNameCaseInsensitive = true,
        NumberHandling = JsonNumberHandling.AllowReadingFromString
    };

    private readonly List<RegistroHistorialRuta> _historialLocal = new();
    private readonly List<RegistroHistorialRuta> _historialCompartido = new();
    private PeriodicTimer? _historialCompartidoTimer;
    private CancellationTokenSource? _historialCompartidoCts;
    private string _historialLocalSeleccionadoId = string.Empty;
    private string _historialCompartidoSeleccionadoId = string.Empty;
    private string _mensajeHistorial = string.Empty;
    private bool _mensajeHistorialEsError = false;
    private bool _mostrarHistorialRutas = false;
    private bool _refrescandoHistorialCompartido = false;
    private bool _omitirRegistroHistorialEnSiguienteCalculo = false;
    private bool _aplicandoRegistroHistorial = false;
    private string? _historialCompartidoPendienteEliminarId;
    private ConfiguracionLocalPlanificador? _configuracionTemporalPreviaHistorial;
    private Task<bool>? _tareaCargaHistorialLocal;
    private bool _historialLocalCargado = false;
    private bool _historialLocalCargando = false;
    private int _versionAperturaHistorial;
    private int _numeroIntentoCargaHistorialLocal;
    private string _resumenErrorHistorialLocal = string.Empty;
    private string _detalleErrorHistorialLocal = string.Empty;

    private sealed class ResultadoStorageNavegador
    {
        [JsonPropertyName("ok")]
        public bool Ok { get; set; }

        [JsonPropertyName("found")]
        public bool Found { get; set; }

        [JsonPropertyName("value")]
        public string? Value { get; set; }

        [JsonPropertyName("errorName")]
        public string? ErrorName { get; set; }

        [JsonPropertyName("errorMessage")]
        public string? ErrorMessage { get; set; }
    }

    private async Task ToggleHistorialPanel()
    {
        if (_mostrarHistorialRutas)
        {
            _versionAperturaHistorial++;
            _mostrarHistorialRutas = false;
            _historialLocalCargando = false;
            StopRefrescoHistorialCompartido();
            await RestaurarConfiguracionTemporalHistorialAsync();
            return;
        }

        var versionApertura = ++_versionAperturaHistorial;
        _mostrarHistorialRutas = true;
        _historialLocalCargando = !_historialLocalCargado;
        StateHasChanged();

        var historialLocalCargado = await AsegurarHistorialLocalCargadoAsync();
        if (!_mostrarHistorialRutas || versionApertura != _versionAperturaHistorial)
            return;

        _historialLocalCargando = false;
        if (!historialLocalCargado)
            MostrarMensajeHistorial("No he podido cargar el historial personal. Intentalo de nuevo.", esError: true);

        await CargarHistorialCompartidoAsync();
        if (!_mostrarHistorialRutas || versionApertura != _versionAperturaHistorial)
            return;

        StartRefrescoHistorialCompartido();
    }

    private async Task<bool> AsegurarHistorialLocalCargadoAsync()
    {
        if (_historialLocalCargado)
            return true;

        _tareaCargaHistorialLocal ??= CargarHistorialLocalDesdeNavegadorAsync();
        var tareaCarga = _tareaCargaHistorialLocal;
        try
        {
            return await tareaCarga;
        }
        catch (Exception ex)
        {
            RegistrarErrorHistorialLocal("coordinacion de la carga", ex);
            return false;
        }
        finally
        {
            if (!_historialLocalCargado && ReferenceEquals(_tareaCargaHistorialLocal, tareaCarga))
                _tareaCargaHistorialLocal = null;
        }
    }

    private async Task<bool> CargarHistorialLocalDesdeNavegadorAsync()
    {
        var numeroIntento = ++_numeroIntentoCargaHistorialLocal;
        ResultadoStorageNavegador? resultadoLectura;
        try
        {
            using var cts = new CancellationTokenSource(
                TimeSpan.FromSeconds(TiempoMaxOperacionHistorialLocalSegundos));
            resultadoLectura = await JS.InvokeAsync<ResultadoStorageNavegador?>(
                "rt.loadLocalValueResult",
                cts.Token,
                ClaveHistorialLocal);
        }
        catch (OperationCanceledException ex)
        {
            RegistrarErrorHistorialLocal(
                "lectura de localStorage",
                new TimeoutException(
                    $"La lectura no respondio en {TiempoMaxOperacionHistorialLocalSegundos} segundos.",
                    ex),
                $"Intento de carga: {numeroIntento}.");
            return false;
        }
        catch (Exception ex)
        {
            RegistrarErrorHistorialLocal(
                "lectura de localStorage",
                ex,
                $"Intento de carga: {numeroIntento}.");
            return false;
        }

        if (resultadoLectura is null)
        {
            RegistrarErrorHistorialLocal(
                "lectura de localStorage",
                "RespuestaVacia",
                "JavaScript no devolvio ningun resultado de lectura.",
                $"Intento de carga: {numeroIntento}.");
            return false;
        }

        if (!resultadoLectura.Ok)
        {
            RegistrarErrorHistorialLocal(
                "lectura de localStorage",
                resultadoLectura.ErrorName ?? "StorageError",
                resultadoLectura.ErrorMessage ?? "El navegador no ha indicado el motivo del error.",
                $"Intento de carga: {numeroIntento}.");
            return false;
        }

        var json = resultadoLectura.Value;
        if (!resultadoLectura.Found || string.IsNullOrWhiteSpace(json))
        {
            FinalizarCargaHistorialLocal(Array.Empty<RegistroHistorialRuta>());
            LimpiarErrorHistorialLocal();
            return true;
        }

        try
        {
            var items = DeserializarHistorialLocal(
                json,
                out var registrosOmitidos,
                out var primerErrorRegistro);
            FinalizarCargaHistorialLocal(items);

            if (registrosOmitidos > 0)
            {
                var recuperacionGuardada = await GuardarHistorialLocalRecuperadoAsync(json);
                var detalleFalloRecuperacion = recuperacionGuardada
                    ? null
                    : _detalleErrorHistorialLocal;
                if (!recuperacionGuardada)
                    _historialLocalCargado = false;

                RegistrarErrorHistorialLocal(
                    "validacion de registros",
                    "RegistrosDaniados",
                    $"Se omitieron {registrosOmitidos} registros que no se podian utilizar.",
                    $"Primer fallo: {primerErrorRegistro ?? "sin detalle"}. " +
                    $"Registros recuperados: {_historialLocal.Count}. " +
                    $"Tamano leido: {json.Length} caracteres. " +
                    $"Copia de recuperacion: {(recuperacionGuardada ? "guardada" : "no guardada")}." +
                    (string.IsNullOrWhiteSpace(detalleFalloRecuperacion)
                        ? string.Empty
                        : $"{Environment.NewLine}Fallo al recuperar: {detalleFalloRecuperacion}"));
                MostrarMensajeHistorial(
                    $"Se han recuperado {_historialLocal.Count} registros del historial; {registrosOmitidos} estaban dañados.",
                    esError: true);
            }
            else
            {
                LimpiarErrorHistorialLocal();
            }

            return _historialLocalCargado;
        }
        catch (Exception ex) when (
            ex is JsonException or
            NotSupportedException or
            InvalidOperationException or
            ArgumentException)
        {
            FinalizarCargaHistorialLocal(Array.Empty<RegistroHistorialRuta>());
            var recuperacionGuardada = await GuardarHistorialLocalRecuperadoAsync(json);
            var detalleFalloRecuperacion = recuperacionGuardada
                ? null
                : _detalleErrorHistorialLocal;
            if (!recuperacionGuardada)
                _historialLocalCargado = false;

            RegistrarErrorHistorialLocal(
                "deserializacion del historial",
                ex,
                $"Tamano leido: {json.Length} caracteres. " +
                $"Copia de recuperacion: {(recuperacionGuardada ? "guardada" : "no guardada")}." +
                (string.IsNullOrWhiteSpace(detalleFalloRecuperacion)
                    ? string.Empty
                    : $"{Environment.NewLine}Fallo al recuperar: {detalleFalloRecuperacion}"));
            MostrarMensajeHistorial(
                recuperacionGuardada
                    ? "El historial personal guardado estaba dañado. Se ha conservado una copia y se ha iniciado uno nuevo."
                    : "El historial personal esta dañado, pero no se ha modificado porque no se pudo guardar la copia. Reintenta la carga.",
                esError: true);
            return _historialLocalCargado;
        }
        catch (Exception ex)
        {
            _historialLocalCargado = false;
            RegistrarErrorHistorialLocal(
                "fallo inesperado al procesar el historial",
                ex,
                $"Tamano leido: {json.Length} caracteres. El contenido original no se ha modificado.");
            MostrarMensajeHistorial(
                "El codigo ha producido un error inesperado al cargar el historial. Consulta el diagnostico; los datos originales no se han modificado.",
                esError: true);
            return false;
        }
    }

    private static IReadOnlyList<RegistroHistorialRuta> DeserializarHistorialLocal(
        string json,
        out int registrosOmitidos,
        out string? primerErrorRegistro)
    {
        using var documento = JsonDocument.Parse(json);
        if (documento.RootElement.ValueKind != JsonValueKind.Array)
            throw new JsonException("El historial local no contiene una lista.");

        registrosOmitidos = 0;
        primerErrorRegistro = null;
        var recuperados = new List<RegistroHistorialRuta>();
        var idsVistos = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
        foreach (var elemento in documento.RootElement.EnumerateArray())
        {
            try
            {
                var item = elemento.Deserialize<RegistroHistorialRuta>(OpcionesLecturaHistorialLocal);
                if (item is null)
                {
                    registrosOmitidos++;
                    primerErrorRegistro ??= "El registro deserializado era nulo.";
                    continue;
                }

                if (string.IsNullOrWhiteSpace(item.Id) || !idsVistos.Add(item.Id))
                {
                    item.Id = Guid.NewGuid().ToString("N");
                    idsVistos.Add(item.Id);
                }

                NormalizarRegistroHistorialLocal(item);
                recuperados.Add(item);
            }
            catch (Exception ex) when (
                ex is JsonException or
                NotSupportedException or
                InvalidOperationException or
                ArgumentException)
            {
                registrosOmitidos++;
                primerErrorRegistro ??= $"{ex.GetType().Name}: {ex.Message}";
            }
        }

        return recuperados
            .OrderByDescending(x => x.FechaActualizacionUtc)
            .Take(MaxItemsHistorialLocal)
            .ToList();
    }

    private static void NormalizarRegistroHistorialLocal(RegistroHistorialRuta item)
    {
        if (!CoordenadaHistorialValida(item.OrigenLatitud, item.OrigenLongitud))
            throw new JsonException("Las coordenadas de origen no son validas.");
        if (!CoordenadaHistorialValida(item.DestinoLatitud, item.DestinoLongitud))
            throw new JsonException("Las coordenadas de destino no son validas.");

        item.OrigenTexto = string.IsNullOrWhiteSpace(item.OrigenTexto)
            ? $"{item.OrigenLatitud.ToString("0.000000", CultureInfo.InvariantCulture)}, {item.OrigenLongitud.ToString("0.000000", CultureInfo.InvariantCulture)}"
            : item.OrigenTexto.Trim();
        item.DestinoTexto = string.IsNullOrWhiteSpace(item.DestinoTexto)
            ? $"{item.DestinoLatitud.ToString("0.000000", CultureInfo.InvariantCulture)}, {item.DestinoLongitud.ToString("0.000000", CultureInfo.InvariantCulture)}"
            : item.DestinoTexto.Trim();
        item.LineasPermitidas = NormalizarLineasHistorial(item.LineasPermitidas).ToList();

        if (item.FechaCreacionUtc == default)
            item.FechaCreacionUtc = item.FechaActualizacionUtc == default ? DateTime.UtcNow : item.FechaActualizacionUtc;
        if (item.FechaActualizacionUtc == default)
            item.FechaActualizacionUtc = item.FechaCreacionUtc;
    }

    private static bool CoordenadaHistorialValida(double latitud, double longitud)
        => double.IsFinite(latitud) &&
           double.IsFinite(longitud) &&
           latitud is >= -90 and <= 90 &&
           longitud is >= -180 and <= 180;

    private void FinalizarCargaHistorialLocal(IEnumerable<RegistroHistorialRuta> items)
    {
        _historialLocal.Clear();
        _historialLocal.AddRange(items);
        if (!_historialLocal.Any(x => x.Id == _historialLocalSeleccionadoId))
            _historialLocalSeleccionadoId = string.Empty;
        _historialLocalCargado = true;
    }

    private async Task<bool> GuardarHistorialLocalRecuperadoAsync(string jsonOriginal)
    {
        if (!await GuardarValorHistorialLocalAsync(
                ClaveRespaldoHistorialLocal,
                jsonOriginal,
                "guardado de la copia de recuperacion"))
            return false;

        var jsonRecuperado = JsonSerializer.Serialize(_historialLocal);
        return await GuardarValorHistorialLocalAsync(
            ClaveHistorialLocal,
            jsonRecuperado,
            "guardado del historial recuperado");
    }

    private async Task PersistirHistorialLocalAsync()
    {
        if (!_historialLocalCargado)
            return;

        var json = JsonSerializer.Serialize(_historialLocal);
        if (!await GuardarValorHistorialLocalAsync(
                ClaveHistorialLocal,
                json,
                "persistencia del historial"))
        {
            MostrarMensajeHistorial(
                "No se ha podido guardar el historial personal. Consulta el diagnostico.",
                esError: true);
        }
    }

    private async Task<bool> GuardarValorHistorialLocalAsync(string clave, string valor, string fase)
    {
        try
        {
            using var cts = new CancellationTokenSource(
                TimeSpan.FromSeconds(TiempoMaxOperacionHistorialLocalSegundos));
            var resultado = await JS.InvokeAsync<ResultadoStorageNavegador?>(
                "rt.saveLocalValueResult",
                cts.Token,
                clave,
                valor);

            if (resultado?.Ok == true)
                return true;

            RegistrarErrorHistorialLocal(
                fase,
                resultado?.ErrorName ?? "RespuestaVacia",
                resultado?.ErrorMessage ?? "JavaScript no devolvio un resultado valido al guardar.",
                $"Tamano que se intentaba guardar: {valor.Length} caracteres.");
            return false;
        }
        catch (OperationCanceledException ex)
        {
            RegistrarErrorHistorialLocal(
                fase,
                new TimeoutException(
                    $"La escritura no respondio en {TiempoMaxOperacionHistorialLocalSegundos} segundos.",
                    ex),
                $"Tamano que se intentaba guardar: {valor.Length} caracteres.");
            return false;
        }
        catch (Exception ex)
        {
            RegistrarErrorHistorialLocal(
                fase,
                ex,
                $"Tamano que se intentaba guardar: {valor.Length} caracteres.");
            return false;
        }
    }

    private async Task ReintentarCargaHistorialLocalAsync()
    {
        if (_historialLocalCargando)
            return;

        _historialLocalCargado = false;
        _tareaCargaHistorialLocal = null;
        _historialLocalCargando = true;
        StateHasChanged();

        try
        {
            var cargado = await AsegurarHistorialLocalCargadoAsync();
            MostrarMensajeHistorial(
                cargado
                    ? $"Historial personal cargado: {_historialLocal.Count} registro(s)."
                    : "El historial personal sigue sin poder cargarse. Consulta el diagnostico.",
                esError: !cargado);
        }
        finally
        {
            _historialLocalCargando = false;
            StateHasChanged();
        }
    }

    private void RegistrarErrorHistorialLocal(
        string fase,
        Exception excepcion,
        string? contexto = null)
    {
        var causa = excepcion.GetBaseException();
        RegistrarErrorHistorialLocal(
            fase,
            causa.GetType().Name,
            causa.Message,
            CombinarDetalle(contexto, excepcion.ToString()));
    }

    private void RegistrarErrorHistorialLocal(
        string fase,
        string tipo,
        string mensaje,
        string? contexto = null)
    {
        var marcaTiempo = DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss", CultureInfo.CurrentCulture);
        _resumenErrorHistorialLocal = $"{tipo}: {LimitarTextoDiagnostico(mensaje, 240)}";
        _detalleErrorHistorialLocal = string.Join(
            Environment.NewLine,
            new[]
            {
                $"Fecha: {marcaTiempo}",
                $"Fase: {fase}",
                $"Tipo: {tipo}",
                $"Mensaje: {mensaje}",
                $"Estado: cargado={_historialLocalCargado}, cargando={_historialLocalCargando}, registrosEnMemoria={_historialLocal.Count}",
                string.IsNullOrWhiteSpace(contexto) ? null : $"Contexto: {contexto}"
            }.Where(linea => linea is not null));

        Console.Error.WriteLine($"[Historial personal] {_detalleErrorHistorialLocal}");
    }

    private void LimpiarErrorHistorialLocal()
    {
        _resumenErrorHistorialLocal = string.Empty;
        _detalleErrorHistorialLocal = string.Empty;
    }

    private static string? CombinarDetalle(string? contexto, string? detalle)
    {
        if (string.IsNullOrWhiteSpace(contexto))
            return detalle;
        if (string.IsNullOrWhiteSpace(detalle))
            return contexto;
        return $"{contexto}{Environment.NewLine}{detalle}";
    }

    private static string LimitarTextoDiagnostico(string texto, int maximo)
    {
        if (string.IsNullOrWhiteSpace(texto) || texto.Length <= maximo)
            return texto;

        return texto[..maximo] + "...";
    }

    private async Task CargarHistorialCompartidoAsync(bool mostrarError = true)
    {
        if (_refrescandoHistorialCompartido)
            return;

        _refrescandoHistorialCompartido = true;
        try
        {
            var items = await HistorialRutasCompartidoService.ObtenerAsync();
            _historialCompartido.Clear();
            _historialCompartido.AddRange(items);

            if (_historialCompartido.Count == 0)
            {
                _historialCompartidoSeleccionadoId = string.Empty;
                return;
            }

            if (!_historialCompartido.Any(x => x.Id == _historialCompartidoSeleccionadoId))
                _historialCompartidoSeleccionadoId = string.Empty;
        }
        catch (Exception)
        {
            if (mostrarError)
                MostrarMensajeHistorial("No he podido cargar el historial compartido.", esError: true);
        }
        finally
        {
            _refrescandoHistorialCompartido = false;
        }
    }

    private void StartRefrescoHistorialCompartido()
    {
        if (_historialCompartidoTimer is not null)
            return;

        var cts = new CancellationTokenSource();
        var timer = new PeriodicTimer(TimeSpan.FromSeconds(IntervaloRefrescoHistorialCompartidoSegundos));
        _historialCompartidoCts = cts;
        _historialCompartidoTimer = timer;

        _ = Task.Run(async () =>
        {
            try
            {
                while (await timer.WaitForNextTickAsync(cts.Token))
                {
                    if (!_mostrarHistorialRutas)
                        continue;

                    await InvokeAsync(async () =>
                    {
                        await CargarHistorialCompartidoAsync(mostrarError: false);
                        StateHasChanged();
                    });
                }
            }
            catch
            {
                // Timer cancelled when the popup or component closes.
            }
        });
    }

    private void StopRefrescoHistorialCompartido()
    {
        _historialCompartidoCts?.Cancel();
        _historialCompartidoTimer?.Dispose();
        _historialCompartidoCts?.Dispose();

        _historialCompartidoTimer = null;
        _historialCompartidoCts = null;
    }

    private async Task RegistrarEscenarioActualEnHistorialLocalAsync()
    {
        if (!await AsegurarHistorialLocalCargadoAsync())
            return;

        var actual = ConstruirRegistroHistorialActual();
        if (actual is null)
            return;

        var clave = CalcularClaveEscenario(actual);
        var existente = _historialLocal.FirstOrDefault(x =>
            string.Equals(CalcularClaveEscenario(x), clave, StringComparison.OrdinalIgnoreCase));

        if (existente is null)
        {
            existente = actual;
            _historialLocal.Insert(0, existente);
        }
        else
        {
            existente.OrigenTexto = actual.OrigenTexto;
            existente.DestinoTexto = actual.DestinoTexto;
            existente.OrigenLatitud = actual.OrigenLatitud;
            existente.OrigenLongitud = actual.OrigenLongitud;
            existente.DestinoLatitud = actual.DestinoLatitud;
            existente.DestinoLongitud = actual.DestinoLongitud;
            existente.UsarHorariosTeoricos = actual.UsarHorariosTeoricos;
            existente.UsarHoraSimulada = actual.UsarHoraSimulada;
            existente.FechaSimulada = actual.FechaSimulada;
            existente.HoraSimulada = actual.HoraSimulada;
            existente.CriterioPrioritarioRuta = actual.CriterioPrioritarioRuta;
            existente.PreferenciaEtiquetaRuta = actual.PreferenciaEtiquetaRuta;
            existente.FactorEquilibradoAndarBus = actual.FactorEquilibradoAndarBus;
            existente.ActivarFiltroLineas = actual.ActivarFiltroLineas;
            existente.LineasPermitidas = actual.LineasPermitidas.ToList();
            existente.FechaActualizacionUtc = DateTime.UtcNow;

            _historialLocal.Remove(existente);
            _historialLocal.Insert(0, existente);
        }

        while (_historialLocal.Count > MaxItemsHistorialLocal)
            _historialLocal.RemoveAt(_historialLocal.Count - 1);

        await PersistirHistorialLocalAsync();
    }

    private async Task CargarHistorialLocalAsync(string id)
    {
        try
        {
            var item = _historialLocal.FirstOrDefault(x => x.Id == id);
            if (item is null)
            {
                MostrarMensajeHistorial("No encuentro esa combinacion en tu historial personal.", esError: true);
                return;
            }

            await AplicarRegistroHistorialAsync(item);
            SeleccionarHistorialLocal(id);
            MostrarMensajeHistorial("Combinacion personal cargada.");
        }
        catch (Exception ex)
        {
            RegistrarErrorHistorialLocal(
                "aplicacion de un registro personal",
                ex,
                $"Id del registro: {id}.");
            MostrarMensajeHistorial(
                "La combinacion personal ha producido un error. Consulta el diagnostico.",
                esError: true);
        }
    }

    private async Task CargarHistorialCompartidoAsync(string id)
    {
        var item = _historialCompartido.FirstOrDefault(x => x.Id == id);
        if (item is null)
        {
            MostrarMensajeHistorial("No encuentro esa combinacion en el historial compartido.", esError: true);
            return;
        }

        await AplicarRegistroHistorialAsync(item);
        SeleccionarHistorialCompartido(id);
        MostrarMensajeHistorial("Combinación compartida cargada.");
    }

    private void SeleccionarHistorialLocal(string id)
    {
        _historialLocalSeleccionadoId = id;
        _historialCompartidoSeleccionadoId = string.Empty;
    }

    private void SeleccionarHistorialCompartido(string id)
    {
        _historialLocalSeleccionadoId = string.Empty;
        _historialCompartidoSeleccionadoId = id;
    }

    private void LimpiarSeleccionHistorial()
    {
        _historialLocalSeleccionadoId = string.Empty;
        _historialCompartidoSeleccionadoId = string.Empty;
    }

    private async Task CompartirEscenarioActualAsync()
    {
        var actual = ConstruirRegistroHistorialActual()
            ?? _historialLocal.FirstOrDefault(x => x.Id == _historialLocalSeleccionadoId);

        if (actual is null)
        {
            MostrarMensajeHistorial("Primero necesitamos una combinacion valida para poder compartirla.", esError: true);
            return;
        }

        try
        {
            await HistorialRutasCompartidoService.GuardarAsync(actual);
            await CargarHistorialCompartidoAsync();
            MostrarMensajeHistorial("Combinacion enviada al historial compartido.");
        }
        catch (Exception)
        {
            MostrarMensajeHistorial("No he podido guardar la combinacion en el historial compartido.", esError: true);
        }
    }

    private async Task CompartirHistorialLocalAsync(string id)
    {
        var item = _historialLocal.FirstOrDefault(x => x.Id == id);
        if (item is null)
        {
            MostrarMensajeHistorial("No encuentro esa combinacion en tu historial personal.", esError: true);
            return;
        }

        try
        {
            await HistorialRutasCompartidoService.GuardarAsync(item);
            await CargarHistorialCompartidoAsync();
            MostrarMensajeHistorial("Combinacion enviada al historial compartido.");
        }
        catch (Exception)
        {
            MostrarMensajeHistorial("No he podido guardar la combinacion en el historial compartido.", esError: true);
        }
    }

    private async Task EliminarHistorialLocalAsync(string id)
    {
        try
        {
            if (!await AsegurarHistorialLocalCargadoAsync())
                return;

            var indiceEliminado = _historialLocal.FindIndex(x => x.Id == id);
            if (indiceEliminado < 0)
                return;

            var estabaSeleccionado = _historialLocalSeleccionadoId == id;
            _historialLocal.RemoveAt(indiceEliminado);

            await PersistirHistorialLocalAsync();

            if (estabaSeleccionado)
            {
                if (_historialLocal.Count == 0)
                {
                    _historialLocalSeleccionadoId = string.Empty;
                }
                else
                {
                    var siguiente = _historialLocal[Math.Min(indiceEliminado, _historialLocal.Count - 1)];
                    await AplicarRegistroHistorialAsync(siguiente);
                    SeleccionarHistorialLocal(siguiente.Id);
                }
            }

            MostrarMensajeHistorial("Combinacion eliminada del historial personal.");
        }
        catch (Exception ex)
        {
            RegistrarErrorHistorialLocal(
                "eliminacion de un registro personal",
                ex,
                $"Id del registro: {id}.");
            MostrarMensajeHistorial(
                "No se ha podido completar la eliminacion. Consulta el diagnostico.",
                esError: true);
        }
    }

    private void PedirConfirmacionEliminarHistorialCompartido(string id)
    {
        if (string.IsNullOrWhiteSpace(id))
            return;

        _historialCompartidoPendienteEliminarId = id;
    }

    private void CancelarConfirmacionEliminarHistorialCompartido()
    {
        _historialCompartidoPendienteEliminarId = null;
    }

    private async Task ConfirmarEliminarHistorialCompartidoAsync()
    {
        var id = _historialCompartidoPendienteEliminarId;
        _historialCompartidoPendienteEliminarId = null;

        if (string.IsNullOrWhiteSpace(id))
            return;

        await EliminarHistorialCompartidoAsync(id);
    }

    private async Task EliminarHistorialCompartidoAsync(string id)
    {
        try
        {
            var indiceEliminado = _historialCompartido.FindIndex(x => x.Id == id);
            var estabaSeleccionado = _historialCompartidoSeleccionadoId == id;
            var ok = await HistorialRutasCompartidoService.EliminarAsync(id);
            if (!ok)
            {
                MostrarMensajeHistorial("No he podido eliminar esa combinacion compartida.", esError: true);
                return;
            }

            await CargarHistorialCompartidoAsync();
            _historialCompartido.RemoveAll(x => x.Id == id);

            if (estabaSeleccionado)
            {
                if (_historialCompartido.Count == 0)
                {
                    _historialCompartidoSeleccionadoId = string.Empty;
                }
                else
                {
                    var indiceSiguiente = Math.Clamp(indiceEliminado, 0, _historialCompartido.Count - 1);
                    var siguiente = _historialCompartido[indiceSiguiente];
                    await AplicarRegistroHistorialAsync(siguiente);
                    SeleccionarHistorialCompartido(siguiente.Id);
                }
            }

            MostrarMensajeHistorial("Combinacion eliminada del historial compartido.");
        }
        catch (Exception)
        {
            MostrarMensajeHistorial("No he podido eliminar esa combinacion compartida.", esError: true);
        }
    }

    private async Task AplicarRegistroHistorialAsync(RegistroHistorialRuta item)
    {
        _aplicandoRegistroHistorial = true;
        try
        {
            CapturarConfiguracionTemporalAntesDeHistorial();

            _omitirRegistroHistorialEnSiguienteCalculo = true;
            _textoBusquedaOrigen = item.OrigenTexto;
            _textoBusquedaDestino = item.DestinoTexto;
            _coordenadasOrigen = new[] { item.OrigenLatitud, item.OrigenLongitud };
            _coordenadasDestino = new[] { item.DestinoLatitud, item.DestinoLongitud };

            _usarHorariosTeoricos = item.UsarHorariosTeoricos;
            _usarHoraSimulada = item.UsarHoraSimulada;

            if (DateOnly.TryParse(item.FechaSimulada, out var fecha))
                _fechaSimulada = fecha;

            if (TimeOnly.TryParse(item.HoraSimulada, out var hora))
                _horaSimulada = hora;

            _criterioPrioritarioRuta = NormalizarCriterioRuta(item.CriterioPrioritarioRuta, item.PreferenciaEtiquetaRuta);
            SincronizarPreferenciaDesdeCriterioRuta();

            if (item.FactorEquilibradoAndarBus is { } factorEquilibrado && double.IsFinite(factorEquilibrado))
            {
                _factorEquilibradoAndarBus = Math.Max(0, factorEquilibrado);
                SincronizarTextosPenalizacionPie();

                if (_configuracionTemporalPreviaHistorial is not null)
                    _configuracionTemporalPreviaHistorial.FactorEquilibradoAndarBus = _factorEquilibradoAndarBus;
            }

            _activarRangoLineas = item.ActivarFiltroLineas;
            _lineasPermitidas.Clear();
            foreach (var linea in NormalizarLineasHistorial(item.LineasPermitidas))
                _lineasPermitidas.Add(linea);
            _busFilteredCache.Clear();

            SincronizarSegundosPorParadaConHorarios();

            StopClock();
            if (_usarHoraSimulada)
                await RefrescarHoraUIAsync();
            else
                StartClock();

            await PersistirConfiguracionAsync();
            await CerrarTodasLasPreviewsAsync();
            await CerrarPopupsAsync();

            if (_mapa is not null)
            {
                try { await _mapa.Geometric.DisplayPolylinesFromArray.deleteConnectors(); } catch { }
                await ClearRoutesJsAsync();
            }

            await SincronizarMarcadoresOrigenDestinoJsAsync();
            await CalcularRutaCompletaAsync();
        }
        finally
        {
            _aplicandoRegistroHistorial = false;
        }
    }

    private void CapturarConfiguracionTemporalAntesDeHistorial()
    {
        if (_configuracionTemporalPreviaHistorial is not null)
            return;

        _configuracionTemporalPreviaHistorial = CrearConfiguracionLocal();
    }

    private async Task RestaurarConfiguracionTemporalHistorialAsync()
    {
        var configuracionPrevia = _configuracionTemporalPreviaHistorial;
        if (configuracionPrevia is null)
            return;

        _configuracionTemporalPreviaHistorial = null;
        AplicarConfiguracionLocal(configuracionPrevia);

        StopClock();
        if (_usarHoraSimulada)
            await RefrescarHoraUIAsync();
        else
            StartClock();

        await PersistirConfiguracionAsync();
    }

    private RegistroHistorialRuta? ConstruirRegistroHistorialActual()
    {
        if (_coordenadasOrigen is null || _coordenadasDestino is null)
            return null;

        var ahora = _ultimaHoraReferenciaRuta ?? AhoraLocal();

        return new RegistroHistorialRuta
        {
            Id = Guid.NewGuid().ToString("N"),
            OrigenTexto = ResolverTextoPuntoHistorial(_coordenadasOrigen),
            DestinoTexto = ResolverTextoPuntoHistorial(_coordenadasDestino),
            OrigenLatitud = _coordenadasOrigen[0],
            OrigenLongitud = _coordenadasOrigen[1],
            DestinoLatitud = _coordenadasDestino[0],
            DestinoLongitud = _coordenadasDestino[1],
            UsarHorariosTeoricos = _usarHorariosTeoricos,
            UsarHoraSimulada = _usarHoraSimulada,
            FechaSimulada = DateOnly.FromDateTime(ahora).ToString("yyyy-MM-dd", CultureInfo.InvariantCulture),
            HoraSimulada = TimeOnly.FromDateTime(ahora).ToString("HH:mm:ss", CultureInfo.InvariantCulture),
            CriterioPrioritarioRuta = _criterioPrioritarioRuta,
            PreferenciaEtiquetaRuta = _preferenciaEtiquetaRuta,
            FactorEquilibradoAndarBus = double.IsFinite(_factorEquilibradoAndarBus)
                ? Math.Max(0, _factorEquilibradoAndarBus)
                : FactorEquilibradoAndarBusPorDefecto,
            ActivarFiltroLineas = _activarRangoLineas,
            LineasPermitidas = ObtenerLineasPermitidasSeleccionadas().ToList(),
            FechaCreacionUtc = DateTime.UtcNow,
            FechaActualizacionUtc = DateTime.UtcNow
        };
    }

    private static string? FormatearParadaHistorial(RutasDBUS.Modelos.RedBus.Parada? parada)
    {
        if (parada is null || string.IsNullOrWhiteSpace(parada.Codigo))
            return null;

        var nombre = string.IsNullOrWhiteSpace(parada.Nombre)
            ? parada.Codigo
            : parada.Nombre.Trim();

        return $"{nombre} ({CodigoParadaFormateado(parada.Codigo)})";
    }

    private string ResolverTextoPuntoHistorial(double[] coords)
    {
        var lat = coords[0];
        var lon = coords[1];

        var parada = RedBusServicio.BuscarParadaMasCercana(lat, lon);

        var textoParada = FormatearParadaHistorial(parada);
        if (!string.IsNullOrWhiteSpace(textoParada))
            return textoParada;

        return $"{lat.ToString("0.000000", CultureInfo.InvariantCulture)}, {lon.ToString("0.000000", CultureInfo.InvariantCulture)}";
    }

    private string TextoOpcionHistorial(RegistroHistorialRuta item)
    {
        var sello = item.FechaActualizacionUtc == default
            ? string.Empty
            : item.FechaActualizacionUtc.ToLocalTime().ToString("dd/MM HH:mm", CultureInfo.CurrentCulture);
        var horaReferencia = FormatearHoraReferenciaHistorial(item);
        var modo = FormatearModoHistorial(item);

        var textoBase = string.IsNullOrWhiteSpace(sello)
            ? $"{item.OrigenTexto} -> {item.DestinoTexto}"
            : $"{sello} - {item.OrigenTexto} -> {item.DestinoTexto}";

        var partes = new List<string> { textoBase };
        if (!string.IsNullOrWhiteSpace(horaReferencia))
            partes.Add($"HR: {horaReferencia}");
        if (!string.IsNullOrWhiteSpace(modo))
            partes.Add($"Modo: {modo}");
        return string.Join(" \u00b7 ", partes);

    }

    private static string FormatearModoHistorial(RegistroHistorialRuta item)
    {
        var criterio = item.CriterioPrioritarioRuta?.Trim();
        if (string.IsNullOrWhiteSpace(criterio))
        {
            var preferencia = item.PreferenciaEtiquetaRuta?.Trim() ?? string.Empty;
            return string.Equals(preferencia, PreferenciaRutaAndar, StringComparison.OrdinalIgnoreCase)
                ? "Andar"
                : preferencia;
        }

        if (string.Equals(criterio, CriterioRutaAndar, StringComparison.OrdinalIgnoreCase))
            return "Andar";
        if (string.Equals(criterio, CriterioRutaTiempo, StringComparison.OrdinalIgnoreCase))
            return "Tiempo";
        if (string.Equals(criterio, CriterioRutaEquilibrado, StringComparison.OrdinalIgnoreCase))
            return "Equilibrado";

        return criterio;
    }

    private static IReadOnlyList<int> NormalizarLineasHistorial(IEnumerable<int>? lineas)
        => (lineas ?? Array.Empty<int>())
            .Where(linea => linea > 0)
            .Distinct()
            .OrderBy(linea => linea)
            .ToList();

    private static string FormatearHoraReferenciaHistorial(RegistroHistorialRuta item)
    {
        if (string.IsNullOrWhiteSpace(item.HoraSimulada))
            return string.Empty;

        var formatosHora = new[] { "HH:mm:ss", "H:mm:ss", "HH:mm", "H:mm" };
        if (!TimeOnly.TryParseExact(
            item.HoraSimulada.Trim(),
            formatosHora,
            CultureInfo.InvariantCulture,
            DateTimeStyles.None,
            out var hora))
        {
            return string.Empty;
        }

        var horaTexto = hora.ToString("HH:mm", CultureInfo.InvariantCulture);
        if (string.IsNullOrWhiteSpace(item.FechaSimulada))
            return horaTexto;

        var formatosFecha = new[] { "yyyy-MM-dd", "dd/MM/yyyy", "d/M/yyyy", "dd/MM/yy", "d/M/yy" };
        return DateOnly.TryParseExact(
            item.FechaSimulada.Trim(),
            formatosFecha,
            CultureInfo.InvariantCulture,
            DateTimeStyles.None,
            out var fecha)
            ? $"{fecha.ToString("dd/MM", CultureInfo.InvariantCulture)} {horaTexto}"
            : $"{item.FechaSimulada.Trim()} {horaTexto}";
    }

    private void MostrarMensajeHistorial(string mensaje, bool esError = false)
    {
        _mensajeHistorial = mensaje;
        _mensajeHistorialEsError = esError;
    }

    private static string CalcularClaveEscenario(RegistroHistorialRuta item)
    {
        var origenLat = Math.Round(item.OrigenLatitud, 6).ToString("0.000000", CultureInfo.InvariantCulture);
        var origenLon = Math.Round(item.OrigenLongitud, 6).ToString("0.000000", CultureInfo.InvariantCulture);
        var destinoLat = Math.Round(item.DestinoLatitud, 6).ToString("0.000000", CultureInfo.InvariantCulture);
        var destinoLon = Math.Round(item.DestinoLongitud, 6).ToString("0.000000", CultureInfo.InvariantCulture);
        var fecha = item.UsarHoraSimulada ? item.FechaSimulada?.Trim() ?? string.Empty : string.Empty;
        var hora = item.UsarHoraSimulada ? item.HoraSimulada?.Trim() ?? string.Empty : string.Empty;
        var criterio = NormalizarCriterioRuta(item.CriterioPrioritarioRuta, item.PreferenciaEtiquetaRuta);
        var factorEquilibrado = item.FactorEquilibradoAndarBus is { } factor && double.IsFinite(factor)
            ? Math.Max(0, factor).ToString("0.######", CultureInfo.InvariantCulture)
            : string.Empty;
        var lineas = item.ActivarFiltroLineas
            ? string.Join(",", NormalizarLineasHistorial(item.LineasPermitidas))
            : string.Empty;

        return string.Join("|",
            origenLat,
            origenLon,
            destinoLat,
            destinoLon,
            item.UsarHorariosTeoricos ? "1" : "0",
            item.UsarHoraSimulada ? "1" : "0",
            fecha,
            hora,
            criterio,
            factorEquilibrado,
            item.ActivarFiltroLineas ? "1" : "0",
            lineas);
    }
}
