"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PIDDepartureBoardsModel = void 0;
const StopEnums_1 = require("../../../helpers/StopEnums");
const data_access_1 = require("../data-access");
const models_1 = require("../../ropid-gtfs/models");
const CoreToken_1 = require("@golemio/core/dist/helpers/ioc/CoreToken");
const trace_provider_1 = require("@golemio/core/dist/monitoring/opentelemetry/trace-provider");
const golemio_errors_1 = require("@golemio/core/dist/shared/golemio-errors");
const moment_timezone_1 = __importDefault(require("@golemio/core/dist/shared/moment-timezone"));
const _1 = require(".");
const __1 = require("..");
const Di_1 = require("../ioc/Di");
const OgPidToken_1 = require("../ioc/OgPidToken");
const PIDDepartureModel_1 = __importDefault(require("./helpers/PIDDepartureModel"));
const RunHelper_1 = require("./helpers/RunHelper");
const STOPS_MAX_COUNT = 100;
// TODO refactor - split into facade and multiple service/strategy classes
// Check V3TransferBoardsController and TransferFacade for inspiration
class PIDDepartureBoardsModel {
    constructor() {
        this.log = Di_1.OgPidContainer.resolve(CoreToken_1.CoreToken.Logger);
        this.ropidDeparturesDirectionsModel = new _1.RopidDeparturesDirectionsModel();
        this.departuresRepository = new data_access_1.DeparturesRepository(Di_1.OgPidContainer.resolve(CoreToken_1.CoreToken.PostgresConnector), Di_1.OgPidContainer.resolve(CoreToken_1.CoreToken.SimpleConfig));
        this.runTripsRedisRepository = Di_1.OgPidContainer.resolve(OgPidToken_1.OgPidToken.RunTripsRedisRepository);
        this.positionsRedisRepository = Di_1.OgPidContainer.resolve(OgPidToken_1.OgPidToken.PublicVehiclePositionsRepository);
        this.infotextFacade = Di_1.OgPidContainer.resolve(OgPidToken_1.OgPidToken.InfotextFacade);
    }
    async getAll(options) {
        const currentMoment = (0, moment_timezone_1.default)();
        //#region Data retrieval from DB (GTFS stops, infotexts, directions, departures)
        const { stopsToInclude, stopIds } = await this.retrieveAndReturnStops(options);
        const [infotextsToInclude, directions, departures] = await Promise.all([
            this.retrieveAndReturnInfotexts(stopIds, currentMoment, options),
            this.retrieveAndReturnDirections(stopIds),
            this.retrieveAndReturnDepartures(stopIds, currentMoment, options),
        ]);
        //#endregion
        //#region Data retrieval from Redis (run schedule, untracked trips)
        let runScheduleMap = new Map();
        let untrackedTrips = new Set();
        const isMissingRequested = options.skip.includes(__1.DepartureSkip.MISSING) && !options.skip.includes(__1.DepartureSkip.UNTRACKED);
        // Note: The run schedule and untracked trips are populated only if the skip[]=missing (without untracked) is present
        // Check the PIDDeparturesModel.skip function. In other cases they are not needed
        if (isMissingRequested) {
            const runTuples = new Set();
            for (const departure of departures) {
                if (!departure.route_id || !departure.run_number) {
                    continue;
                }
                runTuples.add(RunHelper_1.RunHelper.composeRunId(departure.route_id, departure.run_number));
            }
            const { runSchedule, runTripIds } = await this.retrieveAndReturnRunTrips(runTuples);
            runScheduleMap = runSchedule;
            untrackedTrips = await this.retrieveAndReturnUntrackedTrips(runTripIds);
        }
        //#endregion
        //#region Processing and returning departures
        const spanProcessing = (0, trace_provider_1.createChildSpan)("V2PIDRouter.Processing");
        try {
            const departuresToInclude = new PIDDepartureModel_1.default(departures, {
                limit: options.limit,
                offset: options.offset,
                total: options.total,
                mode: options.mode,
                order: options.order,
                filter: options.filter,
                skip: options.skip,
                departuresDirections: directions,
                timezone: options.timezone,
                tripNumber: null, // not used in this context
                runScheduleMap,
                untrackedTrips,
            }).toArray();
            return {
                departures: departuresToInclude,
                infotexts: infotextsToInclude,
                stops: stopsToInclude,
            };
        }
        catch (error) {
            spanProcessing?.recordException(error);
            if (error instanceof golemio_errors_1.AbstractGolemioError) {
                throw error;
            }
            throw new golemio_errors_1.GeneralError("Error while processing departures", this.constructor.name, error, 500);
        }
        finally {
            spanProcessing?.end();
        }
        //#endregion
    }
    async retrieveAndReturnStops(options) {
        const spanStops = (0, trace_provider_1.createChildSpan)("V2PIDRouter.DB.stops");
        spanStops?.setAttributes({ ...options, timeFrom: options.timeFrom?.toString() });
        try {
            const stopsToInclude = await models_1.models.GTFSStopModel.GetAll({
                includeMetroTrains: options.includeMetroTrains,
                appendAswId: true,
                aswIds: options.aswIds,
                cisIds: options.cisIds,
                gtfsIds: options.gtfsIds,
                limit: STOPS_MAX_COUNT + 1,
                locationType: StopEnums_1.GtfsStopLocationType.StopOrPlatform,
                names: options.names,
                returnRaw: true,
            });
            if (stopsToInclude.length === 0) {
                throw new golemio_errors_1.GeneralError("No stops found.", this.constructor.name, undefined, 404);
            }
            else if (stopsToInclude.length > STOPS_MAX_COUNT) {
                throw new golemio_errors_1.GeneralError(`Too many stops, try lower number or split requests. The maximum is ${STOPS_MAX_COUNT} stops.`, this.constructor.name, undefined, 413);
            }
            const stopIds = stopsToInclude.map(({ stop_id }) => stop_id);
            return { stopsToInclude, stopIds };
        }
        catch (error) {
            spanStops?.recordException(error);
            if (error instanceof golemio_errors_1.AbstractGolemioError) {
                throw error;
            }
            throw new golemio_errors_1.GeneralError("Error while getting stops", this.constructor.name, error, 500);
        }
        finally {
            spanStops?.end();
        }
    }
    async retrieveAndReturnInfotexts(stopIds, currentMoment, options) {
        const spanInfotexts = (0, trace_provider_1.createChildSpan)("V2PIDRouter.DB.infotexts");
        try {
            return await this.infotextFacade.getInfotextsForDepartureBoards(stopIds, currentMoment, options);
        }
        catch (error) {
            spanInfotexts?.recordException(error);
            throw error;
        }
        finally {
            spanInfotexts?.end();
        }
    }
    async retrieveAndReturnDirections(stopIds) {
        const spanDirections = (0, trace_provider_1.createChildSpan)("V2PIDRouter.DB.directions");
        try {
            return await this.ropidDeparturesDirectionsModel.GetAll(stopIds);
        }
        catch (error) {
            spanDirections?.recordException(error);
            if (error instanceof golemio_errors_1.AbstractGolemioError) {
                throw error;
            }
            throw new golemio_errors_1.GeneralError("Error while getting directions", this.constructor.name, error, 500);
        }
        finally {
            spanDirections?.end();
        }
    }
    async retrieveAndReturnDepartures(stopIds, currentMoment, options) {
        const spanDepartures = (0, trace_provider_1.createChildSpan)("V2PIDRouter.DB.departures");
        spanDepartures?.setAttributes({ stopIds });
        try {
            const minutesOffset = options.timeFrom ? options.timeFrom.diff(currentMoment, "minutes") : 0;
            return await this.departuresRepository.GetAll({
                currentMoment,
                minutesAfter: options.minutesAfter,
                minutesBefore: options.minutesBefore,
                minutesOffset,
                stopsIds: stopIds,
                mode: options.mode,
                isAirCondition: !!options.airCondition,
            });
        }
        catch (error) {
            spanDepartures?.recordException(error);
            if (error instanceof golemio_errors_1.AbstractGolemioError) {
                throw error;
            }
            throw new golemio_errors_1.GeneralError("Error while getting departures", this.constructor.name, error, 500);
        }
        finally {
            spanDepartures?.end();
        }
    }
    async retrieveAndReturnRunTrips(runTuples) {
        const runScheduleMap = new Map();
        const tripIds = new Set();
        const spanRunTrips = (0, trace_provider_1.createChildSpan)("V2PIDRouter.Redis.runTrips");
        try {
            const runTuplesArray = Array.from(runTuples);
            const runSchedule = await this.runTripsRedisRepository.getMultipleSchedule(runTuplesArray);
            for (let i = 0; i < runSchedule.length; i++) {
                const runTrip = runSchedule[i];
                if (runTrip) {
                    const tuple = runTuplesArray[i];
                    runScheduleMap.set(tuple, runTrip.schedule);
                    for (const trip of runTrip.schedule) {
                        tripIds.add(trip.trip_id);
                    }
                }
            }
        }
        catch (error) {
            spanRunTrips?.recordException(error);
            this.log.error(error, 
            // eslint-disable-next-line max-len
            "Cannot retrieve run schedule. The API consumer will receive departures with missing vehicles even if they requested to skip them.");
        }
        finally {
            spanRunTrips?.end();
            return {
                runSchedule: runScheduleMap,
                runTripIds: tripIds,
            };
        }
    }
    async retrieveAndReturnUntrackedTrips(tripIds) {
        let untrackedTrips = new Set();
        const spanUntrackedTrips = (0, trace_provider_1.createChildSpan)("V2PIDRouter.Redis.untrackedTrips");
        try {
            const tripIdsArray = Array.from(tripIds);
            untrackedTrips = await this.positionsRedisRepository.getTripsWithUntrackedVehicles(tripIdsArray);
        }
        catch (error) {
            spanUntrackedTrips?.recordException(error);
            this.log.error(error, 
            // eslint-disable-next-line max-len
            "Cannot retrieve untracked trips. The API consumer will receive departures with missing vehicles even if they requested to skip them.");
        }
        finally {
            spanUntrackedTrips?.end();
            return untrackedTrips;
        }
    }
}
exports.PIDDepartureBoardsModel = PIDDepartureBoardsModel;
//# sourceMappingURL=PIDDepartureBoardsModel.js.map