📄 src/Integrations/Vasttrafik/VasttrafikClient.cs
using System;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading;
using System.Threading.Tasks;

namespace MMirror.Integrations.Vasttrafik;

public sealed class VasttrafikClient(HttpClient httpClient, TimeProvider timeProvider)
{
    public async Task<Departure[]> GetDeparturesFrom(string id, CancellationToken cancellationToken)
    {
        var response =
            await httpClient.GetFromJsonAsync(
                $"/pr/v4/stop-areas/{id}/departures?timeSpanInMinutes=60&maxDeparturesPerLineAndDirection=2&limit=32",
                VasttrafikJsonSerializerContext.Default.DeparturesResponse,
                cancellationToken
            ) ?? throw new Exception("Received literal null from Vasttrafik departures endpoint.");

        var currentTime = timeProvider.GetLocalNow();
        return
        [
            .. response
                .Results.Where(d => d.EstimatedTime.HasValue && currentTime < d.EstimatedTime.Value)
                .GroupBy(d => (d.ServiceJourney.Line.Name, d.ServiceJourney.DirectionDetails.Direction))
                .Select(g =>
                {
                    var departures = g.ToArray();
                    var departureTimes = departures.Select(d => d.EstimatedTime!.Value).Order().ToArray();
                    return new Departure(
                        departures[0].ServiceJourney.Line.Name,
                        MapTransportType(departures[0].ServiceJourney.Line.Type),
                        departures[0].ServiceJourney.DirectionDetails.Direction,
                        departures[0].ServiceJourney.DirectionDetails.Via,
                        departureTimes[0],
                        departureTimes.Length > 1 ? departureTimes[1] : null,
                        departures[0].StopPoint.Platform
                    );
                })
                .OrderBy(d => d.Next)
                .Take(8),
        ];
    }

    private static TransportType MapTransportType(string type) =>
        type switch
        {
            "tram" => TransportType.Tram,
            "bus" => TransportType.Bus,
            "ferry" => TransportType.Ferry,
            "train" => TransportType.Train,
            _ => TransportType.Unknown,
        };
}

public sealed record Departure(
    string Name,
    TransportType Type,
    string Direction,
    string? Via,
    DateTimeOffset Next,
    DateTimeOffset? NextNext,
    string Platform
);

public enum TransportType
{
    Unknown,
    Tram,
    Bus,
    Ferry,
    Train,
}