"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.GTFSTripsModel = void 0;
const PassengerTransferEnums_1 = require("../../../helpers/PassengerTransferEnums");
const const_1 = require("../../../schema-definitions/const");
const ropid_gtfs_1 = require("../../../schema-definitions/ropid-gtfs");
const TripDto_1 = require("../../../schema-definitions/ropid-gtfs/models/TripDto");
const Geo_1 = require("@golemio/core/dist/output-gateway/Geo");
const ioc_1 = require("@golemio/core/dist/output-gateway/ioc/");
const models_1 = require("@golemio/core/dist/output-gateway/models");
const golemio_errors_1 = require("@golemio/core/dist/shared/golemio-errors");
const sequelize_1 = __importDefault(require("@golemio/core/dist/shared/sequelize"));
const GtfsTripScheduleRepository_1 = require("../data-access/GtfsTripScheduleRepository");
class GTFSTripsModel extends models_1.SequelizeModel {
    constructor() {
        super(ropid_gtfs_1.RopidGTFS.trips.name, ropid_gtfs_1.RopidGTFS.trips.pgTableName, TripDto_1.TripDto.attributeModel, {
            schema: const_1.PG_SCHEMA,
        });
        this.Associate = (models) => {
            this.sequelizeModel.hasMany(models.GTFSStopTimesModel.sequelizeModel, {
                as: "has_stop_id",
                foreignKey: "trip_id",
            });
            this.sequelizeModel.hasMany(models.GTFSStopTimesModel.sequelizeModel, {
                as: "stop_times",
                foreignKey: "trip_id",
            });
            this.sequelizeModel.belongsTo(models.GTFSCalendarModel.sequelizeModel, {
                as: "service",
                foreignKey: "service_id",
            });
            this.sequelizeModel.belongsTo(models.GTFSRoutesModel.sequelizeModel, {
                as: "route",
                foreignKey: "route_id",
            });
            this.sequelizeModel.hasMany(models.GTFSShapesModel.sequelizeModel, {
                as: "shapes",
                foreignKey: "shape_id",
                sourceKey: "shape_id",
            });
            this.sequelizeModel.belongsToMany(models.GTFSStopModel.sequelizeModel, {
                as: "stops",
                foreignKey: "trip_id",
                otherKey: "stop_id",
                through: models.GTFSStopTimesModel.sequelizeModel,
            });
            this.sequelizeModel.hasOne(this.tripScheduleRepository.sequelizeModel, {
                as: "schedule",
                foreignKey: "trip_id",
                sourceKey: "trip_id",
                scope: {
                    [sequelize_1.default.Op.and]: [sequelize_1.default.literal(`start_timestamp::date = CURRENT_DATE`)],
                },
            });
        };
        /** Retrieves all gtfs trips
         * @param {object} options Options object with params
         * @param {number} [options.limit] Limit
         * @param {number} [options.offset] Offset
         * @param {boolean} [options.route] Enhance response with route data
         * @param {string} [options.stopId] Filter routes by specific stop
         * @param {string} [options.date] Filter by specific date in the 'YYYY-MM-DD' format
         * @returns Array of the retrieved records
         */
        this.GetAll = async (options = {}) => {
            const { limit, offset, stopId, date } = options;
            try {
                const include = [];
                if (stopId) {
                    include.push({
                        as: "has_stop_id",
                        attributes: [],
                        duplicating: false,
                        model: this.dbConnector.getConnection().models[ropid_gtfs_1.RopidGTFS.stop_times.pgTableName],
                        where: {
                            stop_id: stopId,
                        },
                    });
                }
                if (date) {
                    include.push({
                        as: "service",
                        attributes: [],
                        model: this.dbConnector
                            .getConnection()
                            .models[ropid_gtfs_1.RopidGTFS.calendar.pgTableName].scope({ method: ["forDate", date] }),
                    });
                }
                const data = await this.sequelizeModel.findAll({
                    include,
                    limit,
                    offset,
                    order: [["trip_id", "DESC"]],
                });
                return data;
            }
            catch (err) {
                throw new golemio_errors_1.GeneralError("Database error", "GTFSTripsModel", err, 500);
            }
        };
        /** Retrieves specific GTFS trip
         * @param {string} id Id of the trip
         * @param {object} [options] Options object with params
         * @param {boolean} [options.route] Enhance response with route data
         * @param {boolean} [options.shapes] Enhance response with shape data
         * @param {boolean} [options.service] Enhance response with service data
         * @param {boolean} [options.stops] Enhance response with stop data
         * @param {boolean} [options.stopTimes] Enhance response with stop times data
         * @param {string} [options.date] Filter by specific date in the 'YYYY-MM-DD' format
         * @returns Object of the retrieved record or null
         */
        this.GetOne = async (id, options = {}) => {
            const { route, stopTimes, stops, shapes } = options;
            let trip;
            try {
                trip = await this.sequelizeModel.findByPk(id, { include: this.GetInclusions(options) });
            }
            catch (err) {
                throw new golemio_errors_1.GeneralError("Database error", "GTFSTripsModel", err, 500);
            }
            return trip ? this.ConvertItem(trip, { route, stops, shapes, stopTimes }) : null;
        };
        this.getOneForPublicGtfsLookup = async (id, options = {}) => {
            try {
                const tripEntity = await this.sequelizeModel.findByPk(id, {
                    attributes: ["trip_id", "shape_id", "trip_headsign", "wheelchair_accessible"],
                    include: this.getInclusionsForPublicGtfsLookup(options),
                });
                return tripEntity ? tripEntity.toJSON() : null;
            }
            catch (err) {
                throw new golemio_errors_1.GeneralError("Cannot get GTFS trip from database", this.constructor.name, err, 500);
            }
        };
        /** Prepare ORM query with selected params
         * @param {object} options Options object with params
         * @param {boolean} [options.route] Enhance response with route data
         * @param {boolean} [options.shapes] Enhance response with shape data
         * @param {boolean} [options.service] Enhance response with service data
         * @param {boolean} [options.stops] Enhance response with stop data
         * @param {boolean} [options.stopTimes] Enhance response with stop times data
         * @param {string} [options.date] Filter by specific date in the 'YYYY-MM-DD' format
         * @returns Array of inclusions
         */
        this.GetInclusions = (options) => {
            const { stops, stopTimes, shapes, service, route, date } = options;
            const include = [];
            // stop_times and stops both selected to include, nest them together
            if (stops && stopTimes) {
                include.push({
                    as: "stop_times",
                    attributes: {
                        exclude: ["arrival_time_seconds", "departure_time_seconds", "timepoint"],
                    },
                    include: [
                        {
                            as: "stop",
                            model: this.dbConnector.getConnection().models[ropid_gtfs_1.RopidGTFS.stops.pgTableName],
                        },
                    ],
                    order: [["stop_sequence", "ASC"]],
                    separate: true,
                    model: this.dbConnector.getConnection().models[ropid_gtfs_1.RopidGTFS.stop_times.pgTableName],
                });
                // Only stops or only stop times selected to include
            }
            else {
                stops &&
                    include.push({
                        as: "stops",
                        model: this.dbConnector.getConnection().models[ropid_gtfs_1.RopidGTFS.stops.pgTableName],
                        through: { attributes: [] },
                    });
                stopTimes &&
                    include.push({
                        as: "stop_times",
                        attributes: {
                            exclude: ["arrival_time_seconds", "departure_time_seconds", "timepoint"],
                        },
                        order: [["stop_sequence", "ASC"]],
                        separate: true,
                        model: this.dbConnector.getConnection().models[ropid_gtfs_1.RopidGTFS.stop_times.pgTableName],
                    });
            }
            shapes &&
                include.push({
                    as: "shapes",
                    model: this.dbConnector.getConnection().models[ropid_gtfs_1.RopidGTFS.shapes.pgTableName],
                });
            if (date || service) {
                include.push({
                    as: "service",
                    model: this.dbConnector
                        .getConnection()
                        .models[ropid_gtfs_1.RopidGTFS.calendar.pgTableName].scope({ method: ["forDate", date] }),
                });
            }
            route &&
                include.push({
                    as: "route",
                    model: this.dbConnector.getConnection().models[ropid_gtfs_1.RopidGTFS.routes.pgTableName],
                });
            return include;
        };
        this.getInclusionsForPublicGtfsLookup = (options) => {
            const { shouldIncludeStopTimes, shouldIncludeShapes } = options;
            const include = [];
            include.push({
                as: "route",
                attributes: ["route_id", "route_short_name", "route_type"],
                model: this.dbConnector.getConnection().models[ropid_gtfs_1.RopidGTFS.routes.pgTableName],
            });
            include.push({
                as: "schedule",
                attributes: ["run_number"],
                model: this.dbConnector.getConnection().models[this.tripScheduleRepository.tableName],
            });
            if (shouldIncludeStopTimes) {
                include.push({
                    as: "stop_times",
                    attributes: [
                        "stop_sequence",
                        "shape_dist_traveled",
                        [sequelize_1.default.literal(`EXTRACT(EPOCH FROM "arrival_time"::INTERVAL)::int`), "arrival_time_seconds"],
                        [sequelize_1.default.literal(`EXTRACT(EPOCH FROM "departure_time"::INTERVAL)::int`), "departure_time_seconds"],
                    ],
                    include: [
                        {
                            as: "stop",
                            attributes: ["stop_name", "zone_id", "stop_lon", "stop_lat", "wheelchair_boarding"],
                            required: true,
                            model: this.dbConnector.getConnection().models[ropid_gtfs_1.RopidGTFS.stops.pgTableName],
                        },
                    ],
                    where: {
                        // exclude no stop waypoints
                        drop_off_type: { [sequelize_1.default.Op.ne]: PassengerTransferEnums_1.GtfsStopTimeDropOffType.NoDropOff.toString() },
                        pickup_type: { [sequelize_1.default.Op.ne]: PassengerTransferEnums_1.GtfsStopTimePickupType.NoPickup.toString() },
                    },
                    order: [["stop_sequence", "ASC"]],
                    separate: true,
                    model: this.dbConnector.getConnection().models[ropid_gtfs_1.RopidGTFS.stop_times.pgTableName],
                });
            }
            if (shouldIncludeShapes) {
                include.push({
                    as: "shapes",
                    attributes: ["shape_dist_traveled", "shape_pt_lon", "shape_pt_lat"],
                    model: this.dbConnector.getConnection().models[ropid_gtfs_1.RopidGTFS.shapes.pgTableName],
                });
            }
            return include;
        };
        /**
         * Convert a single db result to proper output format with all its included sub-elements
         * @param {object} trip Trip object
         * @param {object} options Options object with params
         * @param {boolean} [options.shapes] If shapes were included in filter then convert shapes payload
         * @param {boolean} [options.stops] If stops were included in filter then convert stops payload
         * @returns A converted item of the result
         */
        this.ConvertItem = (trip, options) => {
            const { stop_times: stopTimesItems = [], stops: stopItems = [], shapes: shapeItems = [], ...item } = trip.toJSON();
            return {
                ...item,
                ...(options.route && {
                    route: {
                        ...item.route,
                        is_night: item.route.is_night === "1",
                        is_regional: item.route.is_regional === "1",
                        is_substitute_transport: item.route.is_substitute_transport === "1",
                    },
                }),
                ...(options.stops &&
                    options.stopTimes && {
                    stop_times: stopTimesItems.map((stopTime) => {
                        // exclude computed fields from stop time
                        const { arrival_time_seconds, departure_time_seconds, ...convertedStopTime } = stopTime;
                        convertedStopTime.stop = this.BuildResponse(stopTime.stop, "stop_lon", "stop_lat");
                        return convertedStopTime;
                    }),
                }),
                ...(options.stops &&
                    !options.stopTimes && {
                    stops: stopItems.map((stop) => this.BuildResponse(stop, "stop_lon", "stop_lat")),
                }),
                ...(!options.stops &&
                    options.stopTimes && {
                    stop_times: stopTimesItems.map((stopTime) => {
                        // exclude computed fields from stop time
                        const { arrival_time_seconds, departure_time_seconds, ...convertedStopTime } = stopTime;
                        return convertedStopTime;
                    }),
                }),
                ...(options.shapes && {
                    shapes: shapeItems.map((shape) => this.BuildResponse(shape, "shape_pt_lon", "shape_pt_lat")),
                }),
            };
        };
        /**
         * Builds the correct format of a response data
         */
        this.BuildResponse = (responseObject, longLoc, latLoc) => {
            return (0, Geo_1.buildGeojsonFeature)(responseObject, longLoc, latLoc, true);
        };
        this.dbConnector = ioc_1.OutputGatewayContainer.resolve(ioc_1.ContainerToken.PostgresDatabase);
        this.tripScheduleRepository = new GtfsTripScheduleRepository_1.GtfsTripScheduleRepository();
    }
}
exports.GTFSTripsModel = GTFSTripsModel;
//# sourceMappingURL=GTFSTripsModel.js.map