"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DepartureBoardFacade = void 0;
const RouteTypeEnums_1 = require("../../../../helpers/RouteTypeEnums");
const GtfsStopParser_1 = require("../../../../helpers/GtfsStopParser");
const trace_provider_1 = require("@golemio/core/dist/monitoring/opentelemetry/trace-provider");
const golemio_errors_1 = require("@golemio/core/dist/shared/golemio-errors");
const DepartureGroupRefiner_1 = require("../helpers/DepartureGroupRefiner");
const PublicDepartureTransformation_1 = require("../transformations/PublicDepartureTransformation");
class DepartureBoardFacade {
    constructor(departureRepository, tripRepository, stopTimeRepository, descriptorRepository, gtfsStopsRepository) {
        this.departureRepository = departureRepository;
        this.tripRepository = tripRepository;
        this.stopTimeRepository = stopTimeRepository;
        this.descriptorRepository = descriptorRepository;
        this.gtfsStopsRepository = gtfsStopsRepository;
        this.transformation = new PublicDepartureTransformation_1.PublicDepartureTransformation();
    }
    async getAll(stopIds, limit, routeShortNames, minutesAfter, minutesBefore) {
        let output;
        const rawStopGroupsUnfiltered = this.sortStopGroupsByPriority(stopIds);
        const sortedStopGroups = await this.filterForExistingStopIds(rawStopGroupsUnfiltered);
        const promises = sortedStopGroups.map(async (stopGroup) => await this.handleStopGroupDepartures(stopGroup, limit, routeShortNames, minutesAfter, minutesBefore));
        try {
            output = await Promise.all(promises);
        }
        catch (err) {
            if (err instanceof golemio_errors_1.AbstractGolemioError) {
                throw err;
            }
            throw new golemio_errors_1.GeneralError("Error while retrieving cached data", this.constructor.name, err, 500);
        }
        if (output.every((value) => value === null)) {
            return null;
        }
        else {
            return output.map((value) => {
                if (value === null) {
                    return [];
                }
                else {
                    return value;
                }
            });
        }
    }
    async handleStopGroupDepartures(stopGroup, limit, routeShortNames, minutesAfter, minutesBefore) {
        let outputGroup = [];
        if (!stopGroup.stopIds.length) {
            // Wrong stop identifier, otherwise we'd have at least one valid stopId
            return null;
        }
        const spanDepartures = (0, trace_provider_1.createChildSpan)(`Departures.${stopGroup.priority}.getPublicDepartureCache`);
        const timeFrom = new Date(Date.now() - minutesBefore * 60 * 1000);
        const departures = await this.departureRepository.getPublicGtfsDepartureCache(stopGroup.stopIds, minutesAfter + DepartureBoardFacade.OPEN_MINUTES_IN_PAST, new Date(timeFrom.getTime() - DepartureBoardFacade.OPEN_MINUTES_IN_PAST_MS));
        spanDepartures?.end();
        if (departures.length === 0) {
            // Did not find departure in the time-window, can be a valid result for small time-window
            return outputGroup;
        }
        const spanPositions = (0, trace_provider_1.createChildSpan)(`Departures.${stopGroup.priority}.getAllVehiclePositions`);
        const nonNullTripIds = Array.from(new Set(departures.map((departure) => departure.trip_id))).filter((x) => x);
        const positionsByTrip = await this.tripRepository.getAllVehiclePositionsForMultipleTrips(nonNullTripIds);
        spanPositions?.end();
        const spanTransform = (0, trace_provider_1.createChildSpan)(`Departures.${stopGroup.priority}.transformDepartures`);
        try {
            for (const departure of departures) {
                if (Object.keys(departure).length === 0) {
                    // empty departure object can happen because it is cache-placeholder without real departures
                    continue;
                }
                const positions = positionsByTrip.get(departure.trip_id) ?? [];
                const duplicatedDepartures = await this.duplicateDeparturesWithDifferentVehicleIds(departure, positions);
                outputGroup = outputGroup.concat(duplicatedDepartures);
            }
        }
        catch (err) {
            if (err instanceof golemio_errors_1.AbstractGolemioError) {
                throw err;
            }
            throw new golemio_errors_1.GeneralError("Error while retrieving cached data", this.constructor.name, err, 500);
        }
        finally {
            spanTransform?.end();
        }
        const spanManipulate = (0, trace_provider_1.createChildSpan)(`Departures.${stopGroup.priority}.manipulateDepartures`);
        try {
            outputGroup = DepartureGroupRefiner_1.DepartureGroupRefiner.filterDeparturesByRouteShortNames(outputGroup, routeShortNames);
            outputGroup = DepartureGroupRefiner_1.DepartureGroupRefiner.sortDeparturesByPredictedTimestamp(outputGroup);
            outputGroup = DepartureGroupRefiner_1.DepartureGroupRefiner.removeDeparturesOutsideTimeRange(outputGroup, minutesBefore, minutesAfter).slice(0, limit);
        }
        catch (err) {
            throw new golemio_errors_1.GeneralError("Error while manipulating departures", this.constructor.name, err, 500);
        }
        finally {
            spanManipulate?.end();
        }
        return outputGroup;
    }
    async duplicateDeparturesWithDifferentVehicleIds(departure, vehiclePositions) {
        const outputGroup = [];
        // If there are no RT data, we still want to display the departure
        // but without any RT/vehicle information
        if (vehiclePositions.length === 0) {
            outputGroup.push(this.transformation.transformElement({
                departure,
                vehiclePosition: null,
                vehicleDescriptor: null,
                stopTime: null,
            }));
            return outputGroup;
        }
        // Otherwise, we want to duplicate the departure for each vehicleId
        // (multiple vehicles can be on the same trip at the same time)
        for (const vehiclePosition of vehiclePositions) {
            const vehicleDescriptor = vehiclePosition?.detailed_info.registration_number
                ? await this.descriptorRepository.getOneByRegNumber(departure.route_type, vehiclePosition?.detailed_info.registration_number)
                : null;
            let currentStopTime = null;
            // If the vehicle is a train, we need to get the current stop time
            //   to later determine the RT CIS platform code (trains don't have platform codes in GTFS)
            if (vehiclePosition.route_type === RouteTypeEnums_1.GTFSRouteTypeEnum.TRAIN) {
                const stopTimes = await this.stopTimeRepository.getPublicStopTimeCache(vehiclePosition.vehicle_id, departure.trip_id);
                currentStopTime = stopTimes.find((stopTime) => stopTime.sequence === departure.stop_sequence) ?? null;
            }
            outputGroup.push(this.transformation.transformElement({
                departure,
                vehiclePosition,
                vehicleDescriptor,
                stopTime: currentStopTime,
            }));
        }
        return outputGroup;
    }
    async filterForExistingStopIds(groups) {
        const stopNodes = new Set();
        for (const group of groups) {
            group.stopIds.forEach((stopId) => {
                stopNodes.add(GtfsStopParser_1.GtfsStopParser.normalizedNodeId(stopId));
            });
        }
        const nodesToStopIds = await this.gtfsStopsRepository.getGtfsStopIdsForAswNodes(Array.from(stopNodes.values()));
        if (!nodesToStopIds.size) {
            return [];
        }
        const foundStopIds = new Set(Array.from(nodesToStopIds.values()).flat());
        const filteredGroups = [];
        for (const group of groups) {
            filteredGroups.push({
                priority: group.priority,
                stopIds: group.stopIds.filter((s) => foundStopIds.has(s)),
            });
        }
        return filteredGroups;
    }
    sortStopGroupsByPriority(stopIds) {
        return Array.from(stopIds).sort((a, b) => a.priority - b.priority);
    }
}
exports.DepartureBoardFacade = DepartureBoardFacade;
DepartureBoardFacade.OPEN_MINUTES_IN_PAST = 61; // to be able to see empty "departure" from stop without departure
DepartureBoardFacade.OPEN_MINUTES_IN_PAST_MS = DepartureBoardFacade.OPEN_MINUTES_IN_PAST * 60 * 1000;
//# sourceMappingURL=DepartureBoardFacade.js.map