"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.JISInfotextStopSuppressionFilter = void 0;
const JISInfotextSeverityLevelPriority_1 = require("../../../helpers/jis/JISInfotextSeverityLevelPriority");
const tsyringe_1 = require("@golemio/core/dist/shared/tsyringe");
let JISInfotextStopSuppressionFilter = class JISInfotextStopSuppressionFilter {
    filterInfotexts(infotexts, { includeFuture }) {
        if (!includeFuture) {
            return this.filterInfotextsExcludingFuture(infotexts);
        }
        else {
            return this.filterInfotextsIncludingFuture(infotexts);
        }
    }
    filterInfotextsExcludingFuture(infotexts) {
        const topInfotextSeverityLevelByStopId = this.mapTopInfotextSeverityLevelByStopId(infotexts);
        const filteredInfotexts = [];
        for (const infotext of infotexts) {
            if (!infotext.stops) {
                continue;
            }
            const infotextPrio = JISInfotextSeverityLevelPriority_1.JISInfotextSeverityLevelPriority[infotext.severity_level];
            // Assign directly to the original object — not a spread copy. For Sequelize model
            // instances, `stops` is a prototype getter (not an own property), so a spread copy
            // would not include it, causing a TypeError on the filter call.
            infotext.stops = infotext.stops.filter((stop) => topInfotextSeverityLevelByStopId.get(this.getStopId(stop)) === infotextPrio);
            if (infotext.stops.length > 0) {
                filteredInfotexts.push(infotext);
            }
        }
        return filteredInfotexts;
    }
    getStopId(stop) {
        if ("getDataValue" in stop && typeof stop.getDataValue === "function") {
            return stop.getDataValue("stop_id");
        }
        else {
            return stop.stop_id;
        }
    }
    mapTopInfotextSeverityLevelByStopId(infotexts) {
        const topInfotextSeverityLevelByStopId = new Map();
        for (const infotext of infotexts) {
            const infotextPrio = JISInfotextSeverityLevelPriority_1.JISInfotextSeverityLevelPriority[infotext.severity_level];
            for (const stop of infotext.stops ?? []) {
                const currentValue = topInfotextSeverityLevelByStopId.get(this.getStopId(stop)) ?? -Infinity;
                if (infotextPrio > currentValue) {
                    topInfotextSeverityLevelByStopId.set(this.getStopId(stop), infotextPrio);
                }
            }
        }
        return topInfotextSeverityLevelByStopId;
    }
    filterInfotextsIncludingFuture(infotexts) {
        const infotextIntermediaries = this.createInfotextIntermediaries(infotexts);
        const infotextsByStopId = this.mapInfotextsByStopId(infotextIntermediaries);
        const filteredInfotextsByStopId = new Map();
        for (const [stopId, infotextsForStop] of infotextsByStopId.entries()) {
            const filteredInfotextsForStop = this.filterStopInfotexts(infotextsForStop);
            filteredInfotextsByStopId.set(stopId, filteredInfotextsForStop);
        }
        const filteredInfotextsIdsByStopId = this.mapInfotextIdsByStopId(filteredInfotextsByStopId);
        const filteredInfotexts = this.filterAllInfotexts(infotextIntermediaries, filteredInfotextsIdsByStopId);
        return this.createInfotextsFromIntermediaries(filteredInfotexts);
    }
    createInfotextIntermediaries(infotexts) {
        return infotexts.map((infotext) => ({
            data: infotext,
            active_period_start: new Date(infotext.active_period_start),
            active_period_end: infotext.active_period_end ? new Date(infotext.active_period_end) : null,
        }));
    }
    mapInfotextsByStopId(infotexts) {
        const infotextsByStopId = new Map();
        for (const infotext of infotexts) {
            for (const stop of infotext.data.stops ?? []) {
                const stopId = this.getStopId(stop);
                if (!infotextsByStopId.has(stopId)) {
                    infotextsByStopId.set(stopId, []);
                }
                const infotextsForStop = infotextsByStopId.get(stopId);
                infotextsForStop.push(infotext);
            }
        }
        return infotextsByStopId;
    }
    filterStopInfotexts(infotexts) {
        const segments = this.segmentInfotexts(infotexts);
        const filteredSegments = this.sortAndFilterSegments(segments);
        return this.findUniqueInfotextsFromSegments(filteredSegments);
    }
    segmentInfotexts(infotexts) {
        const maxSegmentationDate = this.findSegmentationThreshold(infotexts);
        const segments = [];
        for (const infotext of infotexts) {
            if (infotext.data.repeat_enabled) {
                segments.push(...this.segmentRepeatedInfotext(infotext, maxSegmentationDate));
            }
            else {
                const start = infotext.active_period_start;
                const end = infotext.active_period_end;
                segments.push({ infotext, start, end });
            }
        }
        return segments;
    }
    findSegmentationThreshold(infotexts) {
        let maxEndDate = new Date(0);
        let maxStartDate = new Date(0);
        for (const infotext of infotexts) {
            if (infotext.active_period_end && infotext.active_period_end > maxEndDate) {
                maxEndDate = infotext.active_period_end;
            }
            if (infotext.active_period_start > maxStartDate) {
                maxStartDate = infotext.active_period_start;
            }
        }
        return this.addDay(maxEndDate > maxStartDate ? maxEndDate : maxStartDate);
    }
    addDay(date, multiplier = 1) {
        const newDate = new Date(date);
        newDate.setDate(date.getDate() + multiplier);
        return newDate;
    }
    *segmentRepeatedInfotext(infotext, segmentationThreshold) {
        // Repeat time windows going through or ending at midnight (e.g. 23:00-01:00, 23:00-00:00)
        const isInfotextActiveUntilOrOverMidnight = infotext.data.repeat_time_end < infotext.data.repeat_time_start;
        const minSegmentationDay = isInfotextActiveUntilOrOverMidnight
            ? this.addDay(infotext.active_period_start, -1)
            : infotext.active_period_start;
        const maxSegmentationDay = this.getMaxSegmentationDayForInfotext(infotext, segmentationThreshold);
        for (let day = minSegmentationDay; day <= maxSegmentationDay; day = this.addDay(day)) {
            const start = this.getDatetimeFromRepeatTime(infotext.data.repeat_time_start, day);
            let end = this.getDatetimeFromRepeatTime(infotext.data.repeat_time_end, day);
            if (isInfotextActiveUntilOrOverMidnight) {
                end = this.addDay(end);
            }
            const boundedSegment = this.fitFiniteSegmentToInfotextTimeBounds({ infotext, start, end });
            if (!boundedSegment) {
                continue;
            }
            yield boundedSegment;
        }
        if (!infotext.active_period_end) {
            const start = this.getDatetimeFromRepeatTime(infotext.data.repeat_time_start, this.addDay(maxSegmentationDay));
            yield { infotext, start, end: null };
        }
    }
    getMaxSegmentationDayForInfotext(infotext, segmentationThreshold) {
        return infotext.active_period_end && infotext.active_period_end <= segmentationThreshold
            ? infotext.active_period_end
            : segmentationThreshold;
    }
    getDatetimeFromRepeatTime(repeatTime, day) {
        const [hours, minutes, seconds] = repeatTime.split(":").map((fragment) => parseInt(fragment, 10));
        const datetime = new Date(day);
        datetime.setHours(hours, minutes, seconds ?? 0, 0);
        return datetime;
    }
    fitFiniteSegmentToInfotextTimeBounds(segment) {
        let { infotext, start, end } = segment;
        if (start < infotext.active_period_start) {
            start = infotext.active_period_start;
        }
        if (infotext.active_period_end && end > infotext.active_period_end) {
            end = infotext.active_period_end;
        }
        if (start > end) {
            return null;
        }
        return { infotext, start, end };
    }
    sortAndFilterSegments(segments) {
        segments.sort((a, b) => {
            const startTimeComparisonResult = a.start.getTime() - b.start.getTime();
            if (startTimeComparisonResult !== 0) {
                return startTimeComparisonResult;
            }
            else if (this.isSegmentBLesserThanA(a, b)) {
                return -1;
            }
            else if (this.isSegmentBLesserThanA(b, a)) {
                return 1;
            }
            else {
                return 0;
            }
        });
        const filteredSegments = [];
        let prevSegment = segments.at(0);
        for (let i = 1; i < segments.length; i++) {
            const currentSegment = segments[i];
            if (this.isSegmentBLesserThanA(prevSegment, currentSegment)) {
                continue;
            }
            filteredSegments.push(prevSegment);
            prevSegment = currentSegment;
        }
        if (prevSegment) {
            filteredSegments.push(prevSegment);
        }
        return filteredSegments;
    }
    isSegmentBLesserThanA(a, b) {
        const aIsMoreSevere = JISInfotextSeverityLevelPriority_1.JISInfotextSeverityLevelPriority[b.infotext.data.severity_level] <
            JISInfotextSeverityLevelPriority_1.JISInfotextSeverityLevelPriority[a.infotext.data.severity_level];
        const aDoesNotStartLater = b.start >= a.start;
        const aDoesNotEndSooner = !a.end || (b.end && b.end <= a.end) || false;
        const bIsWithinTimeBoundsOfA = aDoesNotStartLater && aDoesNotEndSooner;
        return aIsMoreSevere && bIsWithinTimeBoundsOfA;
    }
    findUniqueInfotextsFromSegments(segments) {
        const infotexts = new Map();
        for (const segment of segments) {
            infotexts.set(segment.infotext.data.id, segment.infotext);
        }
        return [...infotexts.values()];
    }
    mapInfotextIdsByStopId(infotextsByStopId) {
        const infotextsIdsByStopId = new Map();
        for (const [stopId, infotextsForStop] of infotextsByStopId.entries()) {
            const infotextIdsForStop = infotextsForStop.map((infotext) => infotext.data.id);
            infotextsIdsByStopId.set(stopId, new Set(infotextIdsForStop));
        }
        return infotextsIdsByStopId;
    }
    filterAllInfotexts(infotexts, filteredInfotextsIdsByStopId) {
        const filteredInfotexts = [];
        for (const infotext of infotexts) {
            if (!infotext.data.stops) {
                continue;
            }
            const filteredStops = infotext.data.stops.filter((stop) => filteredInfotextsIdsByStopId.get(this.getStopId(stop))?.has(infotext.data.id) ?? false);
            if (filteredStops.length > 0) {
                // Same as filterInfotextsExcludingFuture: mutate in-place rather than spreading,
                // because spreading a Sequelize model loses its prototype-getter-backed properties.
                // `infotext.data` is a concrete union type; direct assignment hits TypeScript's
                // union-write restriction (requires intersection of member types). Object.assign
                // sidesteps that while still invoking the Sequelize setter at runtime.
                Object.assign(infotext.data, { stops: filteredStops });
                filteredInfotexts.push(infotext);
            }
        }
        return filteredInfotexts;
    }
    createInfotextsFromIntermediaries(infotextIntermediaries) {
        return infotextIntermediaries.map((intermediary) => intermediary.data);
    }
};
exports.JISInfotextStopSuppressionFilter = JISInfotextStopSuppressionFilter;
exports.JISInfotextStopSuppressionFilter = JISInfotextStopSuppressionFilter = __decorate([
    (0, tsyringe_1.injectable)()
], JISInfotextStopSuppressionFilter);
//# sourceMappingURL=JISInfotextStopSuppressionFilter.js.map