"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.GTFSStopModel = void 0;
const StopEnums_1 = require("../../../helpers/StopEnums");
const const_1 = require("../../../schema-definitions/const");
const ropid_gtfs_1 = require("../../../schema-definitions/ropid-gtfs");
const StopDto_1 = require("../../../schema-definitions/ropid-gtfs/models/StopDto");
const CoreToken_1 = require("@golemio/core/dist/helpers/ioc/CoreToken");
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 GtfsStopParser_1 = require("../../../helpers/GtfsStopParser");
class GTFSStopModel extends models_1.SequelizeModel {
    constructor() {
        super(ropid_gtfs_1.RopidGTFS.stops.name, ropid_gtfs_1.RopidGTFS.stops.pgTableName, StopDto_1.StopDto.attributeModel, {
            schema: const_1.PG_SCHEMA,
        });
        this.outputAttributes = [];
        this.Associate = () => {
            this.sequelizeModel.hasMany(this.sequelizeModel, {
                as: "stops_self",
                foreignKey: "asw_node_id",
                sourceKey: "asw_node_id",
            });
        };
        /**
         *  Retrieve multiple GTFS stop ids by ASW node id
         */
        this.getMultipleIdsByAswNode = async (aswNodeId) => {
            try {
                const stops = await this.sequelizeModel.findAll({
                    attributes: ["stop_id"],
                    raw: true,
                    where: {
                        asw_node_id: aswNodeId,
                        location_type: StopEnums_1.GtfsStopLocationType.StopOrPlatform,
                    },
                });
                if (!stops || stops.length === 0) {
                    return [];
                }
                return stops.map((stop) => stop.stop_id);
            }
            catch (error) {
                throw new golemio_errors_1.GeneralError("Error while getting GTFS stop ids", this.constructor.name, error, 500);
            }
        };
        /**
         * Retrieve specific gtfs stop
         */
        this.GetOne = async (id) => {
            return this.sequelizeModel.findByPk(id).then((data) => {
                if (data) {
                    return (0, Geo_1.buildGeojsonFeature)(data, "stop_lon", "stop_lat", true);
                }
                return null;
            });
        };
        this.prepareCisStops = async (cisIds) => {
            const stops = await this.cisStopsModel.findAll({
                raw: true,
                where: {
                    [sequelize_1.default.Op.or]: {
                        cis: cisIds,
                    },
                },
            });
            return stops;
        };
        const connector = ioc_1.OutputGatewayContainer.resolve(CoreToken_1.CoreToken.PostgresConnector);
        this.outputAttributes = Object.keys(StopDto_1.StopDto.attributeModel);
        const notUsedColumns = [
            "stop_code",
            "stop_desc",
            "stop_url",
            "stop_timezone",
            "asw_node_id",
            "asw_stop_id",
            "computed_cis_stop_id",
        ];
        for (const column of notUsedColumns) {
            this.sequelizeModel.removeAttribute(column);
            this.outputAttributes.splice(this.outputAttributes.indexOf(column), 1);
        }
        this.cisStopsModel = connector
            .getConnection()
            .define(ropid_gtfs_1.RopidGTFS.cis_stops.pgTableName, ropid_gtfs_1.RopidGTFS.cis_stops.outputSequelizeAttributes, {
            schema: const_1.PG_SCHEMA,
        });
    }
    async GetAll(options) {
        let attributes = [...this.outputAttributes];
        let order = [];
        let where = {};
        let allGtfsIds = [];
        if (options.aswIds && options.aswIds.length > 0) {
            where[sequelize_1.default.Op.or] = GtfsStopParser_1.GtfsStopParser.prepareAswWhereOptions(options.aswIds);
        }
        if (options.cisIds && options.cisIds.length > 0) {
            let stops;
            try {
                stops = await this.prepareCisStops(options.cisIds);
            }
            catch (err) {
                throw new golemio_errors_1.GeneralError("Database error", "GTFSStopModel", err, 500);
            }
            // after all stops by other than GTFS ids are collected, we create proper GTFS ids with % like sign.
            // GTFS stops must be split into more ids due to more tarriff zones even if it is one physical stop.
            for (const stop of stops) {
                allGtfsIds.push("(^U" + stop.id.replace("/", "Z") + "(P|N)?(_\\d+)?$)");
            }
            // If aswIds and cisIds are not belong to any GTFS ids and no other gtfsIds or names are given,
            // then return empty stops array
            if (allGtfsIds.length === 0 &&
                (options.gtfsIds === undefined || options.gtfsIds?.length === 0) &&
                (options.names === undefined || options.names.length === 0)) {
                return options.returnRaw ? [] : (0, Geo_1.buildGeojsonFeatureCollection)([], "stop_lon", "stop_lat", true);
            }
        }
        if (options.lng && options.lat) {
            const location = sequelize_1.default.literal(`ST_GeomFromText('POINT(${options.lng} ${options.lat})')`);
            const distance = sequelize_1.default.fn("ST_DistanceSphere", sequelize_1.default.literal("ST_MakePoint(stop_lon, stop_lat)"), location);
            attributes.push([distance, "distance"]);
            order.push(["distance", "ASC"]);
            if (options.range) {
                where.distance = {
                    [sequelize_1.default.Op.lte]: options.range,
                };
            }
        }
        order.push(["stop_id", "ASC"]);
        for (const stop of options.gtfsIds ?? []) {
            allGtfsIds.push("(^" + stop + "$)");
        }
        if (allGtfsIds.length > 0) {
            where.stop_id = {
                [sequelize_1.default.Op.regexp]: allGtfsIds.join("|"),
            };
        }
        if (options.names && options.names?.length > 0) {
            where.stop_name = options.names;
        }
        if (options.locationType !== undefined) {
            where.location_type = options.locationType;
        }
        if (options.appendAswId) {
            const aswId = sequelize_1.default.fn("json_build_object", "node", sequelize_1.default.col(`${ropid_gtfs_1.RopidGTFS.stops.pgTableName}.asw_node_id`), "stop", sequelize_1.default.col(`${ropid_gtfs_1.RopidGTFS.stops.pgTableName}.asw_stop_id`));
            attributes.push([aswId, "asw_id"]);
        }
        let include = undefined;
        if (options.includeMetroTrains) {
            // Workaround for distinct selection
            // https://github.com/sequelize/sequelize/issues/2996
            attributes[0] = [
                sequelize_1.default.literal(`DISTINCT ON(${ropid_gtfs_1.RopidGTFS.stops.pgTableName}.stop_id) ${ropid_gtfs_1.RopidGTFS.stops.pgTableName}.${attributes[0]}`),
                attributes[0],
            ];
            const findOptions = this.prepareFindOptionsForMetro(where);
            include = findOptions.include;
            where = findOptions.where;
        }
        let data = [];
        try {
            data = await this.sequelizeModel.findAll({
                attributes,
                include,
                where,
                order,
                limit: options.limit,
                offset: options.offset,
                raw: true,
                subQuery: false,
            });
        }
        catch (err) {
            throw new golemio_errors_1.GeneralError("Database error", "GTFSStopModel", err, 500);
        }
        return options.returnRaw ? data : (0, Geo_1.buildGeojsonFeatureCollection)(data, "stop_lon", "stop_lat", true);
    }
    prepareFindOptionsForMetro(innerWhere) {
        // Select metro and train stops within a node
        //   matching the previous where criteria
        //   matching asw_node_id (stop_id U1040Z16P -> asw_node_id 1040)
        //   with asw_stop_id greater than or equal to 100 (stop_id U1040Z101P -> asw_stop_id 101)
        //     indicating a metro or train stop
        // EX: U1040Z16P Na Knížecí + U1040Z101P Anděl
        const include = {
            as: "stops_self",
            model: this.sequelizeModel,
            attributes: [],
            on: {
                asw_node_id: {
                    [sequelize_1.default.Op.eq]: sequelize_1.default.col(`${ropid_gtfs_1.RopidGTFS.stops.pgTableName}.asw_node_id`),
                },
            },
            where: innerWhere,
        };
        const where = {
            [sequelize_1.default.Op.or]: [
                innerWhere,
                {
                    asw_stop_id: { [sequelize_1.default.Op.gte]: 100 },
                },
            ],
        };
        return { include, where };
    }
}
exports.GTFSStopModel = GTFSStopModel;
//# sourceMappingURL=GTFSStopModel.js.map