var garminsdk = (function (exports, msfsSdk) {
    'use strict';

    /**
     * SimVar names for Garmin external GPS steering command data.
     */
    exports.APExternalGpsSteerCommandSimVars = void 0;
    (function (APExternalGpsSteerCommandSimVars) {
        APExternalGpsSteerCommandSimVars["IsValid"] = "L:WT_Garmin_External_GPS_Steer_Command_Is_Valid";
        APExternalGpsSteerCommandSimVars["BankAngle"] = "L:WT_Garmin_External_GPS_Steer_Command_Bank_Angle";
        APExternalGpsSteerCommandSimVars["Dtk"] = "L:WT_Garmin_External_GPS_Steer_Command_Dtk";
        APExternalGpsSteerCommandSimVars["Xtk"] = "L:WT_Garmin_External_GPS_Steer_Command_Xtk";
        APExternalGpsSteerCommandSimVars["Tae"] = "L:WT_Garmin_External_GPS_Steer_Command_Tae";
    })(exports.APExternalGpsSteerCommandSimVars || (exports.APExternalGpsSteerCommandSimVars = {}));
    /**
     * SimVar names for Garmin external VNAV guidance data.
     */
    exports.APExternalVNavGuidanceSimVars = void 0;
    (function (APExternalVNavGuidanceSimVars) {
        APExternalVNavGuidanceSimVars["State"] = "L:WT_Garmin_External_VNav_State";
        APExternalVNavGuidanceSimVars["IsActive"] = "L:WT_Garmin_External_VNav_Is_Active";
        APExternalVNavGuidanceSimVars["PathMode"] = "L:WT_Garmin_External_VNav_Path_Mode";
        APExternalVNavGuidanceSimVars["ArmedClimbMode"] = "L:WT_Garmin_External_VNav_Armed_Climb_Mode";
        APExternalVNavGuidanceSimVars["ShouldActivateClimbMode"] = "L:WT_Garmin_External_VNav_Should_Activate_Climb_Mode";
        APExternalVNavGuidanceSimVars["AltitudeCaptureType"] = "L:WT_Garmin_External_VNav_Alt_Capture_Type";
        APExternalVNavGuidanceSimVars["ShouldCaptureAltitude"] = "L:WT_Garmin_External_VNav_Should_Capture_Alt";
        APExternalVNavGuidanceSimVars["AltitudeToCapture"] = "L:WT_Garmin_External_VNav_Alt_To_Capture";
    })(exports.APExternalVNavGuidanceSimVars || (exports.APExternalVNavGuidanceSimVars = {}));
    /**
     * SimVar names for Garmin external vertical path guidance data.
     */
    exports.APExternalVerticalPathGuidanceSimVars = void 0;
    (function (APExternalVerticalPathGuidanceSimVars) {
        APExternalVerticalPathGuidanceSimVars["IsValid"] = "L:WT_Garmin_External_Vertical_Path_Is_Valid";
        APExternalVerticalPathGuidanceSimVars["Fpa"] = "L:WT_Garmin_External_Vertical_Path_Fpa";
        APExternalVerticalPathGuidanceSimVars["Deviation"] = "L:WT_Garmin_External_Vertical_Path_Deviation";
    })(exports.APExternalVerticalPathGuidanceSimVars || (exports.APExternalVerticalPathGuidanceSimVars = {}));
    /**
     * SimVar names for Garmin external glidepath guidance data.
     */
    exports.APExternalGlidepathGuidanceSimVars = void 0;
    (function (APExternalGlidepathGuidanceSimVars) {
        APExternalGlidepathGuidanceSimVars["ApproachHasGp"] = "L:WT_Garmin_External_Glidepath_Approach_Has_Gp";
        APExternalGlidepathGuidanceSimVars["IsValid"] = "L:WT_Garmin_External_Glidepath_Is_Valid";
        APExternalGlidepathGuidanceSimVars["CanCapture"] = "L:WT_Garmin_External_Glidepath_Can_Capture";
        APExternalGlidepathGuidanceSimVars["Fpa"] = "L:WT_Garmin_External_VNav_Path_Fpa";
        APExternalGlidepathGuidanceSimVars["Deviation"] = "L:WT_Garmin_External_VNav_Path_Deviation";
    })(exports.APExternalGlidepathGuidanceSimVars || (exports.APExternalGlidepathGuidanceSimVars = {}));

    /**
     * A provider of external autopilot guidance data. Data are sourced from indexed SimVars whose roots are defined in
     * `APExternalGpsSteerCommandSimVars`, `APExternalVNavGuidanceSimVars`, `APExternalVerticalPathGuidanceSimVars`, and
     * `APExternalGlidepathGuidanceSimVars`.
     */
    class APExternalGuidanceProvider {
        // eslint-disable-next-line jsdoc/require-returns
        /** The current external GPS steer command. */
        get gpsSteerCommand() {
            return this._gpsSteerCommand;
        }
        // eslint-disable-next-line jsdoc/require-returns
        /** The current external VNAV guidance. */
        get vnavGuidance() {
            return this._vnavGuidance;
        }
        // eslint-disable-next-line jsdoc/require-returns
        /** The current external vertical path guidance. */
        get verticalPathGuidance() {
            return this._verticalPathGuidance;
        }
        // eslint-disable-next-line jsdoc/require-returns
        /** The current external glidepath guidance. */
        get glidepathGuidance() {
            return this._glidepathGuidance;
        }
        /**
         * Creates a new instance of APExternalGuidanceProvider.
         * @param index The index of the guidance SimVars from which this provider sources data.
         * @param options Options with which to configure the provider.
         * @throws Error if `index` is not a non-negative integer.
         */
        constructor(index, options) {
            var _a, _b, _c;
            this.index = index;
            this._gpsSteerCommand = {
                isValid: false,
                desiredBankAngle: 0,
                dtk: 0,
                xtk: 0,
                tae: 0
            };
            this._vnavGuidance = {
                state: msfsSdk.VNavState.Disabled,
                isActive: false,
                pathMode: msfsSdk.VNavPathMode.None,
                armedClimbMode: msfsSdk.APVerticalModes.NONE,
                shouldActivateClimbMode: false,
                altitudeCaptureType: msfsSdk.VNavAltCaptureType.None,
                shouldCaptureAltitude: false,
                altitudeToCapture: 0
            };
            this._verticalPathGuidance = {
                isValid: false,
                fpa: 0,
                deviation: 0
            };
            this._glidepathGuidance = {
                approachHasGlidepath: false,
                isValid: false,
                canCapture: false,
                fpa: 0,
                deviation: 0
            };
            if (!Number.isInteger(index) || index < 0) {
                throw new Error(`APExternalGuidanceProvider: invalid index ${index}`);
            }
            this.supportGpsSteer = (_a = options === null || options === void 0 ? void 0 : options.supportGpsSteer) !== null && _a !== void 0 ? _a : false;
            this.supportVNav = (_b = options === null || options === void 0 ? void 0 : options.supportVNav) !== null && _b !== void 0 ? _b : false;
            this.supportGlidepath = (_c = options === null || options === void 0 ? void 0 : options.supportGlidepath) !== null && _c !== void 0 ? _c : false;
            this.simVarNames = {};
            for (const simVar of Object.values(exports.APExternalGpsSteerCommandSimVars)) {
                this.simVarNames[simVar] = `${simVar}:${index}`;
            }
            for (const simVar of Object.values(exports.APExternalVNavGuidanceSimVars)) {
                this.simVarNames[simVar] = `${simVar}:${index}`;
            }
            for (const simVar of Object.values(exports.APExternalVerticalPathGuidanceSimVars)) {
                this.simVarNames[simVar] = `${simVar}:${index}`;
            }
            for (const simVar of Object.values(exports.APExternalGlidepathGuidanceSimVars)) {
                this.simVarNames[simVar] = `${simVar}:${index}`;
            }
        }
        /**
         * Updates this provider's data.
         */
        update() {
            if (this.supportGpsSteer) {
                this.updateGpsSteerCommand();
            }
            if (this.supportVNav) {
                this.updateVNavGuidance();
                this.updateVerticalPathGuidance();
            }
            if (this.supportGlidepath) {
                this.updateGlidepathGuidance();
            }
        }
        /**
         * Updates this provider's GPS steer command.
         */
        updateGpsSteerCommand() {
            this._gpsSteerCommand.isValid = SimVar.GetSimVarValue(this.simVarNames[exports.APExternalGpsSteerCommandSimVars.IsValid], msfsSdk.SimVarValueType.Bool) !== 0;
            if (this._gpsSteerCommand.isValid) {
                this._gpsSteerCommand.desiredBankAngle = SimVar.GetSimVarValue(this.simVarNames[exports.APExternalGpsSteerCommandSimVars.BankAngle], msfsSdk.SimVarValueType.Degree);
                this._gpsSteerCommand.dtk = SimVar.GetSimVarValue(this.simVarNames[exports.APExternalGpsSteerCommandSimVars.Dtk], msfsSdk.SimVarValueType.Degree);
                this._gpsSteerCommand.xtk = SimVar.GetSimVarValue(this.simVarNames[exports.APExternalGpsSteerCommandSimVars.Xtk], msfsSdk.SimVarValueType.NM);
                this._gpsSteerCommand.tae = SimVar.GetSimVarValue(this.simVarNames[exports.APExternalGpsSteerCommandSimVars.Tae], msfsSdk.SimVarValueType.Degree);
            }
            else {
                this._gpsSteerCommand.desiredBankAngle = 0;
                this._gpsSteerCommand.dtk = 0;
                this._gpsSteerCommand.xtk = 0;
                this._gpsSteerCommand.tae = 0;
            }
        }
        /**
         * Updates this provider's VNAV guidance.
         */
        updateVNavGuidance() {
            this._vnavGuidance.state = SimVar.GetSimVarValue(this.simVarNames[exports.APExternalVNavGuidanceSimVars.State], msfsSdk.SimVarValueType.Number);
            this._vnavGuidance.isActive = SimVar.GetSimVarValue(this.simVarNames[exports.APExternalVNavGuidanceSimVars.IsActive], msfsSdk.SimVarValueType.Bool) !== 0;
            this._vnavGuidance.pathMode = SimVar.GetSimVarValue(this.simVarNames[exports.APExternalVNavGuidanceSimVars.PathMode], msfsSdk.SimVarValueType.Number);
            this._vnavGuidance.armedClimbMode = SimVar.GetSimVarValue(this.simVarNames[exports.APExternalVNavGuidanceSimVars.ArmedClimbMode], msfsSdk.SimVarValueType.Number);
            this._vnavGuidance.shouldActivateClimbMode = SimVar.GetSimVarValue(this.simVarNames[exports.APExternalVNavGuidanceSimVars.ShouldActivateClimbMode], msfsSdk.SimVarValueType.Bool) !== 0;
            this._vnavGuidance.altitudeCaptureType = SimVar.GetSimVarValue(this.simVarNames[exports.APExternalVNavGuidanceSimVars.AltitudeCaptureType], msfsSdk.SimVarValueType.Number);
            this._vnavGuidance.shouldCaptureAltitude = SimVar.GetSimVarValue(this.simVarNames[exports.APExternalVNavGuidanceSimVars.ShouldCaptureAltitude], msfsSdk.SimVarValueType.Bool) !== 0;
            this._vnavGuidance.altitudeToCapture = SimVar.GetSimVarValue(this.simVarNames[exports.APExternalVNavGuidanceSimVars.AltitudeToCapture], msfsSdk.SimVarValueType.Feet);
        }
        /**
         * Updates this provider's vertical path guidance.
         */
        updateVerticalPathGuidance() {
            this._verticalPathGuidance.isValid = SimVar.GetSimVarValue(this.simVarNames[exports.APExternalVerticalPathGuidanceSimVars.IsValid], msfsSdk.SimVarValueType.Bool) !== 0;
            if (this._verticalPathGuidance.isValid) {
                this._verticalPathGuidance.fpa = SimVar.GetSimVarValue(this.simVarNames[exports.APExternalVerticalPathGuidanceSimVars.Fpa], msfsSdk.SimVarValueType.Degree);
                this._verticalPathGuidance.deviation = SimVar.GetSimVarValue(this.simVarNames[exports.APExternalVerticalPathGuidanceSimVars.Deviation], msfsSdk.SimVarValueType.Feet);
            }
            else {
                this._verticalPathGuidance.fpa = 0;
                this._verticalPathGuidance.deviation = 0;
            }
        }
        /**
         * Updates this provider's glidepath guidance.
         */
        updateGlidepathGuidance() {
            this._glidepathGuidance.approachHasGlidepath = SimVar.GetSimVarValue(this.simVarNames[exports.APExternalGlidepathGuidanceSimVars.ApproachHasGp], msfsSdk.SimVarValueType.Bool) !== 0;
            this._glidepathGuidance.isValid = SimVar.GetSimVarValue(this.simVarNames[exports.APExternalGlidepathGuidanceSimVars.IsValid], msfsSdk.SimVarValueType.Bool) !== 0;
            if (this._glidepathGuidance.isValid) {
                this._glidepathGuidance.canCapture = SimVar.GetSimVarValue(this.simVarNames[exports.APExternalGlidepathGuidanceSimVars.CanCapture], msfsSdk.SimVarValueType.Bool) !== 0;
                this._glidepathGuidance.fpa = SimVar.GetSimVarValue(this.simVarNames[exports.APExternalGlidepathGuidanceSimVars.Fpa], msfsSdk.SimVarValueType.Degree);
                this._glidepathGuidance.deviation = SimVar.GetSimVarValue(this.simVarNames[exports.APExternalGlidepathGuidanceSimVars.Deviation], msfsSdk.SimVarValueType.Feet);
            }
            else {
                this._glidepathGuidance.canCapture = false;
                this._glidepathGuidance.fpa = 0;
                this._glidepathGuidance.deviation = 0;
            }
        }
    }

    /**
     * A director that handles OBS Lateral Navigation.
     * @deprecated
     */
    class GarminObsDirector {
        /**
         * Creates an instance of the GPS OBS Director.
         * @param bus The event bus to use with this instance.
         * @param apValues Autopilot values from this director's parent autopilot.
         * @param options Options to configure the new director.
         */
        constructor(bus, apValues, options) {
            var _a;
            this.bus = bus;
            this.apValues = apValues;
            this.geoPointCache = [new msfsSdk.GeoPoint(0, 0)];
            this.geoCircleCache = [new msfsSdk.GeoCircle(new Float64Array(3), 0)];
            this.publisher = this.bus.getPublisher();
            this.obsSetting = 0;
            this.obsActive = false;
            this.dtk = undefined;
            this.xtk = undefined;
            this.distanceRemaining = 0;
            this.legIndex = 0;
            this.leg = null;
            this.planePos = new msfsSdk.GeoPoint(0, 0);
            this.groundTrack = 0;
            this.tas = 0;
            this.obsFix = msfsSdk.GeoPointSubject.create(new msfsSdk.GeoPoint(0, 0));
            this.obsMagVar = this.obsFix.map(fix => msfsSdk.MagVar.get(fix));
            this.isTracking = false;
            this.needSubLNavData = false;
            this.lnavData = msfsSdk.ObjectSubject.create({
                dtk: 0,
                xtk: 0,
                trackingState: {
                    isTracking: false,
                    globalLegIndex: 0,
                    transitionMode: msfsSdk.LNavTransitionMode.None,
                    vectorIndex: 0,
                    isSuspended: true
                },
                isTracking: false,
                legIndex: 0,
                transitionMode: msfsSdk.LNavTransitionMode.None,
                vectorIndex: 0,
                courseToSteer: 0,
                isSuspended: true,
                alongLegDistance: 0,
                legDistanceRemaining: 0,
                alongVectorDistance: 0,
                vectorDistanceRemaining: 0
            });
            this.lnavDataHandler = (obj, key, value) => {
                switch (key) {
                    case 'dtk':
                        SimVar.SetSimVarValue(msfsSdk.LNavVars.DTK, msfsSdk.SimVarValueType.Degree, value);
                        break;
                    case 'xtk':
                        SimVar.SetSimVarValue(msfsSdk.LNavVars.XTK, msfsSdk.SimVarValueType.NM, value);
                        break;
                    case 'isTracking':
                        SimVar.SetSimVarValue(msfsSdk.LNavVars.IsTracking, msfsSdk.SimVarValueType.Bool, value);
                        break;
                    case 'legIndex':
                        SimVar.SetSimVarValue(msfsSdk.LNavVars.TrackedLegIndex, msfsSdk.SimVarValueType.Number, value);
                        break;
                    case 'transitionMode':
                        SimVar.SetSimVarValue(msfsSdk.LNavVars.TransitionMode, msfsSdk.SimVarValueType.Number, value);
                        break;
                    case 'vectorIndex':
                        SimVar.SetSimVarValue(msfsSdk.LNavVars.TrackedVectorIndex, msfsSdk.SimVarValueType.Number, value);
                        break;
                    case 'courseToSteer':
                        SimVar.SetSimVarValue(msfsSdk.LNavVars.CourseToSteer, msfsSdk.SimVarValueType.Degree, value);
                        break;
                    case 'isSuspended':
                        SimVar.SetSimVarValue(msfsSdk.LNavVars.IsSuspended, msfsSdk.SimVarValueType.Bool, value);
                        break;
                    case 'alongLegDistance':
                        SimVar.SetSimVarValue(msfsSdk.LNavVars.LegDistanceAlong, msfsSdk.SimVarValueType.NM, value);
                        break;
                    case 'legDistanceRemaining':
                        SimVar.SetSimVarValue(msfsSdk.LNavVars.LegDistanceRemaining, msfsSdk.SimVarValueType.NM, value);
                        break;
                    case 'alongVectorDistance':
                        SimVar.SetSimVarValue(msfsSdk.LNavVars.VectorDistanceAlong, msfsSdk.SimVarValueType.NM, value);
                        break;
                    case 'vectorDistanceRemaining':
                        SimVar.SetSimVarValue(msfsSdk.LNavVars.VectorDistanceRemaining, msfsSdk.SimVarValueType.NM, value);
                        break;
                    case 'trackingState':
                        this.publisher.pub('lnav_tracking_state', value, true, true);
                        break;
                }
            };
            const maxBankAngleOpt = (_a = options === null || options === void 0 ? void 0 : options.maxBankAngle) !== null && _a !== void 0 ? _a : undefined;
            switch (typeof maxBankAngleOpt) {
                case 'number':
                    this.maxBankAngleFunc = () => maxBankAngleOpt;
                    break;
                case 'function':
                    this.maxBankAngleFunc = maxBankAngleOpt;
                    break;
                default:
                    this.maxBankAngleFunc = this.apValues.maxBankAngle.get.bind(this.apValues.maxBankAngle);
            }
            const bankRateOpt = options === null || options === void 0 ? void 0 : options.bankRate;
            switch (typeof bankRateOpt) {
                case 'number':
                    this.driveBankFunc = bank => {
                        if (isFinite(bank) && this.driveBank) {
                            this.driveBank(bank, bankRateOpt * this.apValues.simRate.get());
                        }
                    };
                    break;
                case 'function':
                    this.driveBankFunc = bank => {
                        if (isFinite(bank) && this.driveBank) {
                            this.driveBank(bank, bankRateOpt() * this.apValues.simRate.get());
                        }
                    };
                    break;
                default:
                    this.driveBankFunc = bank => {
                        if (isFinite(bank) && this.driveBank) {
                            this.driveBank(bank);
                        }
                    };
            }
            this.lateralInterceptCurve = options === null || options === void 0 ? void 0 : options.lateralInterceptCurve;
            const sub = bus.getSubscriber();
            const adjustCourseSub = sub.on('hEvent').handle((e) => {
                if (e === 'AS1000_PFD_CRS_INC' || e === 'AS1000_MFD_CRS_INC') {
                    this.incrementObs(true);
                }
                else if (e === 'AS1000_PFD_CRS_DEC' || e === 'AS1000_MFD_CRS_DEC') {
                    this.incrementObs(false);
                }
            }, true);
            sub.on(`cdi_select${msfsSdk.CdiUtils.getEventBusTopicSuffix(this.apValues.cdiId)}`).handle(source => {
                if (source.type === msfsSdk.NavSourceType.Gps) {
                    adjustCourseSub.resume();
                }
                else {
                    adjustCourseSub.pause();
                }
            });
            sub.on('gps_obs_active').whenChanged().handle((state) => {
                var _a;
                this.obsActive = state;
                if (this.obsActive) {
                    const calc = (_a = this.leg) === null || _a === void 0 ? void 0 : _a.calculated;
                    let courseMag = undefined;
                    if (calc && calc.endLat !== undefined && calc.endLon !== undefined) {
                        this.obsFix.set(calc.endLat, calc.endLon);
                        const courseTrue = msfsSdk.FlightPathUtils.getLegFinalCourse(calc);
                        if (courseTrue !== undefined) {
                            courseMag = msfsSdk.MagVar.trueToMagnetic(courseTrue, this.obsMagVar.get());
                        }
                    }
                    if (courseMag !== undefined) {
                        this.obsSetting = courseMag;
                    }
                    else if (this.obsSetting < 0 || this.obsSetting > 360) {
                        this.obsSetting = 0;
                    }
                    SimVar.SetSimVarValue('K:GPS_OBS_SET', msfsSdk.SimVarValueType.Degree, this.obsSetting);
                }
                else {
                    this.deactivate();
                }
            });
            sub.on('lnav_is_suspended').whenChanged().handle(isSuspended => {
                if (this.obsActive && !isSuspended) {
                    SimVar.SetSimVarValue('K:GPS_OBS_OFF', 'number', 0);
                }
            });
            sub.on('gps_obs_value').whenChanged().handle((value) => {
                this.obsSetting = value;
            });
            sub.on('track_deg_true').whenChanged().handle((v) => {
                this.groundTrack = v;
            });
            sub.on('tas').whenChanged().handle((v) => {
                this.tas = v;
            });
            sub.on('gps-position').whenChanged().handle((v) => {
                this.planePos.set(v.lat, v.long);
            });
            this.state = msfsSdk.DirectorState.Inactive;
        }
        /** @inheritdoc */
        activate() {
            this.state = msfsSdk.DirectorState.Active;
            if (this.onActivate !== undefined) {
                this.onActivate();
            }
        }
        /** @inheritdoc */
        arm() {
            this.state = msfsSdk.DirectorState.Armed;
            if (this.onArm !== undefined) {
                this.onArm();
            }
        }
        /** @inheritdoc */
        deactivate() {
            this.state = msfsSdk.DirectorState.Inactive;
            if (this.onDeactivate !== undefined) {
                this.onDeactivate();
            }
        }
        /** @inheritdoc */
        setLeg(index, leg) {
            this.legIndex = index;
            this.leg = leg;
        }
        /** @inheritdoc */
        startTracking() {
            if (this.isTracking) {
                return;
            }
            this.needSubLNavData = true;
            this.isTracking = true;
        }
        /** @inheritdoc */
        stopTracking() {
            if (!this.isTracking) {
                return;
            }
            this.lnavData.unsub(this.lnavDataHandler);
            this.needSubLNavData = false;
            this.isTracking = false;
            SimVar.SetSimVarValue('K:GPS_OBS_OFF', msfsSdk.SimVarValueType.Number, 0);
        }
        /**
         * Increments or Decrements the OBS Setting for GPS if in GPS OBS MODE.
         * @param increment is whether to increment (or decrement) the value.
         */
        incrementObs(increment) {
            if (this.obsActive) {
                if (increment) {
                    SimVar.SetSimVarValue('K:GPS_OBS_INC', msfsSdk.SimVarValueType.Number, 0);
                }
                else {
                    SimVar.SetSimVarValue('K:GPS_OBS_DEC', msfsSdk.SimVarValueType.Number, 0);
                }
            }
        }
        /**
         * Updates the lateral director.
         */
        update() {
            var _a, _b;
            this.lnavData.set('legIndex', this.legIndex);
            if (this.isTracking) {
                this.calculateTracking();
            }
            const isTracking = this.dtk !== undefined && this.xtk !== undefined;
            const dtk = (_a = this.dtk) !== null && _a !== void 0 ? _a : 0;
            const xtk = (_b = this.xtk) !== null && _b !== void 0 ? _b : 0;
            this.lnavData.set('isTracking', isTracking);
            this.lnavData.set('dtk', dtk);
            this.lnavData.set('xtk', xtk);
            this.lnavData.set('legDistanceRemaining', this.distanceRemaining);
            this.lnavData.set('vectorDistanceRemaining', this.distanceRemaining);
            const trackingState = this.lnavData.get().trackingState;
            if (trackingState.isTracking !== isTracking || trackingState.globalLegIndex !== this.legIndex) {
                this.lnavData.set('trackingState', {
                    isTracking: isTracking,
                    globalLegIndex: this.legIndex,
                    transitionMode: msfsSdk.LNavTransitionMode.None,
                    vectorIndex: 0,
                    isSuspended: true
                });
            }
            if (this.dtk === undefined || this.xtk === undefined) {
                SimVar.SetSimVarValue('K:GPS_OBS_OFF', msfsSdk.SimVarValueType.Number, 0);
            }
            if (this.state === msfsSdk.DirectorState.Active) {
                this.navigateFlightPath();
            }
            if (this.needSubLNavData) {
                this.lnavData.sub(this.lnavDataHandler, true);
                this.needSubLNavData = false;
            }
        }
        /**
         * Gets the current obs xtk.
         */
        calculateTracking() {
            var _a, _b, _c, _d;
            this.distanceRemaining = 0;
            if (((_b = (_a = this.leg) === null || _a === void 0 ? void 0 : _a.calculated) === null || _b === void 0 ? void 0 : _b.endLat) !== undefined && ((_d = (_c = this.leg) === null || _c === void 0 ? void 0 : _c.calculated) === null || _d === void 0 ? void 0 : _d.endLon) !== undefined) {
                const end = this.geoPointCache[0].set(this.leg.calculated.endLat, this.leg.calculated.endLon);
                this.obsFix.set(end);
                const obsTrue = msfsSdk.MagVar.magneticToTrue(this.obsSetting, this.obsMagVar.get());
                const path = this.geoCircleCache[0].setAsGreatCircle(end, obsTrue);
                this.dtk = path.bearingAt(this.planePos, Math.PI);
                this.xtk = msfsSdk.UnitType.GA_RADIAN.convertTo(path.distance(this.planePos), msfsSdk.UnitType.NMILE);
                const angleRemaining = (path.angleAlong(this.planePos, end, Math.PI) + Math.PI) % msfsSdk.MathUtils.TWO_PI - Math.PI;
                this.distanceRemaining = msfsSdk.UnitType.GA_RADIAN.convertTo(angleRemaining, msfsSdk.UnitType.NMILE);
            }
            else {
                this.dtk = undefined;
                this.xtk = undefined;
            }
        }
        /**
         * Navigates the provided leg flight path.
         */
        navigateFlightPath() {
            if (this.xtk === undefined || this.dtk === undefined) {
                return;
            }
            let absInterceptAngle;
            if (this.lateralInterceptCurve !== undefined) {
                absInterceptAngle = this.lateralInterceptCurve(this.dtk, this.xtk, this.tas);
            }
            else {
                absInterceptAngle = Math.min(Math.pow(Math.abs(this.xtk) * 20, 1.35) + (Math.abs(this.xtk) * 50), 45);
                if (absInterceptAngle <= 2.5) {
                    absInterceptAngle = msfsSdk.NavMath.clamp(Math.abs(this.xtk * 150), 0, 2.5);
                }
            }
            const interceptAngle = this.xtk < 0 ? absInterceptAngle : -1 * absInterceptAngle;
            const courseToSteer = msfsSdk.NavMath.normalizeHeading(this.dtk + interceptAngle);
            const bankAngle = this.desiredBank(courseToSteer);
            if (this.state === msfsSdk.DirectorState.Active) {
                this.driveBankFunc(bankAngle);
            }
            this.lnavData.set('courseToSteer', courseToSteer);
        }
        /**
         * Tries to activate when armed.
         * @returns whether OBS can activate
         */
        canActivate() {
            if (this.xtk !== undefined && Math.abs(this.xtk) < 1) {
                return true;
            }
            return false;
        }
        /**
         * Gets a desired bank from a desired track.
         * @param desiredTrack The desired track.
         * @returns The desired bank angle.
         */
        desiredBank(desiredTrack) {
            const turnDirection = msfsSdk.NavMath.getTurnDirection(this.groundTrack, desiredTrack);
            const headingDiff = Math.abs(msfsSdk.NavMath.diffAngle(this.groundTrack, desiredTrack));
            let baseBank = Math.min(1.25 * headingDiff, this.maxBankAngleFunc());
            baseBank *= (turnDirection === 'left' ? 1 : -1);
            return baseBank;
        }
    }

    /**
     * An LNAV computer module that calculates lateral navigation for an OBS course to the active flight plan waypoint.
     */
    class GarminObsLNavModule {
        /**
         * Creates a new instance of GarminObsLNavModule.
         * @param index The index of this module's parent computer.
         * @param bus The event bus.
         * @param flightPlanner The flight planner from which to source the active flight plan.
         * @param options Options with which to configure the new module.
         */
        constructor(index, bus, flightPlanner, options) {
            var _a, _b;
            this.index = index;
            this.bus = bus;
            this.flightPlanner = flightPlanner;
            this.geoPointCache = [new msfsSdk.GeoPoint(0, 0)];
            this.geoCircleCache = [new msfsSdk.GeoCircle(msfsSdk.Vec3Math.create(), 0)];
            this.publisher = this.bus.getPublisher();
            this.isObsActive = false;
            this.obsCourse = 0;
            this.legIndex = 0;
            this.leg = null;
            this.obsFix = msfsSdk.GeoPointSubject.create(new msfsSdk.GeoPoint(0, 0));
            this.obsMagVar = this.obsFix.map(fix => msfsSdk.MagVar.get(fix));
            this.dtk = undefined;
            this.xtk = undefined;
            this.distanceRemaining = 0;
            this.lnavData = msfsSdk.ObjectSubject.create({
                dtk: 0,
                xtk: 0,
                trackingState: {
                    isTracking: false,
                    globalLegIndex: 0,
                    transitionMode: msfsSdk.LNavTransitionMode.None,
                    vectorIndex: 0,
                    isSuspended: true
                },
                isTracking: false,
                legIndex: 0,
                transitionMode: msfsSdk.LNavTransitionMode.None,
                vectorIndex: 0,
                courseToSteer: 0,
                isSuspended: true,
                alongLegDistance: 0,
                legDistanceRemaining: 0,
                alongVectorDistance: 0,
                vectorDistanceRemaining: 0
            });
            this.lnavDataSub = this.lnavData.sub((obj, key, value) => {
                switch (key) {
                    case 'dtk':
                        SimVar.SetSimVarValue(this.simVarMap[msfsSdk.LNavVars.DTK], msfsSdk.SimVarValueType.Degree, value);
                        break;
                    case 'xtk':
                        SimVar.SetSimVarValue(this.simVarMap[msfsSdk.LNavVars.XTK], msfsSdk.SimVarValueType.NM, value);
                        break;
                    case 'isTracking':
                        SimVar.SetSimVarValue(this.simVarMap[msfsSdk.LNavVars.IsTracking], msfsSdk.SimVarValueType.Bool, value);
                        break;
                    case 'legIndex':
                        SimVar.SetSimVarValue(this.simVarMap[msfsSdk.LNavVars.TrackedLegIndex], msfsSdk.SimVarValueType.Number, value);
                        break;
                    case 'transitionMode':
                        SimVar.SetSimVarValue(this.simVarMap[msfsSdk.LNavVars.TransitionMode], msfsSdk.SimVarValueType.Number, value);
                        break;
                    case 'vectorIndex':
                        SimVar.SetSimVarValue(this.simVarMap[msfsSdk.LNavVars.TrackedVectorIndex], msfsSdk.SimVarValueType.Number, value);
                        break;
                    case 'courseToSteer':
                        SimVar.SetSimVarValue(this.simVarMap[msfsSdk.LNavVars.CourseToSteer], msfsSdk.SimVarValueType.Degree, value);
                        break;
                    case 'isSuspended':
                        SimVar.SetSimVarValue(this.simVarMap[msfsSdk.LNavVars.IsSuspended], msfsSdk.SimVarValueType.Bool, value);
                        break;
                    case 'alongLegDistance':
                        SimVar.SetSimVarValue(this.simVarMap[msfsSdk.LNavVars.LegDistanceAlong], msfsSdk.SimVarValueType.NM, value);
                        break;
                    case 'legDistanceRemaining':
                        SimVar.SetSimVarValue(this.simVarMap[msfsSdk.LNavVars.LegDistanceRemaining], msfsSdk.SimVarValueType.NM, value);
                        break;
                    case 'alongVectorDistance':
                        SimVar.SetSimVarValue(this.simVarMap[msfsSdk.LNavVars.VectorDistanceAlong], msfsSdk.SimVarValueType.NM, value);
                        break;
                    case 'vectorDistanceRemaining':
                        SimVar.SetSimVarValue(this.simVarMap[msfsSdk.LNavVars.VectorDistanceRemaining], msfsSdk.SimVarValueType.NM, value);
                        break;
                    case 'trackingState':
                        this.publisher.pub(this.eventBusTopicMap['lnav_tracking_state'], value, true, true);
                        break;
                }
            }, false, true);
            this.steerCommand = {
                isValid: false,
                desiredBankAngle: 0,
                dtk: 0,
                xtk: 0,
                tae: 0
            };
            this._isActive = false;
            this.needSubLNavData = false;
            if (!msfsSdk.LNavUtils.isValidLNavIndex(index)) {
                throw new Error(`GarminObsLNavModule: invalid index (${index}) specified (must be a non-negative integer).`);
            }
            this.useSimObsState = (_a = options === null || options === void 0 ? void 0 : options.useSimObsState) !== null && _a !== void 0 ? _a : true;
            const simVarSuffix = this.index === 0 ? '' : `:${this.index}`;
            this.simVarMap = {};
            for (const simVar of Object.values(msfsSdk.LNavVars)) {
                this.simVarMap[simVar] = `${simVar}${simVarSuffix}`;
            }
            const eventBusTopicSuffix = msfsSdk.LNavUtils.getEventBusTopicSuffix(this.index);
            this.eventBusTopicMap = {
                'lnav_tracking_state': `lnav_tracking_state${eventBusTopicSuffix}`,
                'lnav_is_awaiting_calc': `lnav_is_awaiting_calc${eventBusTopicSuffix}`
            };
            this.setActiveTopic = `lnav_obs_set_active${eventBusTopicSuffix}`;
            this.setCourseTopic = `lnav_obs_set_course${eventBusTopicSuffix}`;
            const sub = bus.getSubscriber();
            const maxBankAngleOpt = (_b = options === null || options === void 0 ? void 0 : options.maxBankAngle) !== null && _b !== void 0 ? _b : undefined;
            switch (typeof maxBankAngleOpt) {
                case 'number':
                    this.maxBankAngleFunc = () => maxBankAngleOpt;
                    break;
                case 'function':
                    this.maxBankAngleFunc = maxBankAngleOpt;
                    break;
                default: {
                    const maxBankAngle = msfsSdk.ConsumerSubject.create(sub.on('ap_max_bank_value'), 0);
                    this.maxBankAngleFunc = maxBankAngle.get.bind(maxBankAngle);
                }
            }
            this.interceptFunc = options === null || options === void 0 ? void 0 : options.intercept;
            sub.on(this.useSimObsState ? 'gps_obs_active' : `lnav_obs_active${eventBusTopicSuffix}`).whenChanged().handle(isActive => {
                var _a;
                this.isObsActive = isActive;
                if (this.isObsActive) {
                    const calc = (_a = this.leg) === null || _a === void 0 ? void 0 : _a.calculated;
                    let courseMag = undefined;
                    if (calc && calc.endLat !== undefined && calc.endLon !== undefined) {
                        this.obsFix.set(calc.endLat, calc.endLon);
                        const courseTrue = msfsSdk.FlightPathUtils.getLegFinalCourse(calc);
                        if (courseTrue !== undefined) {
                            courseMag = msfsSdk.MagVar.trueToMagnetic(courseTrue, this.obsMagVar.get());
                        }
                    }
                    if (courseMag !== undefined) {
                        this.obsCourse = courseMag;
                    }
                    else if (this.obsCourse < 0 || this.obsCourse > 360) {
                        this.obsCourse = 0;
                    }
                    this.setObsCourse(this.obsCourse);
                }
            });
            sub.on(this.useSimObsState ? 'gps_obs_value' : `lnav_obs_course${eventBusTopicSuffix}`).whenChanged().handle((value) => {
                this.obsCourse = value;
            });
            sub.on(`suspend_sequencing${eventBusTopicSuffix}`).handle(suspend => {
                if (this.isObsActive && !suspend) {
                    this.setObsActive(false);
                }
            });
        }
        /** @inheritDoc */
        getSteerCommand() {
            return this.steerCommand;
        }
        /** @inheritDoc */
        isActive() {
            return this._isActive;
        }
        /** @inheritDoc */
        canActivate() {
            return this.isObsActive && this.leg !== null;
        }
        /** @inheritDoc */
        activate(lnavState) {
            if (this._isActive) {
                return;
            }
            this._isActive = true;
            lnavState.isSuspended = true;
            lnavState.inhibitedSuspendLegIndex = lnavState.globalLegIndex;
            this.needSubLNavData = true;
        }
        /** @inheritDoc */
        deactivate(lnavState) {
            if (!this._isActive) {
                return;
            }
            this._isActive = false;
            lnavState.isSuspended = false;
            this.lnavDataSub.pause();
            this.needSubLNavData = false;
        }
        /** @inheritDoc */
        update(lnavState, aircraftState) {
            var _a, _b;
            const flightPlan = this.flightPlanner.hasActiveFlightPlan() ? this.flightPlanner.getActiveFlightPlan() : undefined;
            this.legIndex = lnavState.globalLegIndex;
            this.leg = flightPlan ? flightPlan.tryGetLeg(lnavState.globalLegIndex) : null;
            if (!this._isActive) {
                return;
            }
            this.lnavData.set('legIndex', this.legIndex);
            this.calculateTracking(aircraftState);
            const isTracking = this.dtk !== undefined && this.xtk !== undefined;
            const dtk = (_a = this.dtk) !== null && _a !== void 0 ? _a : 0;
            const xtk = (_b = this.xtk) !== null && _b !== void 0 ? _b : 0;
            this.lnavData.set('isTracking', isTracking);
            this.lnavData.set('dtk', dtk);
            this.lnavData.set('xtk', xtk);
            this.lnavData.set('legDistanceRemaining', this.distanceRemaining);
            this.lnavData.set('vectorDistanceRemaining', this.distanceRemaining);
            const trackingState = this.lnavData.get().trackingState;
            if (trackingState.isTracking !== isTracking || trackingState.globalLegIndex !== this.legIndex) {
                this.lnavData.set('trackingState', {
                    isTracking: isTracking,
                    globalLegIndex: this.legIndex,
                    transitionMode: msfsSdk.LNavTransitionMode.None,
                    vectorIndex: 0,
                    isSuspended: true
                });
            }
            if (this.dtk === undefined || this.xtk === undefined) {
                this.setObsActive(false);
            }
            this.updateSteerCommand(aircraftState);
            if (this.needSubLNavData) {
                this.lnavDataSub.resume(true);
                this.needSubLNavData = false;
            }
        }
        /**
         * Calculates tracking data for this module's current OBS course.
         * @param aircraftState The current state of the airplane.
         */
        calculateTracking(aircraftState) {
            var _a;
            this.distanceRemaining = 0;
            if (((_a = this.leg) === null || _a === void 0 ? void 0 : _a.calculated) && this.leg.calculated.endLat !== undefined && this.leg.calculated.endLon !== undefined) {
                const end = this.geoPointCache[0].set(this.leg.calculated.endLat, this.leg.calculated.endLon);
                this.obsFix.set(end);
                const obsTrue = msfsSdk.MagVar.magneticToTrue(this.obsCourse, this.obsMagVar.get());
                const path = this.geoCircleCache[0].setAsGreatCircle(end, obsTrue);
                this.dtk = path.bearingAt(aircraftState.planePos, Math.PI);
                this.xtk = msfsSdk.UnitType.GA_RADIAN.convertTo(path.distance(aircraftState.planePos), msfsSdk.UnitType.NMILE);
                const angleRemaining = (path.angleAlong(aircraftState.planePos, end, Math.PI) + Math.PI) % msfsSdk.MathUtils.TWO_PI - Math.PI;
                this.distanceRemaining = msfsSdk.UnitType.GA_RADIAN.convertTo(angleRemaining, msfsSdk.UnitType.NMILE);
            }
            else {
                this.dtk = undefined;
                this.xtk = undefined;
            }
        }
        /**
         * Updates this module's steering command using guidance generated from this module's currently tracked OBS course.
         * @param aircraftState The current state of the airplane.
         */
        updateSteerCommand(aircraftState) {
            if (this.xtk === undefined || this.dtk === undefined) {
                this.steerCommand.isValid = false;
                this.steerCommand.desiredBankAngle = 0;
                this.steerCommand.dtk = 0;
                this.steerCommand.xtk = 0;
                this.steerCommand.tae = 0;
                return;
            }
            let absInterceptAngle;
            if (this.interceptFunc !== undefined) {
                absInterceptAngle = this.interceptFunc(this.dtk, this.xtk, aircraftState.tas);
            }
            else {
                absInterceptAngle = Math.min(Math.pow(Math.abs(this.xtk) * 20, 1.35) + (Math.abs(this.xtk) * 50), 45);
                if (absInterceptAngle <= 2.5) {
                    absInterceptAngle = msfsSdk.NavMath.clamp(Math.abs(this.xtk * 150), 0, 2.5);
                }
            }
            const interceptAngle = this.xtk < 0 ? absInterceptAngle : -1 * absInterceptAngle;
            const courseToSteer = msfsSdk.NavMath.normalizeHeading(this.dtk + interceptAngle);
            const bankAngle = this.desiredBank(courseToSteer, aircraftState.track);
            this.steerCommand.isValid = true;
            this.steerCommand.desiredBankAngle = bankAngle;
            this.steerCommand.dtk = this.dtk;
            this.steerCommand.xtk = this.xtk;
            this.steerCommand.tae = (msfsSdk.MathUtils.diffAngleDeg(this.dtk, aircraftState.track) + 180) % 360 - 180;
            this.lnavData.set('courseToSteer', courseToSteer);
        }
        /**
         * Calculates a desired bank from a desired track and actual ground track.
         * @param desiredTrack The desired track, in degrees.
         * @param actualTrack The airplane's actual ground track, in degrees.
         * @returns The desired bank angle, in degrees. Positive values represent left bank.
         */
        desiredBank(desiredTrack, actualTrack) {
            const turnDirection = msfsSdk.NavMath.getTurnDirection(actualTrack, desiredTrack);
            const headingDiff = Math.abs(msfsSdk.NavMath.diffAngle(actualTrack, desiredTrack));
            let baseBank = Math.min(1.25 * headingDiff, this.maxBankAngleFunc());
            baseBank *= (turnDirection === 'left' ? 1 : -1);
            return baseBank;
        }
        /**
         * Sets whether OBS is active.
         * @param active Whether OBS should be set to active.
         */
        setObsActive(active) {
            if (this.useSimObsState) {
                SimVar.SetSimVarValue(active ? 'K:GPS_OBS_ON' : 'K:GPS_OBS_OFF', msfsSdk.SimVarValueType.Number, 0);
            }
            else {
                this.publisher.pub(this.setActiveTopic, active, true, false);
            }
        }
        /**
         * Sets the OBS course.
         * @param course The course to set, in degrees.
         */
        setObsCourse(course) {
            if (this.useSimObsState) {
                SimVar.SetSimVarValue('K:GPS_OBS_SET', msfsSdk.SimVarValueType.Degree, course);
            }
            else {
                this.publisher.pub(this.setCourseTopic, course, true, false);
            }
        }
    }

    exports.DirectToState = void 0;
    (function (DirectToState) {
        DirectToState[DirectToState["NONE"] = 0] = "NONE";
        DirectToState[DirectToState["TOEXISTING"] = 1] = "TOEXISTING";
        DirectToState[DirectToState["TORANDOM"] = 2] = "TORANDOM";
    })(exports.DirectToState || (exports.DirectToState = {}));
    exports.ProcedureType = void 0;
    (function (ProcedureType) {
        ProcedureType[ProcedureType["DEPARTURE"] = 0] = "DEPARTURE";
        ProcedureType[ProcedureType["ARRIVAL"] = 1] = "ARRIVAL";
        ProcedureType[ProcedureType["APPROACH"] = 2] = "APPROACH";
        ProcedureType[ProcedureType["VISUALAPPROACH"] = 3] = "VISUALAPPROACH";
    })(exports.ProcedureType || (exports.ProcedureType = {}));
    exports.AirwayLegType = void 0;
    (function (AirwayLegType) {
        AirwayLegType[AirwayLegType["NONE"] = 0] = "NONE";
        AirwayLegType[AirwayLegType["ENTRY"] = 1] = "ENTRY";
        AirwayLegType[AirwayLegType["EXIT"] = 2] = "EXIT";
        AirwayLegType[AirwayLegType["ONROUTE"] = 3] = "ONROUTE";
        AirwayLegType[AirwayLegType["EXIT_ENTRY"] = 4] = "EXIT_ENTRY";
    })(exports.AirwayLegType || (exports.AirwayLegType = {}));
    /**
     * Additional Garmin approach types.
     */
    exports.GarminAdditionalApproachType = void 0;
    (function (GarminAdditionalApproachType) {
        GarminAdditionalApproachType[GarminAdditionalApproachType["APPROACH_TYPE_VFR"] = 200] = "APPROACH_TYPE_VFR";
    })(exports.GarminAdditionalApproachType || (exports.GarminAdditionalApproachType = {}));

    /**
     * Garmin flight plan user data keys.
     */
    exports.FmsFplUserDataKey = void 0;
    (function (FmsFplUserDataKey) {
        /** The name of the flight plan. */
        FmsFplUserDataKey["Name"] = "name";
        /** Data describing the flight plan's loaded visual approach procedure. */
        FmsFplUserDataKey["VisualApproach"] = "visual_approach_data";
        /**
         * The designation of the runway associated with the flight plan's loaded visual approach procedure.
         * @deprecated
         */
        FmsFplUserDataKey["VisualApproachRunway"] = "visual_approach";
        /** Data describing the flight plan's loaded VFR approach procedure. */
        FmsFplUserDataKey["VfrApproach"] = "vfr_approach_data";
        /** Whether the flight plan's loaded approach procedure skips an initial course reversal. */
        FmsFplUserDataKey["ApproachSkipCourseReversal"] = "skipCourseReversal";
    })(exports.FmsFplUserDataKey || (exports.FmsFplUserDataKey = {}));

    /* eslint-disable @typescript-eslint/no-non-null-assertion */
    /**
     * Utility Methods for the FMS.
     */
    class FmsUtils {
        // eslint-disable-next-line jsdoc/require-jsdoc
        static onFmsEvent(id, arg2, baseTopic) {
            return (arg2 instanceof msfsSdk.EventBus ? arg2.getSubscriber() : arg2).on(`${baseTopic}${id === '' ? '' : `_${id}`}`);
        }
        /**
         * Gets the departure segment from a flight plan.
         * @param plan A flight plan.
         * @returns The departure segment in the specified flight plan, or `undefined` if one does not exist.
         */
        static getDepartureSegment(plan) {
            // There should only be one departure segment
            for (const segment of plan.segmentsOfType(msfsSdk.FlightPlanSegmentType.Departure)) {
                return segment;
            }
            return undefined;
        }
        /**
         * Gets the first enroute segment from a flight plan.
         * @param plan A flight plan.
         * @returns The first enroute segment in the specified flight plan, or `undefined` if one does not exist.
         */
        static getFirstEnrouteSegment(plan) {
            const segmentCount = plan.segmentCount;
            for (let i = 0; i < segmentCount; i++) {
                const segment = plan.tryGetSegment(i);
                if (segment && segment.segmentType === msfsSdk.FlightPlanSegmentType.Enroute) {
                    return segment;
                }
            }
            return undefined;
        }
        /**
         * Gets the last enroute segment from a flight plan.
         * @param plan A flight plan.
         * @returns The last enroute segment in the specified flight plan, or `undefined` if one does not exist.
         */
        static getLastEnrouteSegment(plan) {
            const segmentCount = plan.segmentCount;
            for (let i = segmentCount - 1; i >= 0; i--) {
                const segment = plan.tryGetSegment(i);
                if (segment && segment.segmentType === msfsSdk.FlightPlanSegmentType.Enroute) {
                    return segment;
                }
            }
            return undefined;
        }
        /**
         * Gets the arrival segment from a flight plan.
         * @param plan A flight plan.
         * @returns The arrival segment in the specified flight plan, or `undefined` if one does not exist.
         */
        static getArrivalSegment(plan) {
            // There should only be one arrival segment
            for (const segment of plan.segmentsOfType(msfsSdk.FlightPlanSegmentType.Arrival)) {
                return segment;
            }
            return undefined;
        }
        /**
         * Gets the approach segment from a flight plan.
         * @param plan A flight plan.
         * @returns The approach segment in the specified flight plan, or `undefined` if one does not exist.
         */
        static getApproachSegment(plan) {
            // There should only be one approach segment
            for (const segment of plan.segmentsOfType(msfsSdk.FlightPlanSegmentType.Approach)) {
                return segment;
            }
            return undefined;
        }
        /**
         * Gets the destination segment from a flight plan.
         * @param plan A flight plan.
         * @returns The destination segment in the specified flight plan, or `undefined` if one does not exist.
         */
        static getDestinationSegment(plan) {
            // There should only be one destination segment
            for (const segment of plan.segmentsOfType(msfsSdk.FlightPlanSegmentType.Destination)) {
                return segment;
            }
            return undefined;
        }
        /**
         * Builds a flight plan leg to a runway fix.
         * @param airport The runway's parent airport or its ICAO.
         * @param runway The runway associated with the runway fix.
         * @param isInitialFix Whether to create the flight plan leg as an initial fix (IF) leg instead of a track-to-fix
         * (TF) leg.
         * @returns A flight plan leg to the specified runway fix.
         */
        static buildRunwayLeg(airport, runway, isInitialFix) {
            const leg = msfsSdk.FlightPlan.createLeg({
                lat: runway.latitude,
                lon: runway.longitude,
                type: isInitialFix ? msfsSdk.LegType.IF : msfsSdk.LegType.TF,
                fixIcao: msfsSdk.RunwayUtils.getRunwayFacilityIcao(airport, runway),
                altitude1: runway.elevation
            });
            return leg;
        }
        /**
         * Utility method to return a one-way runway leg from an approach runway leg definition
         * @param airport is the facility associated with the arrival
         * @param runwayIcao is the icao string for the runway waypoint in the final legs
         * @returns a leg object for the runway
         */
        static buildRunwayLegForApproach(airport, runwayIcao) {
            for (let i = 0; i < airport.runways.length; i++) {
                const match = msfsSdk.RunwayUtils.getOneWayRunways(airport.runways[i], i).find((r) => {
                    return (r.designation == msfsSdk.ICAO.getIdent(runwayIcao));
                });
                if (match) {
                    const leg = msfsSdk.FlightPlan.createLeg({
                        lat: match.latitude,
                        lon: match.longitude,
                        type: msfsSdk.LegType.TF,
                        fixIcao: runwayIcao
                    });
                    return leg;
                }
            }
            return undefined;
        }
        /**
         * Utility method to return a visual approach for a runway.
         * @param airport is the airport facility for the visual approach.
         * @param runway is the runway to build the visual approach for.
         * @param finalLegDistance is the distance from the runway to place the faf leg in NM.
         * @param initialLegDistance is the distance from the final leg to place the iaf leg in NM.
         * @param name is the optional name for the approach.
         * @param finalLegIdent is the optional name for the faf leg.
         * @param initialLegIdent is the optional name for the iaf leg.
         * @returns an approach procedure.
         */
        static buildVisualApproach(airport, runway, finalLegDistance, initialLegDistance, name, finalLegIdent, initialLegIdent) {
            const runwayVec = msfsSdk.GeoPoint.sphericalToCartesian(runway.latitude, runway.longitude, FmsUtils.vec3Cache[0]);
            const approachPath = FmsUtils.geoCircleCache[0].setAsGreatCircle(runwayVec, runway.course);
            const iafLatLon = approachPath.offsetDistanceAlong(runwayVec, msfsSdk.UnitType.NMILE.convertTo(-(initialLegDistance + finalLegDistance), msfsSdk.UnitType.GA_RADIAN), FmsUtils.geoPointCache[0]);
            const runwayCode = msfsSdk.RunwayUtils.getRunwayCode(runway.direction);
            const runwayLetter = msfsSdk.RunwayUtils.getDesignatorLetter(runway.runwayDesignator).padStart(1, ' ');
            initialLegIdent !== null && initialLegIdent !== void 0 ? initialLegIdent : (initialLegIdent = 'STRGHT');
            const iafLeg = msfsSdk.FlightPlan.createLeg({
                type: msfsSdk.LegType.IF,
                fixIcao: `S${msfsSdk.ICAO.getIdent(airport.icao).padStart(4, ' ')}${runwayCode}${runwayLetter}${initialLegIdent}`,
                lat: iafLatLon.lat,
                lon: iafLatLon.lon,
            });
            const fafLatLon = approachPath.offsetDistanceAlong(runwayVec, msfsSdk.UnitType.NMILE.convertTo(-finalLegDistance, msfsSdk.UnitType.GA_RADIAN), FmsUtils.geoPointCache[0]);
            finalLegIdent !== null && finalLegIdent !== void 0 ? finalLegIdent : (finalLegIdent = ' FINAL');
            const fafLeg = msfsSdk.FlightPlan.createLeg({
                type: msfsSdk.LegType.CF,
                fixIcao: `S${msfsSdk.ICAO.getIdent(airport.icao).padStart(4, ' ')}${runwayCode}${runwayLetter}${finalLegIdent}`,
                course: msfsSdk.MagVar.trueToMagnetic(approachPath.bearingAt(fafLatLon), fafLatLon),
                fixTypeFlags: msfsSdk.FixTypeFlags.FAF,
                lat: fafLatLon.lat,
                lon: fafLatLon.lon,
                altDesc: msfsSdk.AltitudeRestrictionType.AtOrAbove,
                // We want to be slightly below the glidepath at the faf so that if the plane is following VNAV, it will be able
                // to capture the GP from below.
                altitude1: runway.elevation + 10 + msfsSdk.VNavUtils.altitudeForDistance(3, msfsSdk.UnitType.NMILE.convertTo(finalLegDistance, msfsSdk.UnitType.METER)),
            });
            const runwayLeg = FmsUtils.buildRunwayLeg(airport, runway, false);
            runwayLeg.altitude1 += 15; //Runway leg altitude should be 50 feet above threshold
            runwayLeg.fixTypeFlags = msfsSdk.FixTypeFlags.MAP;
            const finalLegs = [];
            finalLegs.push(iafLeg);
            finalLegs.push(fafLeg);
            finalLegs.push(runwayLeg);
            const missedLegLatLon = approachPath.offsetDistanceAlong(runwayVec, msfsSdk.UnitType.NMILE.convertTo(5, msfsSdk.UnitType.GA_RADIAN), FmsUtils.geoPointCache[0]);
            const missedLeg = msfsSdk.FlightPlan.createLeg({
                type: msfsSdk.LegType.TF,
                fixIcao: `S${msfsSdk.ICAO.getIdent(airport.icao).padStart(4, ' ')}${runwayCode}${runwayLetter}MANSEQ`,
                lat: missedLegLatLon.lat,
                lon: missedLegLatLon.lon,
            });
            const missedLegs = [];
            missedLegs.push(missedLeg);
            const proc = {
                name: name !== null && name !== void 0 ? name : `Visual RW${runway.designation}`,
                runway: msfsSdk.RunwayUtils.getRunwayNameString(runway.direction, runway.runwayDesignator, false),
                icaos: [],
                transitions: [{ name: 'STRAIGHT', legs: [] }],
                finalLegs: finalLegs,
                missedLegs: missedLegs,
                approachType: msfsSdk.AdditionalApproachType.APPROACH_TYPE_VISUAL,
                approachSuffix: '',
                runwayDesignator: runway.runwayDesignator,
                runwayNumber: runway.direction,
                rnavTypeFlags: msfsSdk.RnavTypeFlags.None
            };
            return proc;
        }
        /**
         * Builds an empty approach procedure object for a visual approach. The empty object contains all descriptive data
         * for the approach but lacks flight plan leg information for the approach.
         * @param runway The runway to which the approach belongs.
         * @returns An empty approach procedure object for the specified approach.
         */
        static buildEmptyVisualApproach(runway) {
            return {
                name: `VISUAL RW${runway.designation}`,
                runway: msfsSdk.RunwayUtils.getRunwayNameString(runway.direction, runway.runwayDesignator, false),
                icaos: [],
                transitions: [{ name: 'STRAIGHT', legs: [] }],
                finalLegs: [],
                missedLegs: [],
                approachType: msfsSdk.AdditionalApproachType.APPROACH_TYPE_VISUAL,
                approachSuffix: '',
                runwayDesignator: runway.runwayDesignator,
                runwayNumber: runway.direction,
                rnavTypeFlags: msfsSdk.RnavTypeFlags.None
            };
        }
        /**
         * Creates a VFR approach object based on a published approach.
         * @param airport The airport facility containing the published approach on which the VFR approach is based.
         * @param approachIndex The index of the published approach on which the VFR approach is based.
         * @returns A new VFR approach object based on the specified published approach, or `undefined` if a VFR approach
         * could not be generated.
         */
        static buildVfrApproach(airport, approachIndex) {
            const approach = airport.approaches[approachIndex];
            if (!approach) {
                return undefined;
            }
            let didFindFaf = false;
            const publishedFinalLegs = approach.finalLegs;
            const finalLegs = [];
            for (let i = 0; i < publishedFinalLegs.length; i++) {
                // VFR approaches begin at the faf, so skip all legs until we find the faf.
                if (!didFindFaf && msfsSdk.BitFlags.isAll(publishedFinalLegs[i].fixTypeFlags, msfsSdk.FixTypeFlags.FAF)) {
                    didFindFaf = true;
                }
                if (!didFindFaf) {
                    continue;
                }
                const publishedLeg = publishedFinalLegs[i];
                if (msfsSdk.BitFlags.isAll(publishedLeg.fixTypeFlags, msfsSdk.FixTypeFlags.FAF)) {
                    switch (publishedLeg.type) {
                        case msfsSdk.LegType.IF:
                        case msfsSdk.LegType.TF:
                        case msfsSdk.LegType.CF:
                        case msfsSdk.LegType.DF:
                        case msfsSdk.LegType.AF:
                        case msfsSdk.LegType.RF: {
                            const insertLeg = msfsSdk.FlightPlan.createLeg(publishedLeg);
                            insertLeg.type = msfsSdk.LegType.IF;
                            finalLegs.push(insertLeg);
                            break;
                        }
                        default:
                            // If we can't convert the faf to an IF leg, then bail immediately since a non-VTF VFR approach must
                            // begin with an IF leg at the faf.
                            return undefined;
                    }
                }
                else {
                    finalLegs.push(msfsSdk.FlightPlan.createLeg(publishedLeg));
                }
                // VFR approaches end at the map, so skip all legs after the map.
                if (msfsSdk.BitFlags.isAll(publishedLeg.fixTypeFlags, msfsSdk.FixTypeFlags.MAP)) {
                    break;
                }
            }
            // VFR approaches must contain at least two legs: the faf and the map.
            if (finalLegs.length < 2) {
                return undefined;
            }
            // Ensure the last leg has the map flag.
            const lastLeg = finalLegs[finalLegs.length - 1];
            lastLeg.fixTypeFlags |= msfsSdk.FixTypeFlags.MAP;
            const proc = {
                name: approach.name,
                runway: approach.runway,
                icaos: [],
                transitions: [],
                finalLegs,
                missedLegs: [],
                approachType: exports.GarminAdditionalApproachType.APPROACH_TYPE_VFR,
                approachSuffix: approach.approachSuffix,
                runwayDesignator: approach.runwayDesignator,
                runwayNumber: approach.runwayNumber,
                rnavTypeFlags: msfsSdk.RnavTypeFlags.None,
                parentApproachInfo: {
                    approachType: approach.approachType,
                    rnavTypeFlags: approach.rnavTypeFlags
                }
            };
            return proc;
        }
        /**
         * Gets the best approach type available at an airport.
         * @param airport An airport facility.
         * @param includeVisual Whether to include visual approaches. Defaults to `false`.
         * @param includeRnpAr Whether to include RNP AR approaches. Defaults to `false`.
         * @returns The best approach type available at the specified airport.
         */
        static getBestApproachType(airport, includeVisual = false, includeRnpAr = false) {
            let best = (includeVisual && airport.runways.length > 0) ? msfsSdk.AdditionalApproachType.APPROACH_TYPE_VISUAL : ApproachType.APPROACH_TYPE_UNKNOWN;
            for (let i = 0; i < airport.approaches.length; i++) {
                const approach = airport.approaches[i];
                const type = approach.approachType;
                if ((includeRnpAr || !FmsUtils.isApproachRnpAr(approach)) && FmsUtils.APPROACH_TYPE_QUALITY[type] > FmsUtils.APPROACH_TYPE_QUALITY[best]) {
                    best = type;
                }
            }
            return best;
        }
        /**
         * Utility method to check whether an approach is authorized for GPS guidance.
         * @param approach The approach procedure
         * @returns True if GPS guidance is authorized, false otherwise.
         */
        static isGpsApproach(approach) {
            switch (approach.approachType) {
                case ApproachType.APPROACH_TYPE_GPS:
                case ApproachType.APPROACH_TYPE_RNAV:
                    return true;
            }
            return false;
        }
        /**
         * Utility method to check for an approach with a a tunable localizer.
         * @param approach The approach procedure
         * @returns True if a localizer needs to be tuned, otherwise false.
         */
        static isLocalizerApproach(approach) {
            switch (approach.approachType) {
                case ApproachType.APPROACH_TYPE_ILS:
                case ApproachType.APPROACH_TYPE_LDA:
                case ApproachType.APPROACH_TYPE_LOCALIZER:
                case ApproachType.APPROACH_TYPE_LOCALIZER_BACK_COURSE:
                case ApproachType.APPROACH_TYPE_SDF:
                    return true;
            }
            return false;
        }
        /**
         * Gets an approach procedure from a flight plan. If the flight plan has an visual approach loaded, an empty
         * procedure object, containing descriptive data for the approach but lacking flight plan leg information, will be
         * returned.
         * @param plan A flight plan.
         * @param destination The destination airport of the flight plan.
         * @returns The approach procedure from the flight plan, or undefined if the plan has no approach.
         */
        static getApproachFromPlan(plan, destination) {
            if (destination.approaches[plan.procedureDetails.approachIndex]) {
                return destination.approaches[plan.procedureDetails.approachIndex];
            }
            const visualApproachData = plan.getUserData(exports.FmsFplUserDataKey.VisualApproach);
            if (visualApproachData && plan.destinationAirport) {
                const runway = msfsSdk.RunwayUtils.matchOneWayRunwayFromDesignation(destination, visualApproachData.runwayDesignation);
                if (runway) {
                    return FmsUtils.buildEmptyVisualApproach(runway);
                }
            }
            const vfrApproachData = plan.getUserData(exports.FmsFplUserDataKey.VfrApproach);
            if (vfrApproachData && plan.destinationAirport) {
                return FmsUtils.buildVfrApproach(destination, vfrApproachData.approachIndex);
            }
            return undefined;
        }
        /**
         * Checks whether a flight plan has an approach loaded.
         * @param plan A flight plan.
         * @returns Whether the flight plan has an approach loaded.
         */
        static isApproachLoaded(plan) {
            return FmsUtils.isPublishedApproachLoaded(plan)
                || FmsUtils.isVisualApproachLoaded(plan)
                || FmsUtils.isVfrApproachLoaded(plan);
        }
        /**
         * Checks whether a flight plan has an approach loaded.
         * @param plan A flight plan.
         * @returns Whether the flight plan has an approach loaded.
         */
        static isPublishedApproachLoaded(plan) {
            return plan.procedureDetails.approachIndex >= 0;
        }
        /**
         * Checks whether a flight plan has an approach loaded.
         * @param plan A flight plan.
         * @returns Whether the flight plan has an approach loaded.
         */
        static isVisualApproachLoaded(plan) {
            return plan.destinationAirport !== undefined
                && plan.getUserData(exports.FmsFplUserDataKey.VisualApproach) !== undefined;
        }
        /**
         * Checks whether a flight plan has an approach loaded.
         * @param plan A flight plan.
         * @returns Whether the flight plan has an approach loaded.
         */
        static isVfrApproachLoaded(plan) {
            return plan.destinationAirport !== undefined
                && plan.getUserData(exports.FmsFplUserDataKey.VfrApproach) !== undefined;
        }
        /**
         * Gets the final approach fix leg of a flight plan.
         * @param plan A flight plan.
         * @returns The final approach fix leg of a flight plan, or `undefined` if one could not be found.
         */
        static getApproachFafLeg(plan) {
            var _a;
            if (!FmsUtils.isApproachLoaded(plan)) {
                return undefined;
            }
            return (_a = FmsUtils.getApproachSegment(plan)) === null || _a === void 0 ? void 0 : _a.legs.find(leg => msfsSdk.BitFlags.isAll(leg.leg.fixTypeFlags, msfsSdk.FixTypeFlags.FAF) && !msfsSdk.BitFlags.isAny(leg.flags, msfsSdk.LegDefinitionFlags.DirectTo));
        }
        /**
         * Checks whether a plan has a vectors-to-final approach loaded.
         * @param plan A flight plan.
         * @returns Whether the flight plan has a vectors-to-final approach loaded.
         */
        static isVtfApproachLoaded(plan) {
            var _a, _b, _c, _d;
            return (plan.procedureDetails.approachIndex >= 0 && plan.procedureDetails.approachTransitionIndex === -1)
                || ((_b = (_a = plan.getUserData(exports.FmsFplUserDataKey.VisualApproach)) === null || _a === void 0 ? void 0 : _a.isVtf) !== null && _b !== void 0 ? _b : false)
                || ((_d = (_c = plan.getUserData(exports.FmsFplUserDataKey.VfrApproach)) === null || _c === void 0 ? void 0 : _c.isVtf) !== null && _d !== void 0 ? _d : false);
        }
        /**
         * Gets the vectors-to-final faf leg of a flight plan.
         * @param plan A flight plan.
         * @returns The vectors-to-final faf leg of the flight plan, or `undefined` if one could not be found.
         */
        static getApproachVtfLeg(plan) {
            var _a;
            if (!FmsUtils.isVtfApproachLoaded(plan)) {
                return undefined;
            }
            return (_a = FmsUtils.getApproachSegment(plan)) === null || _a === void 0 ? void 0 : _a.legs.find(leg => msfsSdk.BitFlags.isAll(leg.flags, msfsSdk.LegDefinitionFlags.VectorsToFinalFaf));
        }
        /**
         * Gets the name of a departure procedure as a string.
         * @param airport The airport to which the departure belongs.
         * @param departure A departure procedure definition.
         * @param transitionIndex The index of the departure enroute transition.
         * @param runway The runway of the departure, if any.
         * @returns The name of the departure procedure.
         */
        static getDepartureNameAsString(airport, departure, transitionIndex, runway) {
            let name = `${msfsSdk.ICAO.getIdent(airport.icao)}–`;
            if (runway) {
                name += `RW${runway.designation}.`;
            }
            const transition = departure.enRouteTransitions[transitionIndex];
            if (transition !== undefined && transitionIndex > -1 && transition.legs.length > 0) {
                name += `${departure.name}.${msfsSdk.ICAO.getIdent(transition.legs[transition.legs.length - 1].fixIcao)}`;
            }
            else if (departure.commonLegs.length > 0) {
                name += `${departure.name}.${msfsSdk.ICAO.getIdent(departure.commonLegs[departure.commonLegs.length - 1].fixIcao)}`;
            }
            else {
                name += `${departure.name}`;
            }
            return name;
        }
        /**
         * Gets the name of a arrival procedure as a string.
         * @param airport The airport to which the departure belongs.
         * @param arrival An arrival procedure definition.
         * @param transitionIndex The index of the arrival enroute transition.
         * @param runway The runway of the arrival, if any.
         * @returns The name of the arrival procedure.
         */
        static getArrivalNameAsString(airport, arrival, transitionIndex, runway) {
            let name = `${msfsSdk.ICAO.getIdent(airport.icao)}–`;
            const transition = arrival.enRouteTransitions[transitionIndex];
            if (transition !== undefined && transitionIndex > -1 && transition.legs.length > 0) {
                name += `${msfsSdk.ICAO.getIdent(transition.legs[0].fixIcao)}.${arrival === null || arrival === void 0 ? void 0 : arrival.name}`;
            }
            else if (arrival.commonLegs.length > 0) {
                name += `${msfsSdk.ICAO.getIdent(arrival.commonLegs[0].fixIcao)}.${arrival === null || arrival === void 0 ? void 0 : arrival.name}`;
            }
            else {
                name += `${arrival === null || arrival === void 0 ? void 0 : arrival.name}`;
            }
            if (runway) {
                name += `.RW${runway.designation}`;
            }
            return name;
        }
        /**
         * Gets the transition name and creates a default transition when the procedure has no transitions.
         * @param procedure is the departure procedure.
         * @param transitionIndex is the index of the enroute transition in the procedure.
         * @param rwyTransitionIndex is the index of the runway transition in the procedure.
         * @returns The transition name string.
         */
        static getDepartureEnrouteTransitionName(procedure, transitionIndex, rwyTransitionIndex) {
            if (transitionIndex == -1) {
                if (procedure.commonLegs.length > 0) {
                    const legsLen = procedure.commonLegs.length;
                    /** For Departures, default transition name should be last leg icao */
                    return msfsSdk.ICAO.getIdent(procedure.commonLegs[legsLen - 1].fixIcao);
                }
                else if (rwyTransitionIndex !== -1) {
                    const rwyTrans = procedure.runwayTransitions[rwyTransitionIndex];
                    const legsLen = rwyTrans.legs.length;
                    /** For Departures, default transition name should be last leg icao */
                    return msfsSdk.ICAO.getIdent(rwyTrans.legs[legsLen - 1].fixIcao);
                }
                else {
                    return '';
                }
            }
            else {
                const enrTrans = procedure.enRouteTransitions[transitionIndex];
                if (enrTrans.name.length > 0) {
                    return enrTrans.name;
                }
                else {
                    /** For Departures, default transition name should be last leg icao */
                    const legsLen = enrTrans.legs.length;
                    return msfsSdk.ICAO.getIdent(enrTrans.legs[legsLen - 1].fixIcao);
                }
            }
        }
        /**
         * Gets the transition name and creates a default transition when the procedure has no transitions.
         * @param procedure is the arrival procedure.
         * @param transitionIndex is the index of the enroute transition in the procedure.
         * @param rwyTransitionIndex is the index of the runway transition in the procedure.
         * @returns The transition name string.
         */
        static getArrivalEnrouteTransitionName(procedure, transitionIndex, rwyTransitionIndex) {
            if (transitionIndex == -1) {
                if (procedure.commonLegs.length > 0) {
                    /** For Arrivals, default transition name should be first leg icao */
                    return msfsSdk.ICAO.getIdent(procedure.commonLegs[0].fixIcao);
                }
                else if (rwyTransitionIndex !== -1) {
                    const rwyTrans = procedure.runwayTransitions[rwyTransitionIndex];
                    /** For Arrivals, default transition name should be first leg icao */
                    return msfsSdk.ICAO.getIdent(rwyTrans.legs[0].fixIcao);
                }
                else {
                    return '';
                }
            }
            else {
                const enrTrans = procedure.enRouteTransitions[transitionIndex];
                if (enrTrans.name.length > 0) {
                    return enrTrans.name;
                }
                else {
                    /** For Arrivals, default transition name should be first leg icao */
                    return msfsSdk.ICAO.getIdent(enrTrans.legs[0].fixIcao);
                }
            }
        }
        /**
         * Utility method to analyze an approach for its name components and
         * pack them into a custom type.
         * @param proc The approach procedure.
         * @returns The name as an ApproachNameParts
         */
        static getApproachNameAsParts(proc) {
            let type;
            let subtype;
            let rnavType;
            let approachType;
            let rnavTypeFlags;
            if (proc.approachType === exports.GarminAdditionalApproachType.APPROACH_TYPE_VFR) {
                approachType = proc.parentApproachInfo.approachType;
                rnavTypeFlags = proc.parentApproachInfo.rnavTypeFlags;
            }
            else {
                approachType = proc.approachType;
                rnavTypeFlags = proc.rnavTypeFlags;
            }
            switch (approachType) {
                case ApproachType.APPROACH_TYPE_GPS:
                    type = 'GPS';
                    break;
                case ApproachType.APPROACH_TYPE_VOR:
                    type = 'VOR';
                    break;
                case ApproachType.APPROACH_TYPE_NDB:
                    type = 'NDB';
                    break;
                case ApproachType.APPROACH_TYPE_ILS:
                    type = 'ILS';
                    break;
                case ApproachType.APPROACH_TYPE_LOCALIZER:
                    type = 'LOC';
                    break;
                case ApproachType.APPROACH_TYPE_SDF:
                    type = 'SDF';
                    break;
                case ApproachType.APPROACH_TYPE_LDA:
                    type = 'LDA';
                    break;
                case ApproachType.APPROACH_TYPE_VORDME:
                    type = 'VOR/DME';
                    break;
                case ApproachType.APPROACH_TYPE_NDBDME:
                    type = 'NDB/DME';
                    break;
                case ApproachType.APPROACH_TYPE_RNAV:
                    type = 'RNAV';
                    break;
                case ApproachType.APPROACH_TYPE_LOCALIZER_BACK_COURSE:
                    type = 'LOC BC';
                    break;
                case msfsSdk.AdditionalApproachType.APPROACH_TYPE_VISUAL:
                    type = 'VISUAL';
                    break;
                default:
                    type = '???';
                    break;
            }
            const approachIsCircling = !proc.runway ? true : false;
            if (approachType === ApproachType.APPROACH_TYPE_RNAV) {
                subtype = 'GPS';
                switch (FmsUtils.getBestRnavType(rnavTypeFlags)) {
                    case msfsSdk.RnavTypeFlags.LNAV:
                        rnavType = approachIsCircling ? 'LNAV' : 'LNAV+V';
                        break;
                    case msfsSdk.RnavTypeFlags.LP:
                        rnavType = approachIsCircling ? 'LP' : 'LP+V';
                        break;
                    case msfsSdk.RnavTypeFlags.LNAVVNAV:
                        rnavType = 'LNAV/VNAV';
                        break;
                    case msfsSdk.RnavTypeFlags.LPV:
                        rnavType = 'LPV';
                        break;
                    case msfsSdk.RnavTypeFlags.None: // If there are no defined RNAV minima, assume it is an RNP (AR) approach if it is not circling.
                        if (!approachIsCircling) {
                            subtype = 'RNP';
                        }
                        break;
                }
            }
            return {
                type: type,
                subtype: subtype,
                suffix: proc.approachSuffix ? proc.approachSuffix : undefined,
                runway: proc.runwayNumber === 0 ? undefined : msfsSdk.RunwayUtils.getRunwayNameString(proc.runwayNumber, proc.runwayDesignator, true),
                flags: rnavType
            };
        }
        /**
         * Utility method that takes an approach and returns its name as a flat
         * string suitable for use in embedded text content.
         * @param approach The approach as an ApproaceProcedure
         * @returns The formatted name as a string.
         */
        static getApproachNameAsString(approach) {
            const parts = FmsUtils.getApproachNameAsParts(approach);
            let name = parts.type;
            parts.subtype && (name += `${parts.subtype}`);
            parts.suffix && (name += `${parts.runway ? ' ' : '–'}${parts.suffix}`);
            parts.runway && (name += ` ${parts.runway}`);
            parts.flags && (name += ` ${parts.flags}`);
            return name;
        }
        /**
         * Checks whether an approach has a primary NAV frequency based on its type. Only approaches of the following types
         * have primary NAV frequencies: ILS, LOC (BC), LDA, SDF, VOR(DME).
         * @param approach The approach to check.
         * @returns Whether the specified approach has a primary NAV frequency based on its type.
         */
        static approachHasNavFrequency(approach) {
            switch (approach.approachType) {
                case ApproachType.APPROACH_TYPE_ILS:
                case ApproachType.APPROACH_TYPE_LOCALIZER:
                case ApproachType.APPROACH_TYPE_LOCALIZER_BACK_COURSE:
                case ApproachType.APPROACH_TYPE_LDA:
                case ApproachType.APPROACH_TYPE_SDF:
                case ApproachType.APPROACH_TYPE_VOR:
                case ApproachType.APPROACH_TYPE_VORDME:
                    return true;
                default:
                    return false;
            }
        }
        /**
         * Gets an array of approach list items from an airport.
         * @param airport An airport.
         * @param includeVisual Whether to include visual approaches. Defaults to `true`.
         * @returns An array of approach list items for the specified airport.
         */
        static getApproaches(airport, includeVisual = true) {
            if (airport === undefined) {
                return [];
            }
            const ilsFound = new Set();
            for (const approach of airport.approaches) {
                if (approach.approachType == ApproachType.APPROACH_TYPE_ILS) {
                    ilsFound.add(approach.runway);
                }
            }
            const approaches = [];
            airport.approaches.forEach((approach, index) => {
                if (approach.approachType !== ApproachType.APPROACH_TYPE_LOCALIZER || !ilsFound.has(approach.runway)) {
                    approaches.push({
                        approach,
                        index,
                        isVisualApproach: false
                    });
                }
            });
            if (includeVisual) {
                FmsUtils.getVisualApproaches(airport).forEach(va => {
                    approaches.push({
                        approach: va,
                        index: -1,
                        isVisualApproach: true
                    });
                });
            }
            return approaches;
        }
        /**
         * Gets an array of approach list items from an airport.
         * @param airport An airport.
         * @returns An array of approach list items for the specified airport.
         */
        static getVfrApproaches(airport) {
            if (airport === undefined) {
                return [];
            }
            const approaches = [];
            for (let index = 0; index < airport.approaches.length; index++) {
                const approach = FmsUtils.buildVfrApproach(airport, index);
                if (approach) {
                    approaches.push({
                        approach,
                        index
                    });
                }
            }
            return approaches;
        }
        /**
         * Gets the visual approaches for the facility.
         * @param facility is the facility.
         * @returns The Approach Procedures.
         */
        static getVisualApproaches(facility) {
            const runways = msfsSdk.RunwayUtils.getOneWayRunwaysFromAirport(facility);
            const approaches = [];
            runways.forEach(r => {
                approaches.push({
                    name: `VISUAL ${r.designation}`,
                    runway: r.designation,
                    icaos: [],
                    transitions: [{ name: 'STRAIGHT', legs: [] }],
                    finalLegs: [],
                    missedLegs: [],
                    approachType: msfsSdk.AdditionalApproachType.APPROACH_TYPE_VISUAL,
                    approachSuffix: '',
                    runwayDesignator: r.runwayDesignator,
                    runwayNumber: r.direction,
                    rnavTypeFlags: msfsSdk.RnavTypeFlags.None
                });
            });
            return approaches;
        }
        /**
         * Creates an ApproachListItem from an ApproachProcedure and the approach index.
         * @param approach The approach procedure.
         * @param index The approach index.
         * @returns The created ApproachListItem.
         */
        static createApproachListItem(approach, index) {
            if (approach.approachType === msfsSdk.AdditionalApproachType.APPROACH_TYPE_VISUAL) {
                return {
                    approach,
                    index: -1,
                    isVisualApproach: true,
                };
            }
            else {
                return {
                    approach,
                    index: index,
                    isVisualApproach: false,
                };
            }
        }
        /**
         * Gets the transitions for the approach, adding suffixes, vectors transtion, and default approach if needed.
         * @param approachItem The approach procedure to get the transitions for.
         * @returns The transitions for the approach.
         */
        static getApproachTransitions(approachItem) {
            const approach = approachItem === null || approachItem === void 0 ? void 0 : approachItem.approach;
            const transitions = [];
            if (approach) {
                for (let i = 0; i < approach.transitions.length; i++) {
                    transitions.push({
                        name: this.getApproachTransitionName(approach, i),
                        transitionIndex: i
                    });
                }
                transitions.unshift({ name: 'VECTORS', transitionIndex: -1 });
                // If approach has no transitions in the nav data, create a default one beginning at the start of finalLegs
                if (!approachItem.isVisualApproach && approach.transitions.length === 0 && approach.finalLegs.length > 0) {
                    transitions.push({
                        name: msfsSdk.ICAO.getIdent(approach.finalLegs[0].fixIcao),
                        transitionIndex: 0
                    });
                }
            }
            return transitions;
        }
        /**
         * Creates an TransitionListItem from an ApproachProcedure and the transition index.
         * @param approach The approach procedure.
         * @param transitionIndex The approach transition index.
         * @returns The created TransitionListItem.
         */
        static createApproachTransitionListItem(approach, transitionIndex) {
            return {
                name: this.getApproachTransitionName(approach, transitionIndex),
                transitionIndex,
            };
        }
        /**
         * Creates an TransitionListItem from an ApproachProcedure and the transition index.
         * @param approach The approach procedure.
         * @param transitionIndex The approach transition index.
         * @returns The created TransitionListItem.
         */
        static getApproachTransitionName(approach, transitionIndex) {
            var _a, _b;
            if (transitionIndex === -1) {
                return 'VECTORS';
            }
            const transition = approach.transitions[transitionIndex];
            if (!transition) {
                return msfsSdk.ICAO.getIdent(approach.finalLegs[0].fixIcao);
            }
            const firstLeg = transition.legs[0];
            const name = (_a = transition.name) !== null && _a !== void 0 ? _a : (firstLeg ? msfsSdk.ICAO.getIdent(firstLeg.fixIcao) : '');
            const suffix = msfsSdk.BitFlags.isAll((_b = firstLeg === null || firstLeg === void 0 ? void 0 : firstLeg.fixTypeFlags) !== null && _b !== void 0 ? _b : 0, msfsSdk.FixTypeFlags.IAF) ? ' iaf' : '';
            return name + suffix;
        }
        /**
         * Gets the global leg index from a segment and segment leg index, whether or not the leg exists.
         * @param lateralPlan The Lateral Flight Plan.
         * @param segmentIndex The Segment Index.
         * @param segmentLegIndex The Segment Leg Index.
         * @returns The global leg index.
         */
        static getGlobalLegIndex(lateralPlan, segmentIndex, segmentLegIndex) {
            if (segmentIndex < lateralPlan.segmentCount) {
                const segment = lateralPlan.getSegment(segmentIndex);
                return segment.offset + segmentLegIndex;
            }
            return -1;
        }
        /**
         * Gets the indexes for a leg.
         * @param lateralPlan The Lateral Flight Plan.
         * @param leg The leg definition.
         * @returns The leg indexes, or undefined if not found.
         */
        static getLegIndexes(lateralPlan, leg) {
            const globalLegIndex = lateralPlan.getLegIndexFromLeg(leg);
            if (globalLegIndex === -1) {
                return undefined;
            }
            const segmentIndex = lateralPlan.getSegmentIndex(globalLegIndex);
            const segmentLegIndex = lateralPlan.getSegmentLegIndex(globalLegIndex);
            return {
                globalLegIndex,
                segmentIndex,
                segmentLegIndex,
            };
        }
        /**
         * Gets the nominal leg from which a specified flight plan leg originates. The nominal from leg excludes any legs
         * which are part of a direct to or vectors-to-final sequence.
         * @param plan A flight plan.
         * @param segmentIndex The index of the segment containing the leg for which to get the from leg.
         * @param segmentLegIndex The index of the leg for which to get the from leg in its segment.
         * @returns The nominal leg from which the specified flight plan leg originates.
         */
        static getNominalFromLeg(plan, segmentIndex, segmentLegIndex) {
            let leg = plan.getPrevLeg(segmentIndex, segmentLegIndex);
            if (!leg) {
                return undefined;
            }
            for (leg of plan.legs(true, plan.getLegIndexFromLeg(leg))) {
                if (!msfsSdk.BitFlags.isAny(leg.flags, msfsSdk.LegDefinitionFlags.DirectTo | msfsSdk.LegDefinitionFlags.VectorsToFinal)) {
                    return leg;
                }
            }
            return undefined;
        }
        /**
         * Gets the global leg index of the nominal leg from which a specified flight plan leg originates. The nominal from
         * leg excludes any legs which are part of a direct to or vectors-to-final sequence.
         * @param plan A flight plan.
         * @param segmentIndex The index of the segment containing the leg for which to get the from leg.
         * @param segmentLegIndex The index of the leg for which to get the from leg in its segment.
         * @returns The nominal leg from which the specified flight plan leg originates.
         */
        static getNominalFromLegIndex(plan, segmentIndex, segmentLegIndex) {
            let leg = plan.getPrevLeg(segmentIndex, segmentLegIndex);
            if (!leg) {
                return -1;
            }
            let index = plan.getLegIndexFromLeg(leg);
            for (leg of plan.legs(true, index)) {
                if (!msfsSdk.BitFlags.isAny(leg.flags, msfsSdk.LegDefinitionFlags.DirectTo | msfsSdk.LegDefinitionFlags.VectorsToFinal)) {
                    return index;
                }
                index--;
            }
            return -1;
        }
        // eslint-disable-next-line jsdoc/require-jsdoc
        static getFromLegForArrowDisplay(plan, arg2, arg3) {
            var _a, _b;
            const globalLegIndex = arg3 === undefined ? arg2 : ((_b = (_a = plan.tryGetSegment(arg2)) === null || _a === void 0 ? void 0 : _a.offset) !== null && _b !== void 0 ? _b : -1 - arg3) + arg3;
            const toLeg = plan.tryGetLeg(globalLegIndex);
            if (!toLeg) {
                return undefined;
            }
            if (msfsSdk.BitFlags.isAll(toLeg.flags, msfsSdk.LegDefinitionFlags.VectorsToFinalFaf)) {
                return undefined;
            }
            let prevLeg = plan.tryGetLeg(globalLegIndex - 1);
            if (!prevLeg) {
                return undefined;
            }
            switch (toLeg.leg.type) {
                case msfsSdk.LegType.CA:
                case msfsSdk.LegType.VA:
                case msfsSdk.LegType.VM:
                case msfsSdk.LegType.VI:
                case msfsSdk.LegType.VD:
                case msfsSdk.LegType.VR:
                    return undefined;
                case msfsSdk.LegType.CF: {
                    const showDirectArrow = !!prevLeg && (msfsSdk.FlightPlanUtils.isDiscontinuityLeg(prevLeg.leg.type)
                        || prevLeg.leg.type === msfsSdk.LegType.FM
                        || prevLeg.leg.type === msfsSdk.LegType.VM);
                    if (showDirectArrow) {
                        return undefined;
                    }
                }
            }
            for (prevLeg of plan.legs(true, globalLegIndex - 1)) {
                if (!msfsSdk.BitFlags.isAny(prevLeg.flags, msfsSdk.LegDefinitionFlags.DirectTo | msfsSdk.LegDefinitionFlags.VectorsToFinal)) {
                    return prevLeg;
                }
            }
            return undefined;
        }
        /**
         * Gets the nominal desired track for a flight plan leg, as `[dtk, magVar]` where `dtk` is the true desired track and
         * `magVar` is the magnetic variation used to convert between true and magnetic desired tracks, both in degrees. If a
         * nominal desired track could not be obtained, then the value of `dtk` will be equal to `NaN`.
         * @param leg The leg for which to get the nominal desired track.
         * @param out The array to which to write the results.
         * @returns The nominal desired track for the specified flight plan leg, as `[dtk, magVar]` where `dtk` is the true
         * desired track and `magVar` is the magnetic variation used to convert between true and magnetic desired tracks,
         * both in degrees.
         */
        static getNominalLegDtk(leg, out) {
            var _a, _b;
            out[0] = NaN;
            out[1] = 0;
            const legCalc = leg.calculated;
            if (!legCalc) {
                return out;
            }
            // Fallback resolution paths are equivalent to DF legs.
            if (!legCalc.endsInFallback
                && msfsSdk.BitFlags.isAll((_b = (_a = legCalc.flightPath[0]) === null || _a === void 0 ? void 0 : _a.flags) !== null && _b !== void 0 ? _b : 0, msfsSdk.FlightPathVectorFlags.Fallback | msfsSdk.FlightPathVectorFlags.Direct)) {
                return FmsUtils.getNominalLegDtkForEndCourse(legCalc, out);
            }
            switch (leg.leg.type) {
                case msfsSdk.LegType.FA:
                case msfsSdk.LegType.CA:
                case msfsSdk.LegType.VA:
                case msfsSdk.LegType.FM:
                case msfsSdk.LegType.VM:
                case msfsSdk.LegType.DF:
                case msfsSdk.LegType.CD:
                case msfsSdk.LegType.VD:
                case msfsSdk.LegType.CR:
                case msfsSdk.LegType.VR:
                case msfsSdk.LegType.CI:
                case msfsSdk.LegType.VI:
                    return FmsUtils.getNominalLegDtkForEndCourse(legCalc, out);
                case msfsSdk.LegType.HM:
                case msfsSdk.LegType.HF:
                case msfsSdk.LegType.HA:
                    // The nominal DTK for hold legs is the inbound course.
                    if (legCalc.flightPath.length > 0) {
                        // The last base flight path vector for hold legs should always be the inbound leg.
                        const vector = legCalc.flightPath[legCalc.flightPath.length - 1];
                        out[0] = msfsSdk.FlightPathUtils.getVectorFinalCourse(vector);
                        out[1] = legCalc.courseMagVar;
                    }
                    break;
                default: {
                    // For all other leg types, the nominal DTK is the DTK at the beginning of the leg.
                    const vector = legCalc.flightPath[0];
                    if (vector) {
                        out[0] = msfsSdk.FlightPathUtils.getVectorInitialCourse(vector);
                        out[1] = legCalc.courseMagVar;
                    }
                }
            }
            return out;
        }
        /**
         * Gets the nominal desired track from a flight plan leg's last flight path vector, as `[dtk, magVar]` where `dtk` is
         * the true desired track and `magVar` is the magnetic variation used to convert between true and magnetic desired
         * tracks, both in degrees. If the last flight path vector is a great-circle vector, then the nominal desired track
         * is equal to the vector's initial course. Otherwise, the nominal desired track is equal to the vector's final
         * course. If no flight path vectors exist, then the output array is returned unchanged.
         * @param legCalc The calculations for the flight plan leg for which to get the desired track.
         * @param out The array to which to write the results.
         * @returns The nominal desired track from the specified flight plan leg's last flight path vector, as
         * `[dtk, magVar]` where `dtk` is the true desired track and `magVar` is the magnetic variation used to convert
         * between true and magnetic desired tracks, both in degrees.
         */
        static getNominalLegDtkForEndCourse(legCalc, out) {
            const vector = legCalc.flightPath[legCalc.flightPath.length - 1];
            if (!vector) {
                return out;
            }
            if (msfsSdk.FlightPathUtils.isVectorGreatCircle(vector)) {
                out[0] = msfsSdk.FlightPathUtils.getVectorInitialCourse(vector);
            }
            else {
                out[0] = msfsSdk.FlightPathUtils.getVectorFinalCourse(vector);
            }
            out[1] = legCalc.courseMagVar;
            return out;
        }
        /**
         * Reconciles a flight plan's Direct-To data with its internal leg structure. Scans the legs of the flight plan for
         * Direct-To legs and sets the segment index and segment leg index of the plan's Direct-To data to point to the leg
         * immediately preceding the first Direct-To leg found, or to -1 for both if the plan contains no Direct-To legs.
         * @param plan A flight plan.
         */
        static reconcileDirectToData(plan) {
            // Scan flight plan for DTO legs
            for (let i = 0; i < plan.segmentCount; i++) {
                const segment = plan.getSegment(i);
                for (let j = 0; j < segment.legs.length; j++) {
                    const leg = segment.legs[j];
                    if (msfsSdk.BitFlags.isAll(leg.flags, msfsSdk.LegDefinitionFlags.DirectTo)) {
                        plan.directToData.segmentIndex = i;
                        plan.directToData.segmentLegIndex = j - 1;
                        return;
                    }
                }
            }
            plan.directToData.segmentIndex = -1;
            plan.directToData.segmentLegIndex = -1;
        }
        /**
         * Gets the string for the leg fix type for use in a sequence list.
         * @param leg The leg definition.
         * @param allowHdg If false, will not return 'hdg'. Defaults to true.
         * @returns The left padded suffix string or empty string.
         */
        static getSequenceLegFixTypeSuffix(leg, allowHdg = true) {
            if (leg.leg.type === msfsSdk.LegType.VM && allowHdg === true) {
                return ' hdg';
            }
            if (msfsSdk.BitFlags.isAll(leg.leg.fixTypeFlags, msfsSdk.FixTypeFlags.FAF)) {
                return ' faf';
            }
            else if (msfsSdk.BitFlags.isAll(leg.leg.fixTypeFlags, msfsSdk.FixTypeFlags.IAF)) {
                return ' iaf';
            }
            else if (msfsSdk.BitFlags.isAll(leg.leg.fixTypeFlags, msfsSdk.FixTypeFlags.MAP)) {
                return ' map';
            }
            else if (msfsSdk.BitFlags.isAll(leg.leg.fixTypeFlags, msfsSdk.FixTypeFlags.MAHP)) {
                return ' mahp';
            }
            return '';
        }
        /**
         * Checks for a course reversal in the procedure.
         * @param legs The legs in the procedure.
         * @param ppos The current aircraft present position.
         * @returns true if there is an optional course reversal.
         */
        static checkForCourseReversal(legs, ppos) {
            if (legs && legs.length > 0) {
                const leg = legs[1];
                switch (leg.leg.type) {
                    case msfsSdk.LegType.HA:
                    case msfsSdk.LegType.HF:
                    case msfsSdk.LegType.HM: {
                        if (leg.calculated && leg.calculated.endLat && leg.calculated.endLon) {
                            if (Math.abs(msfsSdk.NavMath.diffAngle(msfsSdk.MagVar.trueToMagnetic(ppos.bearingTo(leg.calculated.endLat, leg.calculated.endLon), ppos.lat, ppos.lon), leg.leg.course)) > 90) {
                                return false;
                            }
                        }
                        return true;
                    }
                }
            }
            return false;
        }
        /**
         * Gets and returns the ICAO of first airport fix from the flight plan legs.
         * @param plan The flight plan to use.
         * @returns The ICAO of first airport fix from the flight plan legs.
         */
        static getFirstAirportFromPlan(plan) {
            return this.getAirportFromPlan(plan, false);
        }
        /**
         * Gets and returns the ICAO of last airport fix from the flight plan legs.
         * @param plan The flight plan to use.
         * @returns The ICAO of last airport fix from the flight plan legs.
         */
        static getLastAirportFromPlan(plan) {
            return this.getAirportFromPlan(plan, true);
        }
        /**
         * Gets and returns the ICAO of first or last airport fix from the flight plan legs.
         * @param plan The flight plan to use.
         * @param reverse Whether to get the first or last airport.
         * @returns The ICAO of last airport fix from the flight plan legs.
         */
        static getAirportFromPlan(plan, reverse) {
            for (const leg of plan.legs(reverse)) {
                if (msfsSdk.ICAO.isFacility(leg.leg.fixIcao) && msfsSdk.ICAO.getFacilityType(leg.leg.fixIcao) === msfsSdk.FacilityType.Airport) {
                    return leg.leg.fixIcao;
                }
            }
            return undefined;
        }
        /**
         * Determines if a flight plan leg's altitude constraint is considered to be edited. If the leg does not have a
         * designated altitude constraint, `false` will be returned.
         * @param leg A flight plan leg.
         * @param isAdvancedVNav Whether advanced VNAV is supported.
         * @returns Whether the specified flight plan leg's altitude constraint is considered to be edited.
         */
        static isLegAltitudeEdited(leg, isAdvancedVNav) {
            const publishedAltDesc = leg.leg.altDesc;
            const constraintAltDesc = leg.verticalData.altDesc;
            if (constraintAltDesc === msfsSdk.AltitudeRestrictionType.Unused) {
                return false;
            }
            const altitude1Feet = Math.round(msfsSdk.UnitType.METER.convertTo(leg.verticalData.altitude1, msfsSdk.UnitType.FOOT));
            const altitude2Feet = Math.round(msfsSdk.UnitType.METER.convertTo(leg.verticalData.altitude2, msfsSdk.UnitType.FOOT));
            const altitude1FeetPublished = Math.round(msfsSdk.UnitType.METER.convertTo(leg.leg.altitude1, msfsSdk.UnitType.FOOT));
            const altitude2FeetPublished = Math.round(msfsSdk.UnitType.METER.convertTo(leg.leg.altitude2, msfsSdk.UnitType.FOOT));
            if (isAdvancedVNav) {
                return constraintAltDesc !== publishedAltDesc
                    || altitude1Feet !== altitude1FeetPublished
                    || altitude2Feet !== altitude2FeetPublished;
            }
            else {
                if (publishedAltDesc === msfsSdk.AltitudeRestrictionType.Unused) {
                    return true;
                }
                // In simple mode, the auto-designated altitude is derived from altitude2 for between constraints and from
                // altitude1 for all other constraints.
                if (publishedAltDesc === msfsSdk.AltitudeRestrictionType.Between) {
                    return altitude1Feet !== altitude2FeetPublished;
                }
                else {
                    return altitude1Feet !== altitude1FeetPublished;
                }
            }
        }
        /**
         * Checks whether a flight plan leg's altitude constraint should be editable.
         * @param plan The flight plan containing the leg to evaluate.
         * @param leg The flight plan leg to evaluate.
         * @param isAdvancedVNav Whether this is for advanced vnav mode or not.
         * @returns whether a leg's altitude constraint should be editable.
         */
        static isAltitudeEditable(plan, leg, isAdvancedVNav) {
            if (!this.isAltitudeVisible(plan, leg, isAdvancedVNav)) {
                return false;
            }
            // TODO FAF is only editable if next leg is not the MAP, or if RNAV approach?
            // TODO If there are legs between FAF and MAP, the last one will not be editable?
            // TODO If not editable, it appears to not be designatable as well, meaning VNAV won't use it?
            // TODO I think we will just make FAF always editable, but anything between FAF and MAP should not be editable
            if (!isAdvancedVNav && msfsSdk.BitFlags.isAny(leg.leg.fixTypeFlags, msfsSdk.FixTypeFlags.MAHP)) {
                return false;
            }
            // In simple mode, no missed approach constraints are editable
            if (!isAdvancedVNav && msfsSdk.BitFlags.isAny(leg.flags, msfsSdk.LegDefinitionFlags.MissedApproach)) {
                return false;
            }
            if (isAdvancedVNav) {
                switch (leg.leg.type) {
                    case msfsSdk.LegType.CA:
                    case msfsSdk.LegType.FA:
                    case msfsSdk.LegType.VA:
                    case msfsSdk.LegType.HA:
                        return false;
                    default:
                        return true;
                }
            }
            else {
                switch (leg.leg.type) {
                    case msfsSdk.LegType.CA:
                    case msfsSdk.LegType.FA:
                    case msfsSdk.LegType.VA:
                    case msfsSdk.LegType.HA:
                    case msfsSdk.LegType.HF:
                    case msfsSdk.LegType.HM:
                    case msfsSdk.LegType.FM:
                    case msfsSdk.LegType.VM:
                    case msfsSdk.LegType.CI:
                    case msfsSdk.LegType.VI:
                    case msfsSdk.LegType.CR:
                    case msfsSdk.LegType.VR:
                        return false;
                    default:
                        return true;
                }
            }
        }
        /**
         * Checks whether a leg's altitude constraint should be visible.
         * @param plan The flight plan containing the leg to evaluate.
         * @param leg The flight plan leg to evaluate.
         * @param isAdvancedVNav Whether this is for advanced vnav mode or not.
         * @param isEditable Whether the constraint is editable, leave undefined if we don't know yet.
         * @returns whether a leg's altitude constraint should be visible.
         */
        static isAltitudeVisible(plan, leg, isAdvancedVNav, isEditable) {
            const segment = plan.getSegmentFromLeg(leg);
            if (!segment) {
                return false;
            }
            if (isEditable === false && leg.leg.altDesc === msfsSdk.AltitudeRestrictionType.Unused) {
                return false;
            }
            // Altitudes on discontniuity legs are never visible (these legs should never be displayed in the first place).
            if (msfsSdk.FlightPlanUtils.isDiscontinuityLeg(leg.leg.type)) {
                return false;
            }
            const segmentLegIndex = segment.legs.indexOf(leg);
            const globalLegIndex = segment.offset + segmentLegIndex;
            // The altitude constraint on the target leg of an on-route direct-to is always visible.
            if (plan.directToData.segmentIndex === segment.segmentIndex && plan.directToData.segmentLegIndex === segmentLegIndex) {
                return true;
            }
            // The altitude constraint on the VTF faf leg is always visible.
            if (msfsSdk.BitFlags.isAll(leg.flags, msfsSdk.LegDefinitionFlags.VectorsToFinalFaf)) {
                return true;
            }
            // The altitude constraint on the map leg is never visible.
            if (msfsSdk.BitFlags.isAny(leg.leg.fixTypeFlags, msfsSdk.FixTypeFlags.MAP)) {
                return false;
            }
            if (globalLegIndex === 0) {
                // The altitude constraint on the first flight plan leg is never visible, unless it is the first approach leg.
                // Note that the iaf of VTF approaches won't be handled by this case; VTF approaches always start with a
                // discontinuity leg so the iaf is never the first leg in the flight plan.
                return segment.segmentType === msfsSdk.FlightPlanSegmentType.Approach && segmentLegIndex === 0;
            }
            else {
                // Altitude constraints on legs immediately following discontinuities are not visible. This includes the iaf of
                // VTF approaches.
                const prevLeg = plan.getLeg(globalLegIndex - 1);
                if (msfsSdk.FlightPlanUtils.isDiscontinuityLeg(prevLeg.leg.type) || msfsSdk.FlightPlanUtils.isManualDiscontinuityLeg(prevLeg.leg.type)) {
                    return false;
                }
            }
            if (isAdvancedVNav) {
                switch (leg.leg.type) {
                    case msfsSdk.LegType.FM:
                    case msfsSdk.LegType.VM:
                    case msfsSdk.LegType.HM:
                        return false;
                    default:
                        return true;
                }
            }
            else {
                switch (leg.leg.type) {
                    case msfsSdk.LegType.FM:
                    case msfsSdk.LegType.VM:
                    case msfsSdk.LegType.HM:
                    case msfsSdk.LegType.CI:
                    case msfsSdk.LegType.VI:
                    case msfsSdk.LegType.CR:
                    case msfsSdk.LegType.VR:
                        return false;
                    default:
                        return true;
                }
            }
        }
        /**
         * Returns the speed restriction type to use based on the published speed and what segment it's in.
         * @param publishedSpeedRestriction The published speed.
         * @param segmentType The segment type.
         * @returns The speed restriction type to use.
         */
        static getPublishedSpeedDescBasedOnSegment(publishedSpeedRestriction, segmentType) {
            return publishedSpeedRestriction > 0 ?
                segmentType === msfsSdk.FlightPlanSegmentType.Departure
                    ? msfsSdk.SpeedRestrictionType.AtOrBelow
                    : msfsSdk.SpeedRestrictionType.At
                : msfsSdk.SpeedRestrictionType.Unused;
        }
        /**
         * Determines whether an altitude should be displayed as a flight level.
         * @param altitudeMeters The altitude in meters.
         * @returns Whether an altitude should be displayed as a flight level.
         */
        static displayAltitudeAsFlightLevel(altitudeMeters) {
            // TODO If we ever get transition level info from the sim, or implement the advanced VNAV Profile page (G3000), change this
            return Math.round(msfsSdk.UnitType.METER.convertTo(altitudeMeters, msfsSdk.UnitType.FOOT)) >= 18000;
        }
        /**
         * Checks whether two FMS flight phase objects are equal.
         * @param a The first FMS flight phase object to compare.
         * @param b The second FMS flight phase object to compare.
         * @returns Whether the two FMS flight phase objects are equal.
         */
        static flightPhaseEquals(a, b) {
            return a.isApproachActive === b.isApproachActive
                && a.isToFaf === b.isToFaf
                && a.isPastFaf === b.isPastFaf
                && a.isInMissedApproach === b.isInMissedApproach;
        }
        /**
         * Checks whether two FMS approach details objects are equal.
         * @param a The first FMS approach details object to compare.
         * @param b The second FMS approach details object to compare.
         * @returns Whether the two FMS approach details objects are equal.
         */
        static approachDetailsEquals(a, b) {
            var _a, _b, _c, _d;
            return a.isLoaded === b.isLoaded
                && a.type === b.type
                && a.isRnpAr === b.isRnpAr
                && a.bestRnavType === b.bestRnavType
                && a.rnavTypeFlags === b.rnavTypeFlags
                && a.isCircling === b.isCircling
                && a.isVtf === b.isVtf
                && ((_a = a.referenceFacility) === null || _a === void 0 ? void 0 : _a.icao) === ((_b = b.referenceFacility) === null || _b === void 0 ? void 0 : _b.icao)
                && ((_c = a.runway) === null || _c === void 0 ? void 0 : _c.designation) === ((_d = b.runway) === null || _d === void 0 ? void 0 : _d.designation);
        }
    }
    /** The index of the primary flight plan. */
    FmsUtils.PRIMARY_PLAN_INDEX = 0;
    /** The index of the off-route direct-to flight plan. */
    FmsUtils.DTO_RANDOM_PLAN_INDEX = 1;
    /** The index of the procedure preview flight plan. */
    FmsUtils.PROC_PREVIEW_PLAN_INDEX = 2;
    /** The number of flight plan legs between a direct-to target leg and its associated direct-to leg. */
    FmsUtils.DTO_LEG_OFFSET = 3;
    FmsUtils.vec3Cache = [new Float64Array(3)];
    FmsUtils.geoPointCache = [new msfsSdk.GeoPoint(0, 0)];
    FmsUtils.geoCircleCache = [new msfsSdk.GeoCircle(new Float64Array(3), 0)];
    /**
     * Gets the best RNAV minimum type available for a given approach.
     * @param query The approach to check, or its RNAV type flags.
     * @returns The best RNAV minimum type available for the specified approach.
     */
    FmsUtils.getBestRnavType = msfsSdk.ApproachUtils.getBestRnavType;
    FmsUtils.APPROACH_TYPE_QUALITY = {
        [ApproachType.APPROACH_TYPE_UNKNOWN]: 0,
        [msfsSdk.AdditionalApproachType.APPROACH_TYPE_VISUAL]: 1,
        [ApproachType.APPROACH_TYPE_NDB]: 2,
        [ApproachType.APPROACH_TYPE_NDBDME]: 3,
        [ApproachType.APPROACH_TYPE_VOR]: 4,
        [ApproachType.APPROACH_TYPE_VORDME]: 5,
        [ApproachType.APPROACH_TYPE_GPS]: 6,
        [ApproachType.APPROACH_TYPE_RNAV]: 7,
        [ApproachType.APPROACH_TYPE_SDF]: 8,
        [ApproachType.APPROACH_TYPE_LDA]: 9,
        [ApproachType.APPROACH_TYPE_LOCALIZER_BACK_COURSE]: 10,
        [ApproachType.APPROACH_TYPE_LOCALIZER]: 11,
        [ApproachType.APPROACH_TYPE_ILS]: 12
    };
    /**
     * Checks whether an approach procedure is an RNP (AR) approach.
     * @param proc The approach procedure to check.
     * @returns Whether the approach procedure is an RNP (AR) approach.
     */
    FmsUtils.isApproachRnpAr = msfsSdk.ApproachUtils.isRnpAr;

    /**
     * A computer that calculates Garmin guidance data for an autopilot nav-to-nav manager.
     *
     * The guidance data produced by the computer allows automatic CDI source switching to be armed while all of the
     * following conditions are met:
     * - A localizer-based approach is loaded and active in the flight plan.
     * - The approach frequency is tuned in the active frequency bank of one or more supported NAV radios.
     *
     * While the guidance data allows automatic CDI source switching to be armed, it also allows the autopilot's LOC and GS
     * modes to be armed.
     *
     * The guidance data allows automatic CDI source switching to occur when the conditions for arming in addition to all
     * of the following conditions are met:
     * - The approach leg ending at the faf fix is active in the flight plan.
     * - The airplane is within 15 nautical miles of the faf fix.
     * - LNAV CDI deviation is less than 1.2 times full-scale deviation.
     */
    class GarminNavToNavComputer {
        // eslint-disable-next-line jsdoc/require-jsdoc
        constructor(bus, arg2, options) {
            var _a, _b, _c;
            this.bus = bus;
            this.navRadioData = [];
            this.cdiSource = msfsSdk.ConsumerSubject.create(null, undefined);
            this.planePos = new msfsSdk.GeoPoint(0, 0);
            this.lnavIsTracking = false;
            this.lnavDataXtk = msfsSdk.ConsumerValue.create(null, 0);
            this.lnavDataCdiScale = msfsSdk.ConsumerValue.create(null, 1);
            this.approachDetails = GarminNavToNavComputer.EMPTY_APPROACH_DETAILS;
            this.isApproachActive = false;
            this.inhibitSwitchArmed = false;
            this.inhibitSwitch = false;
            this._armableNavRadioIndex = msfsSdk.Subject.create(-1);
            /**
             * The index of the NAV radio that can be armed for a CDI source switch, or `-1` if a CDI source switch cannot be
             * armed.
             */
            this.armableNavRadioIndex = this._armableNavRadioIndex;
            this._armableLateralMode = msfsSdk.Subject.create(msfsSdk.APLateralModes.NONE);
            /**
             * The autopilot lateral mode that can be armed prior to a CDI source switch, or `APLateralModes.NONE` if no modes
             * can be armed.
             */
            this.armableLateralMode = this._armableLateralMode;
            this._armableVerticalMode = msfsSdk.Subject.create(msfsSdk.APVerticalModes.NONE);
            /**
             * The autopilot vertical mode that can be armed prior to a CDI source switch, or `APVerticalModes.NONE` if no modes
             * can be armed.
             */
            this.armableVerticalMode = this._armableVerticalMode;
            this._canSwitchCdi = msfsSdk.Subject.create(false);
            /** Whether a CDI source switch is allowed at the current time. */
            this.canSwitchCdi = this._canSwitchCdi;
            this.fmsSubs = [];
            if (arg2 instanceof msfsSdk.FlightPlanner) {
                this.flightPlanner = arg2;
            }
            else {
                this.fms = msfsSdk.SubscribableUtils.toSubscribable(arg2, true);
            }
            this.cdiId = (_a = options === null || options === void 0 ? void 0 : options.cdiId) !== null && _a !== void 0 ? _a : '';
            this.navRadioIndexes = msfsSdk.SubscribableUtils.isSubscribableSet(options === null || options === void 0 ? void 0 : options.navRadioIndexes)
                ? options.navRadioIndexes
                : msfsSdk.SetSubject.create(options === null || options === void 0 ? void 0 : options.navRadioIndexes);
            this.inhibitMultipleSwitches = (_b = options === null || options === void 0 ? void 0 : options.inhibitMultipleSwitches) !== null && _b !== void 0 ? _b : false;
            const sub = this.bus.getSubscriber();
            if (this.inhibitMultipleSwitches) {
                this.cdiSource.setConsumer(sub.on(`cdi_select${msfsSdk.CdiUtils.getEventBusTopicSuffix(this.cdiId)}`));
                this.inhibitSwitchSub = this.cdiSource.sub(this.onCdiSourceChanged.bind(this), false, true);
            }
            this.canSwitchUpdateSub = sub.on('realTime').atFrequency((_c = options === null || options === void 0 ? void 0 : options.canSwitchUpdateFrequency) !== null && _c !== void 0 ? _c : 1).handle(this.updateCanSwitchCdi.bind(this), true);
            sub.on('gps-position').handle(lla => { this.planePos.set(lla.lat, lla.long); });
            const approachDetailsHandler = this.onApproachDetailsChanged.bind(this);
            const fmsFlightPhaseHandler = this.onFmsFlightPhaseChanged.bind(this);
            const approachActivatedHandler = this.onApproachActivated.bind(this);
            const lnavIsTrackingHandler = (isTracking) => { this.lnavIsTracking = isTracking; };
            if (this.fms) {
                this.fms.sub(fms => {
                    for (const fmsSub of this.fmsSubs) {
                        fmsSub.destroy();
                    }
                    this.fmsSubs.length = 0;
                    this.lnavIsTracking = false;
                    this.approachDetails = GarminNavToNavComputer.EMPTY_APPROACH_DETAILS;
                    this.isApproachActive = false;
                    this.updateArmableState();
                    const lnavTopicSuffix = msfsSdk.LNavUtils.getEventBusTopicSuffix(fms.lnavIndex);
                    this.fmsSubs.push(FmsUtils.onFmsEvent(fms.flightPlanner.id, sub, 'fms_approach_details').handle(approachDetailsHandler), FmsUtils.onFmsEvent(fms.flightPlanner.id, sub, 'fms_flight_phase').handle(fmsFlightPhaseHandler), sub.on(`lnav_is_tracking${lnavTopicSuffix}`).handle(lnavIsTrackingHandler));
                    this.lnavDataXtk.setConsumer(sub.on(`lnavdata_xtk${lnavTopicSuffix}`));
                    this.lnavDataCdiScale.setConsumer(sub.on(`lnavdata_cdi_scale${lnavTopicSuffix}`));
                    if (this.inhibitMultipleSwitches) {
                        this.fmsSubs.push(FmsUtils.onFmsEvent(fms.flightPlanner.id, sub, 'fms_approach_activate').handle(approachActivatedHandler));
                    }
                }, true);
            }
            else if (this.flightPlanner) {
                FmsUtils.onFmsEvent(this.flightPlanner.id, sub, 'fms_approach_details').handle(approachDetailsHandler);
                FmsUtils.onFmsEvent(this.flightPlanner.id, sub, 'fms_flight_phase').handle(fmsFlightPhaseHandler);
                sub.on('lnav_is_tracking').handle(lnavIsTrackingHandler);
                this.lnavDataXtk.setConsumer(sub.on('lnavdata_xtk'));
                this.lnavDataCdiScale.setConsumer(sub.on('lnavdata_cdi_scale'));
                if (this.inhibitMultipleSwitches) {
                    FmsUtils.onFmsEvent(this.flightPlanner.id, sub, 'fms_approach_activate').handle(approachActivatedHandler);
                }
            }
            this.navRadioIndexes.sub(this.onNavRadioIndexesChanged.bind(this), true);
        }
        /**
         * Responds to when this computer's supported NAV radio indexes change.
         * @param set The set containing the supported NAV radio indexes.
         * @param type The type of change that occurred.
         * @param index The NAV radio index that was added or removed.
         */
        onNavRadioIndexesChanged(set, type, index) {
            if (type === msfsSdk.SubscribableSetEventType.Deleted) {
                const data = this.navRadioData[index];
                if (data) {
                    data.activeFreq.destroy();
                    data.isLocalizer.destroy();
                    this.navRadioData[index] = undefined;
                }
            }
            else {
                if (!this.navRadioData[index]) {
                    const sub = this.bus.getSubscriber();
                    const updateState = this.updateArmableState.bind(this);
                    const activeFreq = msfsSdk.ConsumerSubject.create(sub.on(`nav_active_frequency_${index}`), 0);
                    const isLocalizer = msfsSdk.ConsumerSubject.create(sub.on(`nav_localizer_${index}`), false);
                    activeFreq.sub(updateState);
                    isLocalizer.sub(updateState);
                    this.navRadioData[index] = {
                        activeFreq,
                        isLocalizer
                    };
                }
            }
            this.updateArmableState();
        }
        /**
         * Responds to when the FMS approach details change.
         * @param approachDetails The new FMS approach details.
         */
        onApproachDetailsChanged(approachDetails) {
            const didChange = !FmsUtils.approachDetailsEquals(approachDetails, this.approachDetails);
            this.approachDetails = approachDetails;
            if (didChange) {
                this.inhibitSwitchArmed = false;
                this.inhibitSwitch = false;
                this.updateArmableState();
            }
        }
        /**
         * Responds to when the FMS flight phase changes.
         * @param flightPhase The new FMS flight phase.
         */
        onFmsFlightPhaseChanged(flightPhase) {
            const isApproachActive = flightPhase.isApproachActive;
            if (isApproachActive !== this.isApproachActive) {
                this.inhibitSwitchArmed = false;
                this.inhibitSwitch = false;
                this.isApproachActive = isApproachActive;
                this.updateArmableState();
            }
        }
        /**
         * Responds to when the FMS flight phase changes.
         */
        onApproachActivated() {
            this.inhibitSwitchArmed = false;
            this.inhibitSwitch = false;
        }
        /**
         * Responds to when the source for this computer's associated CDI changes.
         * @param source The new CDI source.
         */
        onCdiSourceChanged(source) {
            if (source === undefined) {
                return;
            }
            if (this.inhibitSwitchArmed) {
                if (source.type === msfsSdk.NavSourceType.Nav) {
                    this.inhibitSwitch = true;
                    this.inhibitSwitchArmed = false;
                    this.inhibitSwitchSub.pause();
                }
            }
            else if (source.type === msfsSdk.NavSourceType.Gps) {
                this.inhibitSwitchArmed = true;
            }
        }
        /**
         * Updates the CDI switch armable state calculated by this computer.
         */
        updateArmableState() {
            let armableNavRadioIndex = -1;
            if (this.approachDetails.referenceFacility
                && (
                // Nav-to-nav supports all approaches that use a localizer except backcourse approaches.
                this.approachDetails.type === ApproachType.APPROACH_TYPE_ILS
                    || this.approachDetails.type === ApproachType.APPROACH_TYPE_LOCALIZER
                    || this.approachDetails.type === ApproachType.APPROACH_TYPE_LDA
                    || this.approachDetails.type === ApproachType.APPROACH_TYPE_SDF)
                && this.isApproachActive) {
                const apprFreq = this.approachDetails.referenceFacility.freqMHz;
                if (msfsSdk.RadioUtils.isLocalizerFrequency(apprFreq)) {
                    for (let i = 1; i < 5; i++) {
                        const data = this.navRadioData[i];
                        if (data
                            && Math.abs(data.activeFreq.get() - apprFreq) < 1e-3
                            && data.isLocalizer.get()) {
                            armableNavRadioIndex = i;
                            break;
                        }
                    }
                }
            }
            this._armableNavRadioIndex.set(armableNavRadioIndex);
            if (armableNavRadioIndex === -1) {
                this.canSwitchUpdateSub.pause();
                this._canSwitchCdi.set(false);
                this._armableLateralMode.set(msfsSdk.APLateralModes.NONE);
                this._armableVerticalMode.set(msfsSdk.APVerticalModes.NONE);
            }
            else {
                this.canSwitchUpdateSub.resume(true);
                this._armableLateralMode.set(msfsSdk.APLateralModes.LOC);
                this._armableVerticalMode.set(msfsSdk.APVerticalModes.GS);
            }
        }
        /**
         * Updates whether a CDI switch is allowed at the current time.
         */
        updateCanSwitchCdi() {
            var _a, _b;
            let canSwitchCdi = false;
            if (!this.inhibitSwitch) {
                const armedNavIndex = this._armableNavRadioIndex.get();
                if (armedNavIndex !== -1 && this.lnavIsTracking) {
                    const flightPlanner = (_b = (_a = this.fms) === null || _a === void 0 ? void 0 : _a.get().flightPlanner) !== null && _b !== void 0 ? _b : this.flightPlanner;
                    if (flightPlanner) {
                        const plan = flightPlanner.hasActiveFlightPlan() ? flightPlanner.getActiveFlightPlan() : undefined;
                        const activeLeg = plan === null || plan === void 0 ? void 0 : plan.tryGetLeg(plan.activeLateralLeg);
                        const deviation = Math.abs(this.lnavDataXtk.get() / this.lnavDataCdiScale.get());
                        if (activeLeg
                            && msfsSdk.BitFlags.isAny(activeLeg.leg.fixTypeFlags, msfsSdk.FixTypeFlags.FAF)
                            && activeLeg.calculated
                            && activeLeg.calculated.endLat !== undefined
                            && activeLeg.calculated.endLon !== undefined
                            && isFinite(deviation) && deviation < 1.2) {
                            const distanceToFaf = msfsSdk.UnitType.GA_RADIAN.convertTo(this.planePos.distance(activeLeg.calculated.endLat, activeLeg.calculated.endLon), msfsSdk.UnitType.NMILE);
                            if (distanceToFaf < 15) {
                                canSwitchCdi = true;
                            }
                        }
                    }
                }
            }
            if (this.inhibitSwitchSub) {
                if (canSwitchCdi) {
                    this.inhibitSwitchSub.resume(true);
                }
                else {
                    this.inhibitSwitchArmed = false;
                    this.inhibitSwitchSub.pause();
                }
            }
            this._canSwitchCdi.set(canSwitchCdi);
        }
    }
    GarminNavToNavComputer.EMPTY_APPROACH_DETAILS = {
        isLoaded: false,
        type: ApproachType.APPROACH_TYPE_UNKNOWN,
        isRnpAr: false,
        bestRnavType: msfsSdk.RnavTypeFlags.None,
        rnavTypeFlags: msfsSdk.RnavTypeFlags.None,
        isCircling: false,
        isVtf: false,
        referenceFacility: null,
        runway: null
    };

    /**
     * A Garmin nav-to-nav manager.
     */
    class GarminNavToNavManager {
        // eslint-disable-next-line jsdoc/require-jsdoc
        constructor(bus, arg2, apValues) {
            this.bus = bus;
            this.apValues = apValues;
            this.onTransferred = () => { };
            this.currentHeading = 0;
            this.approachDetails = GarminNavToNavManager.EMPTY_APPROACH_DETAILS;
            this.nav1Frequency = 0;
            this.nav2Frequency = 0;
            this.isSourceChanging = false;
            /** Index of nav radio that has a localizer and frequency matches loaded approach. */
            this.canArmIndex = 0;
            this.navToNavCompleted = false;
            this.lnavTrackedLegIndex = -1;
            this.planePos = new msfsSdk.GeoPoint(0, 0);
            this.fmsSubs = [];
            if (arg2 instanceof msfsSdk.FlightPlanner) {
                this.flightPlanner = arg2;
            }
            else {
                this.fms = msfsSdk.SubscribableUtils.toSubscribable(arg2, true);
            }
            this.monitorEvents();
        }
        /**
         * Method to monitor nav events to keep track of NAV related data needed for guidance.
         */
        monitorEvents() {
            const sub = this.bus.getSubscriber();
            this.sourceChangeSub = sub.on(`cdi_select${msfsSdk.CdiUtils.getEventBusTopicSuffix(this.apValues.cdiId)}`)
                .handle(this.handleNavToNavSourceChanged.bind(this), true);
            sub.on('set_radio_state').handle((state) => {
                this.updateRadioState(state);
            });
            sub.on('set_frequency').handle((frequency) => {
                if (frequency.bank == msfsSdk.FrequencyBank.Active && frequency.radio.radioType === msfsSdk.RadioType.Nav) {
                    switch (frequency.radio.index) {
                        case 1:
                            this.nav1Frequency = Math.round(frequency.frequency * 100) / 100;
                            break;
                        case 2:
                            this.nav2Frequency = Math.round(frequency.frequency * 100) / 100;
                    }
                    this.updateState();
                }
            });
            sub.on('nav_radio_localizer_1').handle((loc) => {
                this.nav1Localizer = loc;
                this.updateState();
            });
            sub.on('nav_radio_localizer_2').handle((loc) => {
                this.nav2Localizer = loc;
                this.updateState();
            });
            sub.on('nav_radio_cdi_1').handle((cdi) => {
                this.nav1Cdi = cdi;
            });
            sub.on('nav_radio_cdi_2').handle((cdi) => {
                this.nav2Cdi = cdi;
            });
            sub.on(`cdi_select${msfsSdk.CdiUtils.getEventBusTopicSuffix(this.apValues.cdiId)}`).handle((source) => this.activeSource = source);
            sub.on('hdg_deg').withPrecision(0).handle((h) => {
                this.currentHeading = h;
            });
            sub.on('gps-position').handle(lla => { this.planePos.set(lla.lat, lla.long); });
            const approachDetailsHandler = (approachDetails) => {
                this.approachDetails = approachDetails;
                this.navToNavCompleted = false;
                this.updateState();
            };
            const approachActivateHandler = () => {
                this.navToNavCompleted = false;
            };
            const lnavTrackedLegIndexHandler = (index) => {
                this.lnavTrackedLegIndex = index;
            };
            if (this.fms) {
                this.fms.sub(fms => {
                    for (const fmsSub of this.fmsSubs) {
                        fmsSub.destroy();
                    }
                    this.fmsSubs.length = 0;
                    this.lnavTrackedLegIndex = -1;
                    this.approachDetails = GarminNavToNavManager.EMPTY_APPROACH_DETAILS;
                    this.navToNavCompleted = false;
                    this.canArmIndex = 0;
                    this.fmsSubs.push(FmsUtils.onFmsEvent(fms.flightPlanner.id, sub, 'fms_approach_details').handle(approachDetailsHandler), FmsUtils.onFmsEvent(fms.flightPlanner.id, sub, 'fms_approach_activate').handle(approachActivateHandler), sub.on(`lnav_tracked_leg_index${msfsSdk.LNavUtils.getEventBusTopicSuffix(fms.lnavIndex)}`).handle(lnavTrackedLegIndexHandler));
                }, true);
            }
            else if (this.flightPlanner) {
                FmsUtils.onFmsEvent(this.flightPlanner.id, sub, 'fms_approach_details').handle(approachDetailsHandler);
                FmsUtils.onFmsEvent(this.flightPlanner.id, sub, 'fms_approach_activate').handle(approachActivateHandler);
                sub.on('lnav_tracked_leg_index').handle(lnavTrackedLegIndexHandler);
            }
            sub.on('realTime').atFrequency(1).handle(() => {
                if (!this.navToNavCompleted && !this.isSourceChanging && this.canArmIndex > 0) {
                    this.tryAutoSwitchSource();
                }
            });
            this.apValues.approachIsActive.sub((v) => {
                this.updateState();
                if (v) {
                    this.navToNavCompleted = false;
                }
            });
        }
        /** @inheritdoc */
        canLocArm() {
            return this.canArmIndex > 0;
        }
        /** @inheritdoc */
        canLocActivate() {
            if (this.canArmIndex < 1) {
                return false;
            }
            const cdi = this.canArmIndex === 1 ? this.nav1Cdi : this.nav2Cdi;
            const loc = this.canArmIndex === 1 ? this.nav1Localizer : this.nav2Localizer;
            if (cdi && cdi.deviation !== null && Math.abs(cdi.deviation) < 127 && (loc === null || loc === void 0 ? void 0 : loc.course)) {
                const dtk = loc && loc.isValid && loc.course ? loc.course * Avionics.Utils.RAD2DEG : undefined;
                if (dtk === null || dtk === undefined) {
                    return false;
                }
                const headingDiff = msfsSdk.NavMath.diffAngle(this.currentHeading, dtk);
                if (cdi.deviation > 0 && cdi.deviation < 65 && headingDiff < 0 && headingDiff > -90) {
                    return true;
                }
                else if (cdi.deviation < 0 && cdi.deviation > -65 && headingDiff > 0 && headingDiff < 90) {
                    return true;
                }
                else if (Math.abs(cdi.deviation) < 35 && Math.abs(headingDiff) < 20) {
                    return true;
                }
            }
            return false;
        }
        /**
         * Updates the canArmIndex after inputs from the event bus or changes in the approach frequency.
         */
        updateState() {
            if (this.approachDetails.referenceFacility
                && (
                // Nav-to-nav supports all approaches that use a localizer except backcourse approaches.
                this.approachDetails.type === ApproachType.APPROACH_TYPE_ILS
                    || this.approachDetails.type === ApproachType.APPROACH_TYPE_LOCALIZER
                    || this.approachDetails.type === ApproachType.APPROACH_TYPE_LDA
                    || this.approachDetails.type === ApproachType.APPROACH_TYPE_SDF)
                && this.apValues.approachIsActive.get()) {
                const apprFreq = Math.round(this.approachDetails.referenceFacility.freqMHz * 100) / 100;
                if (apprFreq > 107) {
                    if (apprFreq == this.nav1Frequency && this.nav1Localizer && this.nav1Localizer.isValid) {
                        this.canArmIndex = 1;
                    }
                    else if (apprFreq == this.nav2Frequency && this.nav2Localizer && this.nav2Localizer.isValid) {
                        this.canArmIndex = 2;
                    }
                    else {
                        this.canArmIndex = 0;
                    }
                }
                else {
                    this.canArmIndex = 0;
                }
            }
            else {
                this.canArmIndex = 0;
            }
        }
        /**
         * Updates the nav 1 and nav 2 frequency from the bus.
         * @param radioState A radiostate event.
         */
        updateRadioState(radioState) {
            if (radioState.radioType === msfsSdk.RadioType.Nav) {
                switch (radioState.index) {
                    case 1:
                        this.nav1Frequency = Math.round(radioState.activeFrequency * 100) / 100;
                        break;
                    case 2:
                        this.nav2Frequency = Math.round(radioState.activeFrequency * 100) / 100;
                        break;
                }
                this.updateState();
            }
        }
        /**
         * Tries to auto switch the source if criteria are met.
         */
        tryAutoSwitchSource() {
            var _a, _b, _c, _d, _e;
            const flightPlanner = (_b = (_a = this.fms) === null || _a === void 0 ? void 0 : _a.get().flightPlanner) !== null && _b !== void 0 ? _b : this.flightPlanner;
            if (!flightPlanner) {
                return;
            }
            const plan = flightPlanner.hasActiveFlightPlan() && flightPlanner.getActiveFlightPlan();
            const currentLeg = plan && this.lnavTrackedLegIndex >= 0 && this.lnavTrackedLegIndex < plan.length
                ? plan.getLeg(this.lnavTrackedLegIndex)
                : undefined;
            if (!this.navToNavCompleted
                && !this.isSourceChanging
                && ((_c = currentLeg === null || currentLeg === void 0 ? void 0 : currentLeg.calculated) === null || _c === void 0 ? void 0 : _c.endLat) !== undefined
                && ((_d = currentLeg === null || currentLeg === void 0 ? void 0 : currentLeg.calculated) === null || _d === void 0 ? void 0 : _d.endLon) !== undefined) {
                const fafIsActive = (currentLeg.leg.fixTypeFlags & msfsSdk.FixTypeFlags.FAF) !== 0;
                const fafDistance = msfsSdk.UnitType.GA_RADIAN.convertTo(this.planePos.distance(currentLeg.calculated.endLat, currentLeg.calculated.endLon), msfsSdk.UnitType.NMILE);
                if (fafIsActive && fafDistance < 15 && this.canArmIndex > 0 && this.canLocActivate() && ((_e = this.activeSource) === null || _e === void 0 ? void 0 : _e.type) === msfsSdk.NavSourceType.Gps) {
                    this.sourceChangeSub.resume();
                    this.changeSource();
                }
            }
        }
        /**
         * Method to set the HSI/NAV Source to the Can Arm Index.
         */
        changeSource() {
            const navSource = {
                type: msfsSdk.NavSourceType.Nav,
                index: this.canArmIndex
            };
            this.isSourceChanging = true;
            this.bus.getPublisher()
                .pub(`cdi_src_set${msfsSdk.CdiUtils.getEventBusTopicSuffix(this.apValues.cdiId)}`, navSource, true, false);
        }
        /**
         * Callback to handle the nav source changed event when received.
         * @param e is the NavSourceId event
         */
        handleNavToNavSourceChanged(e) {
            if (e.type === msfsSdk.NavSourceType.Nav && e.index === this.canArmIndex) {
                this.onTransferred();
                this.isSourceChanging = false;
                this.sourceChangeSub.pause();
            }
        }
    }
    GarminNavToNavManager.EMPTY_APPROACH_DETAILS = {
        isLoaded: false,
        type: ApproachType.APPROACH_TYPE_UNKNOWN,
        isRnpAr: false,
        bestRnavType: msfsSdk.RnavTypeFlags.None,
        rnavTypeFlags: msfsSdk.RnavTypeFlags.None,
        isCircling: false,
        isVtf: false,
        referenceFacility: null,
        runway: null
    };

    /**
     * An implementation of `NavToNavManager2` that uses guidance data generated from an outside source to determine when
     * CDI source switching can be armed and triggered.
     */
    class GarminNavToNavManager2 {
        /**
         * Creates a new instance of GarminNavToNavManager2.
         * @param bus The event bus.
         * @param apValues Autopilot values from this manager's parent autopilot.
         * @param guidance The guidance data used by this manager.
         * @param options Options with which to configure the manager.
         */
        constructor(bus, apValues, guidance, options) {
            var _a;
            this.bus = bus;
            this.apValues = apValues;
            this.guidance = guidance;
            /** @inheritDoc */
            this.isNavToNavManager2 = true;
            this.cdiSource = msfsSdk.ConsumerValue.create(null, undefined);
            this.isNavToNavInProgress = false;
            this.navToNavInProgressCdiId = undefined;
            this.navToNavInProgressLateralMode = msfsSdk.APLateralModes.NONE;
            this.cdiSource.setConsumer(bus.getSubscriber().on(`cdi_select${msfsSdk.CdiUtils.getEventBusTopicSuffix(apValues.cdiId)}`));
            this.canArmedModeActivate = (_a = options === null || options === void 0 ? void 0 : options.canArmedModeActivate) !== null && _a !== void 0 ? _a : GarminNavToNavManager2.defaultCanArmedModeActivate;
        }
        /** @inheritDoc */
        getArmableNavRadioIndex() {
            return this.guidance.armableNavRadioIndex.get();
        }
        /** @inheritDoc */
        getArmableLateralMode() {
            return this.guidance.armableLateralMode.get();
        }
        /** @inheritDoc */
        getArmableVerticalMode() {
            return this.guidance.armableVerticalMode.get();
        }
        /** @inheritDoc */
        isTransferInProgress() {
            return this.isNavToNavInProgress || this.guidance.isExternalCdiSwitchInProgress.get();
        }
        /** @inheritDoc */
        onBeforeUpdate() {
            // noop
        }
        /** @inheritDoc */
        onAfterUpdate() {
            const isExternalSwitchInProgress = this.guidance.isExternalCdiSwitchInProgress.get();
            if (this.isNavToNavInProgress) {
                if (isExternalSwitchInProgress) {
                    this.cancelNavToNav();
                }
                else {
                    this.updateInProgress();
                }
            }
            else {
                if (!isExternalSwitchInProgress) {
                    this.updateDefault();
                }
            }
        }
        /**
         * Updates this manager in the default state while a CDI source switch is not in progress.
         */
        updateDefault() {
            var _a;
            const armableNavRadioIndex = this.guidance.armableNavRadioIndex.get();
            const armableLateralMode = this.guidance.armableLateralMode.get();
            const isArmed = armableNavRadioIndex !== -1
                && armableLateralMode !== msfsSdk.APLateralModes.NONE
                && ((_a = this.cdiSource.get()) === null || _a === void 0 ? void 0 : _a.type) === msfsSdk.NavSourceType.Gps
                && this.apValues.lateralActive.get() !== armableLateralMode
                && this.apValues.lateralArmed.get() === armableLateralMode;
            if (isArmed && this.guidance.canSwitchCdi.get() && this.canArmedModeActivate(armableNavRadioIndex, armableLateralMode)) {
                this.startNavToNav(this.guidance.cdiId.get(), armableNavRadioIndex, armableLateralMode);
            }
        }
        /**
         * Updates this manager while a CDI source switch is in progress.
         */
        updateInProgress() {
            var _a;
            const cdiId = this.guidance.cdiId.get();
            if (cdiId !== this.navToNavInProgressCdiId) {
                this.cancelNavToNav();
            }
            else {
                if (((_a = this.cdiSource.get()) === null || _a === void 0 ? void 0 : _a.type) === msfsSdk.NavSourceType.Nav) {
                    this.completeNavToNav();
                }
            }
        }
        /**
         * Starts a CDI source switch.
         * @param cdiId The ID of the CDI for which to command the switch.
         * @param navRadioIndex The index of the NAV radio to which to switch the CDI.
         * @param armedLateralMode The armed autopilot lateral mode to activate as part of the switch.
         */
        startNavToNav(cdiId, navRadioIndex, armedLateralMode) {
            this.isNavToNavInProgress = true;
            this.navToNavInProgressCdiId = cdiId;
            this.navToNavInProgressLateralMode = armedLateralMode;
            this.bus.getPublisher().pub(`cdi_src_set${msfsSdk.CdiUtils.getEventBusTopicSuffix(cdiId)}`, { type: msfsSdk.NavSourceType.Nav, index: navRadioIndex }, true, false);
        }
        /**
         * Cancels a CDI source switch.
         */
        cancelNavToNav() {
            this.isNavToNavInProgress = false;
            this.navToNavInProgressCdiId = undefined;
            this.navToNavInProgressLateralMode = msfsSdk.APLateralModes.NONE;
        }
        /**
         * Completes a CDI source switch.
         */
        completeNavToNav() {
            const lateralMode = this.navToNavInProgressLateralMode;
            this.isNavToNavInProgress = false;
            this.navToNavInProgressCdiId = undefined;
            this.navToNavInProgressLateralMode = msfsSdk.APLateralModes.NONE;
            this.onTransferred && this.onTransferred(lateralMode, msfsSdk.APVerticalModes.NONE);
        }
        /**
         * Checks whether an armed autopilot lateral mode can be activated as part of a CDI source switch using criteria that
         * reproduce the default localizer capture criteria for the autopilot's LOC director.
         * @param navRadioIndex The index of the NAV radio that is armed for a potential CDI source switch.
         * @param armedLateralMode The armed autopilot lateral mode.
         * @returns Whether the specified armed autopilot lateral mode can be activated as part of a CDI source switch using
         * criteria that reproduce the default localizer capture criteria for the autopilot's LOC director.
         */
        static defaultCanArmedModeActivate(navRadioIndex, armedLateralMode) {
            if (armedLateralMode === msfsSdk.APLateralModes.LOC) {
                const hasSignal = SimVar.GetSimVarValue(`NAV SIGNAL:${navRadioIndex}`, msfsSdk.SimVarValueType.Number) > 0;
                if (hasSignal) {
                    const isLocalizer = SimVar.GetSimVarValue(`NAV HAS LOCALIZER:${navRadioIndex}`, msfsSdk.SimVarValueType.Bool) !== 0;
                    if (isLocalizer) {
                        const deviation = SimVar.GetSimVarValue(`NAV CDI:${navRadioIndex}`, msfsSdk.SimVarValueType.Number);
                        if (Math.abs(deviation) < 127) {
                            const localizerCourse = SimVar.GetSimVarValue(`NAV LOCALIZER:${navRadioIndex}`, msfsSdk.SimVarValueType.Degree);
                            const planeHeading = SimVar.GetSimVarValue('PLANE HEADING DEGREES MAGNETIC', msfsSdk.SimVarValueType.Degree);
                            return msfsSdk.MathUtils.diffAngleDeg(localizerCourse, planeHeading, false) < 110;
                        }
                    }
                }
            }
            return false;
        }
    }

    /**
     * Glidepath service levels.
     */
    exports.GlidepathServiceLevel = void 0;
    (function (GlidepathServiceLevel) {
        /** No glidepath. */
        GlidepathServiceLevel[GlidepathServiceLevel["None"] = 0] = "None";
        /** Visual. */
        GlidepathServiceLevel[GlidepathServiceLevel["Visual"] = 1] = "Visual";
        /** Visual with baro-VNAV. */
        GlidepathServiceLevel[GlidepathServiceLevel["VisualBaro"] = 2] = "VisualBaro";
        /** LNAV+V. */
        GlidepathServiceLevel[GlidepathServiceLevel["LNavPlusV"] = 3] = "LNavPlusV";
        /** LNAV+V with baro-VNAV. */
        GlidepathServiceLevel[GlidepathServiceLevel["LNavPlusVBaro"] = 4] = "LNavPlusVBaro";
        /** LNAV/VNAV. */
        GlidepathServiceLevel[GlidepathServiceLevel["LNavVNav"] = 5] = "LNavVNav";
        /** LNAV/VNAV with baro-VNAV. */
        GlidepathServiceLevel[GlidepathServiceLevel["LNavVNavBaro"] = 6] = "LNavVNavBaro";
        /** LP+V. */
        GlidepathServiceLevel[GlidepathServiceLevel["LpPlusV"] = 7] = "LpPlusV";
        /** LPV. */
        GlidepathServiceLevel[GlidepathServiceLevel["Lpv"] = 8] = "Lpv";
        /** RNP. */
        GlidepathServiceLevel[GlidepathServiceLevel["Rnp"] = 9] = "Rnp";
        /** RNP with baro-VNAV. */
        GlidepathServiceLevel[GlidepathServiceLevel["RnpBaro"] = 10] = "RnpBaro";
    })(exports.GlidepathServiceLevel || (exports.GlidepathServiceLevel = {}));

    /**
     * A calculator for providing the glidepath service level.
     */
    class GlidepathServiceLevelCalculator {
        /**
         * Creates an instance of the GlidepathServiceLevelCalculator.
         * @param allowPlusVWithoutSbas Whether to allow +V approach service levels when no SBAS is present.
         * @param allowApproachBaroVNav Whether to allow approach service levels requiring baro VNAV.
         * @param allowRnpAr Whether to allow RNP (AR) approach service levels.
         * @param gpsSystemState The current GPS system state.
         * @param approachDetails The currently selected approach details.
         */
        constructor(allowPlusVWithoutSbas, allowApproachBaroVNav, allowRnpAr, gpsSystemState, approachDetails) {
            this.allowPlusVWithoutSbas = allowPlusVWithoutSbas;
            this.allowApproachBaroVNav = allowApproachBaroVNav;
            this.allowRnpAr = allowRnpAr;
            this.gpsSystemState = gpsSystemState;
            this.approachDetails = approachDetails;
        }
        /**
         * Gets the current glidepath service level from the calculator.
         * @returns The current glidepath service level.
         */
        getServiceLevel() {
            if (this.gpsSystemState.get() === msfsSdk.GPSSystemState.Searching || this.gpsSystemState.get() === msfsSdk.GPSSystemState.Acquiring) {
                return exports.GlidepathServiceLevel.None;
            }
            const approachDetails = this.approachDetails.get();
            if (approachDetails.type === msfsSdk.AdditionalApproachType.APPROACH_TYPE_VISUAL) {
                return this.handleVisual();
            }
            if (approachDetails.isRnpAr && this.allowRnpAr) {
                return this.handleRnp();
            }
            else {
                switch (approachDetails.bestRnavType) {
                    case msfsSdk.RnavTypeFlags.LPV:
                        return this.handleLpv();
                    case msfsSdk.RnavTypeFlags.LP:
                        return this.handleLp();
                    case msfsSdk.RnavTypeFlags.LNAVVNAV:
                        return this.handleLnavVnav();
                    case msfsSdk.RnavTypeFlags.LNAV:
                        return this.handleLnav();
                    default:
                        return exports.GlidepathServiceLevel.None;
                }
            }
        }
        /**
         * Checks a service level to see if baro guidance is required.
         * @param serviceLevel The service level to check.
         * @returns True if baro guidance is required, false otherwise.
         */
        isBaroServiceLevel(serviceLevel) {
            switch (serviceLevel) {
                case exports.GlidepathServiceLevel.LNavPlusVBaro:
                case exports.GlidepathServiceLevel.LNavVNavBaro:
                case exports.GlidepathServiceLevel.VisualBaro:
                case exports.GlidepathServiceLevel.RnpBaro:
                    return true;
                default:
                    return false;
            }
        }
        /**
         * Handles when the best RNAV service level type is RNP (AR).
         * @returns The calculated result glidepath service level.
         */
        handleRnp() {
            if (this.gpsSystemState.get() === msfsSdk.GPSSystemState.DiffSolutionAcquired) {
                return exports.GlidepathServiceLevel.Rnp;
            }
            else if (this.gpsSystemState.get() === msfsSdk.GPSSystemState.SolutionAcquired) {
                if (this.allowApproachBaroVNav) {
                    return exports.GlidepathServiceLevel.RnpBaro;
                }
            }
            return exports.GlidepathServiceLevel.None;
        }
        /**
         * Handles when the best RNAV service level type is LPV.
         * @returns The calculated result glidepath service level.
         */
        handleLpv() {
            if (this.gpsSystemState.get() === msfsSdk.GPSSystemState.DiffSolutionAcquired) {
                return exports.GlidepathServiceLevel.Lpv;
            }
            else if (this.gpsSystemState.get() === msfsSdk.GPSSystemState.SolutionAcquired) {
                if (msfsSdk.BitFlags.isAny(this.approachDetails.get().rnavTypeFlags, msfsSdk.RnavTypeFlags.LNAVVNAV) && this.allowApproachBaroVNav) {
                    return exports.GlidepathServiceLevel.LNavVNavBaro;
                }
            }
            return exports.GlidepathServiceLevel.None;
        }
        /**
         * Handles when the best RNAV service level type is LP.
         * @returns The calculated result glidepath service level.
         */
        handleLp() {
            if (this.gpsSystemState.get() === msfsSdk.GPSSystemState.DiffSolutionAcquired) {
                return exports.GlidepathServiceLevel.LpPlusV;
            }
            return exports.GlidepathServiceLevel.None;
        }
        /**
         * Handles when the best RNAV service level type is LNAV/VNAV.
         * @returns The calculated result glidepath service level.
         */
        handleLnavVnav() {
            if (this.gpsSystemState.get() === msfsSdk.GPSSystemState.DiffSolutionAcquired) {
                return exports.GlidepathServiceLevel.LNavVNav;
            }
            else if (this.gpsSystemState.get() === msfsSdk.GPSSystemState.SolutionAcquired && this.allowApproachBaroVNav) {
                return exports.GlidepathServiceLevel.LNavVNavBaro;
            }
            return exports.GlidepathServiceLevel.None;
        }
        /**
         * Handles when the best RNAV service level type is LNAV.
         * @returns The calculated result glidepath service level.
         */
        handleLnav() {
            if (this.gpsSystemState.get() === msfsSdk.GPSSystemState.DiffSolutionAcquired) {
                return exports.GlidepathServiceLevel.LNavPlusV;
            }
            else if (this.gpsSystemState.get() === msfsSdk.GPSSystemState.SolutionAcquired) {
                if (this.allowPlusVWithoutSbas) {
                    return exports.GlidepathServiceLevel.LNavPlusV;
                }
                if (this.allowApproachBaroVNav) {
                    return exports.GlidepathServiceLevel.LNavPlusVBaro;
                }
            }
            return exports.GlidepathServiceLevel.None;
        }
        /**
         * Handles when the best RNAV service level type is Visual.
         * @returns The calculated result glidepath service level.
         */
        handleVisual() {
            if (this.gpsSystemState.get() === msfsSdk.GPSSystemState.DiffSolutionAcquired) {
                return exports.GlidepathServiceLevel.Visual;
            }
            else if (this.gpsSystemState.get() === msfsSdk.GPSSystemState.SolutionAcquired) {
                if (this.allowPlusVWithoutSbas) {
                    return exports.GlidepathServiceLevel.Visual;
                }
                if (this.allowApproachBaroVNav) {
                    return exports.GlidepathServiceLevel.VisualBaro;
                }
            }
            return exports.GlidepathServiceLevel.None;
        }
    }

    /**
     * A computer that calculates Garmin glidepath guidance for an active flight plan.
     */
    class GarminGlidepathComputer {
        /**
         * Creates a new instance of GarminGlidepathComputer.
         * @param index The index of this computer.
         * @param bus The event bus.
         * @param flightPlanner The flight planner containing the flight plan for which this computer provides guidance.
         * @param options Options with which to configure the computer.
         */
        constructor(index, bus, flightPlanner, options) {
            var _a, _b, _c, _d, _e, _f;
            this.index = index;
            this.bus = bus;
            this.flightPlanner = flightPlanner;
            this.publisher = this.bus.getPublisher();
            this.isLNavIndexValid = false;
            this.planePos = new msfsSdk.GeoPoint(0, 0);
            this.currentAltitude = 0;
            this.currentGpsAltitude = 0;
            this.fmsFlightPhase = msfsSdk.ConsumerValue.create(FmsUtils.onFmsEvent(this.flightPlanner.id, this.bus, 'fms_flight_phase'), {
                isApproachActive: false,
                isToFaf: false,
                isPastFaf: false,
                isInMissedApproach: false
            });
            this.approachDetails = msfsSdk.ConsumerSubject.create(FmsUtils.onFmsEvent(this.flightPlanner.id, this.bus, 'fms_approach_details'), {
                isLoaded: false,
                type: ApproachType.APPROACH_TYPE_UNKNOWN,
                isRnpAr: false,
                bestRnavType: msfsSdk.RnavTypeFlags.None,
                rnavTypeFlags: msfsSdk.RnavTypeFlags.None,
                isCircling: false,
                isVtf: false,
                referenceFacility: null,
                runway: null
            }, FmsUtils.approachDetailsEquals);
            this.approachHasGp = msfsSdk.Subject.create(false);
            this.gpVerticalDeviation = msfsSdk.Subject.create(null);
            this.gpDistance = msfsSdk.Subject.create(null);
            this.gpFpa = msfsSdk.Subject.create(null);
            this.gpServiceLevel = msfsSdk.Subject.create(exports.GlidepathServiceLevel.None);
            // LNAV Consumer Subjects
            this.lnavLegIndex = msfsSdk.ConsumerValue.create(null, 0);
            this.lnavLegDistanceAlong = msfsSdk.ConsumerValue.create(null, 0);
            this.lnavDataXtk = msfsSdk.ConsumerValue.create(null, 0);
            this.lnavDataCdiScale = msfsSdk.ConsumerValue.create(null, 4);
            this.glidepathGuidanceBuffer = msfsSdk.ArrayUtils.create(2, () => {
                return {
                    approachHasGlidepath: false,
                    isValid: false,
                    canCapture: false,
                    fpa: 0,
                    deviation: 0
                };
            });
            this._glidepathGuidance = msfsSdk.Subject.create(this.glidepathGuidanceBuffer[0], (a, b) => {
                return a.approachHasGlidepath === b.approachHasGlidepath
                    && ((!a.isValid && !b.isValid)
                        || (a.isValid === b.isValid
                            && a.canCapture === b.canCapture
                            && a.fpa === b.fpa
                            && a.deviation === b.deviation));
            });
            /** The glidepath guidance calculated by this computer. */
            this.glidepathGuidance = this._glidepathGuidance;
            if (!msfsSdk.VNavUtils.isValidVNavIndex(index)) {
                throw new Error(`GarminGlidepathComputer: invalid index (${index}) specified (must be a non-negative integer)`);
            }
            const simVarSuffix = this.index === 0 ? '' : `:${this.index}`;
            this.simVarMap = {};
            for (const simVar of Object.values(msfsSdk.VNavVars)) {
                this.simVarMap[simVar] = `${simVar}${simVarSuffix}`;
            }
            const eventBusTopicSuffix = msfsSdk.VNavUtils.getEventBusTopicSuffix(this.index);
            this.vnavTopicMap = {
                'gp_available': `gp_available${eventBusTopicSuffix}`
            };
            this.lnavIndex = msfsSdk.SubscribableUtils.toSubscribable((_a = options === null || options === void 0 ? void 0 : options.lnavIndex) !== null && _a !== void 0 ? _a : 0, true);
            this.primaryPlanIndex = (_b = options === null || options === void 0 ? void 0 : options.primaryPlanIndex) !== null && _b !== void 0 ? _b : 0;
            this.gpsSystemState = (_c = options === null || options === void 0 ? void 0 : options.gpsSystemState) !== null && _c !== void 0 ? _c : msfsSdk.Subject.create(msfsSdk.GPSSystemState.DiffSolutionAcquired);
            this.glidepathServiceLevelCalculator = new GlidepathServiceLevelCalculator((_d = options === null || options === void 0 ? void 0 : options.allowPlusVWithoutSbas) !== null && _d !== void 0 ? _d : true, (_e = options === null || options === void 0 ? void 0 : options.allowApproachBaroVNav) !== null && _e !== void 0 ? _e : false, (_f = options === null || options === void 0 ? void 0 : options.allowRnpAr) !== null && _f !== void 0 ? _f : false, this.gpsSystemState, this.approachDetails);
            this.glidepathCalculator = new msfsSdk.GlidePathCalculator(this.bus, this.flightPlanner, this.primaryPlanIndex);
            const sub = this.bus.getSubscriber();
            this.lnavIndex.sub(lnavIndex => {
                this.isLNavIndexValid = msfsSdk.LNavUtils.isValidLNavIndex(lnavIndex);
                if (this.isLNavIndexValid) {
                    const suffix = msfsSdk.LNavUtils.getEventBusTopicSuffix(lnavIndex);
                    this.lnavLegIndex.setConsumer(sub.on(`lnav_tracked_leg_index${suffix}`));
                    this.lnavLegDistanceAlong.setConsumer(sub.on(`lnav_leg_distance_along${suffix}`));
                    this.lnavDataXtk.setConsumer(sub.on(`lnavdata_xtk${suffix}`));
                    this.lnavDataCdiScale.setConsumer(sub.on(`lnavdata_cdi_scale${suffix}`));
                }
                else {
                    this.lnavLegIndex.setConsumer(null);
                    this.lnavLegDistanceAlong.setConsumer(null);
                    this.lnavDataXtk.setConsumer(null);
                    this.lnavDataCdiScale.setConsumer(null);
                }
            }, true);
            sub.on('indicated_alt').handle(alt => this.currentAltitude = alt);
            sub.on('gps-position').handle(lla => {
                this.planePos.set(lla.lat, lla.long);
                this.currentGpsAltitude = msfsSdk.UnitType.METER.convertTo(lla.alt, msfsSdk.UnitType.FOOT);
            });
            this.gpSupported = msfsSdk.ConsumerValue.create(FmsUtils.onFmsEvent(this.flightPlanner.id, sub, 'approach_supports_gp'), false);
            this.approachHasGp.sub(v => {
                this.publisher.pub(this.vnavTopicMap['gp_available'], v, true, true);
            });
            this.monitorVars();
        }
        /**
         * Applies the failed state to this computer's glidepath calculations.
         */
        failGlidepath() {
            this.approachHasGp.set(false);
            this.resetGpVars();
        }
        /**
         * Updates this computer.
         */
        update() {
            if (!this.isLNavIndexValid || !this.flightPlanner.hasFlightPlan(this.primaryPlanIndex)) {
                this.failGlidepath();
                this.updateGlidepathGuidance();
                return;
            }
            const lateralPlan = this.flightPlanner.getFlightPlan(this.primaryPlanIndex);
            const alongLegDistance = msfsSdk.UnitType.NMILE.convertTo(this.lnavLegDistanceAlong.get(), msfsSdk.UnitType.METER);
            const lateralLegIndex = this.lnavLegIndex.get();
            if (lateralPlan.length > 0
                && lateralLegIndex < lateralPlan.length) {
                this.manageGlidepath(lateralPlan, lateralLegIndex, alongLegDistance);
            }
            else {
                this.failGlidepath();
            }
            this.updateGlidepathGuidance();
        }
        /**
         * Updates the glidepath guidance provided by this computer.
         */
        updateGlidepathGuidance() {
            const guidanceBufferActiveIndex = this._glidepathGuidance.get() === this.glidepathGuidanceBuffer[0] ? 0 : 1;
            const guidance = this.glidepathGuidanceBuffer[(guidanceBufferActiveIndex + 1) % 2];
            const fpa = this.gpFpa.get();
            const deviation = this.gpVerticalDeviation.get();
            const fmsFlightPhase = this.fmsFlightPhase.get();
            guidance.approachHasGlidepath = this.approachHasGp.get();
            // Glidepath guidance is valid if and only if...
            const isValid 
            // ... FPA and deviation were successfully computed...
            = fpa !== null && deviation !== null
                // ... and the active flight plan leg is to or past the faf but not in the missed approach.
                && (fmsFlightPhase.isToFaf || fmsFlightPhase.isPastFaf) && !fmsFlightPhase.isInMissedApproach;
            guidance.isValid = isValid;
            // Can capture the glidepath if and only if...
            guidance.canCapture
                // ... guidance is valid...
                = isValid
                    // ... and FPA is downward sloping...
                    && fpa > 0
                    // ... and deviation is within limits...
                    && deviation <= 100 && deviation >= -15
                    // ... and the CDI is at less than full-scale deviation.
                    && Math.abs(this.lnavDataXtk.get() / this.lnavDataCdiScale.get()) < 1;
            guidance.fpa = fpa !== null && fpa !== void 0 ? fpa : 0;
            guidance.deviation = deviation !== null && deviation !== void 0 ? deviation : 0;
            this._glidepathGuidance.set(guidance);
        }
        /**
         * Method to monitor VNavVars.
         */
        monitorVars() {
            // init vnav vars
            this.initVars();
            this.gpVerticalDeviation.sub(dev => SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.GPVerticalDeviation], msfsSdk.SimVarValueType.Feet, dev !== null && dev !== void 0 ? dev : -1001));
            this.gpDistance.sub(dis => SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.GPDistance], msfsSdk.SimVarValueType.Meters, dis !== null && dis !== void 0 ? dis : -1));
            this.gpFpa.sub(fpa => SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.GPFpa], msfsSdk.SimVarValueType.Degree, fpa !== null && fpa !== void 0 ? fpa : 0));
            this.gpServiceLevel.sub(gpServiceLevel => SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.GPServiceLevel], msfsSdk.SimVarValueType.Number, gpServiceLevel));
        }
        /**
         * Initializes glidepath-related SimVars.
         */
        initVars() {
            SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.GPServiceLevel], msfsSdk.SimVarValueType.Number, exports.GlidepathServiceLevel.None);
            SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.GPVerticalDeviation], msfsSdk.SimVarValueType.Feet, -1001);
            SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.GPDistance], msfsSdk.SimVarValueType.Meters, -1);
            SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.GPFpa], msfsSdk.SimVarValueType.Degree, 0);
        }
        /**
         * Resets glidepath-related SimVars.
         */
        resetGpVars() {
            this.gpServiceLevel.set(exports.GlidepathServiceLevel.None);
            this.gpVerticalDeviation.set(null);
            this.gpDistance.set(null);
            this.gpFpa.set(null);
        }
        /**
         * Manages glidepath state.
         * @param lateralPlan The lateral flight plan.
         * @param activeLegIndex The global index of the active flight plan leg.
         * @param distanceAlongLeg The along-track distance from the start of the active flight plan leg to the airplane's
         * position, in meters.
         */
        manageGlidepath(lateralPlan, activeLegIndex, distanceAlongLeg) {
            if (lateralPlan && this.gpSupported.get()) {
                const gpServiceLevel = this.glidepathServiceLevelCalculator.getServiceLevel();
                if (gpServiceLevel !== exports.GlidepathServiceLevel.None) {
                    this.gpServiceLevel.set(gpServiceLevel);
                    const gpDistance = this.glidepathCalculator.getGlidepathDistance(activeLegIndex, distanceAlongLeg);
                    this.gpDistance.set(gpDistance);
                    const currentAlt = this.glidepathServiceLevelCalculator.isBaroServiceLevel(gpServiceLevel) ? this.currentAltitude : this.currentGpsAltitude;
                    const desiredGPAltitudeFeet = msfsSdk.UnitType.METER.convertTo(this.glidepathCalculator.getDesiredGlidepathAltitude(gpDistance), msfsSdk.UnitType.FOOT);
                    this.gpVerticalDeviation.set(msfsSdk.MathUtils.clamp(desiredGPAltitudeFeet - currentAlt, -1000, 1000));
                    this.gpFpa.set(this.glidepathCalculator.glidepathFpa);
                    this.approachHasGp.set(true);
                    return;
                }
            }
            this.approachHasGp.set(false);
            this.resetGpVars();
        }
    }

    /**
     * Garmin VNAV flight phases.
     */
    exports.GarminVNavFlightPhase = void 0;
    (function (GarminVNavFlightPhase) {
        GarminVNavFlightPhase["None"] = "None";
        GarminVNavFlightPhase["Climb"] = "Climb";
        GarminVNavFlightPhase["Cruise"] = "Cruise";
        GarminVNavFlightPhase["Descent"] = "Descent";
    })(exports.GarminVNavFlightPhase || (exports.GarminVNavFlightPhase = {}));
    /**
     * Garmin VNAV tracking phases.
     */
    exports.GarminVNavTrackingPhase = void 0;
    (function (GarminVNavTrackingPhase) {
        GarminVNavTrackingPhase["None"] = "None";
        GarminVNavTrackingPhase["Climb"] = "Climb";
        GarminVNavTrackingPhase["Cruise"] = "Cruise";
        GarminVNavTrackingPhase["Descent"] = "Descent";
        GarminVNavTrackingPhase["MissedApproach"] = "MissedApproach";
    })(exports.GarminVNavTrackingPhase || (exports.GarminVNavTrackingPhase = {}));

    /**
     * A utility class for working with Garmin VNAV.
     */
    class GarminVNavUtils {
        /**
         * Checks if a lateral flight plan leg is eligible for VNAV.
         * @param lateralLeg A lateral flight plan leg.
         * @returns Whether the specified leg is eligible for VNAV.
         */
        static isLegVNavEligible(lateralLeg) {
            switch (lateralLeg.leg.type) {
                case msfsSdk.LegType.HA:
                case msfsSdk.LegType.HF:
                case msfsSdk.LegType.HM:
                case msfsSdk.LegType.PI:
                case msfsSdk.LegType.VM:
                case msfsSdk.LegType.FM:
                case msfsSdk.LegType.Discontinuity:
                case msfsSdk.LegType.ThruDiscontinuity:
                    return false;
                default:
                    return true;
            }
        }
        /**
         * Checks whether an altitude constraint defined for a lateral flight plan leg should be used for VNAV.
         * @param lateralPlan The lateral flight plan that hosts the altitude constraint.
         * @param lateralLeg The lateral flight plan leg that hosts the altitude constraint.
         * @param globalLegIndex The global index of the lateral flight plan leg that hosts the altitude constraint.
         * @param segment The lateral flight plan segment containing the flight plan leg that hosts the altitude constraint.
         * @param segmentLegIndex The index of the lateral flight plan leg that hosts the altitude constraint in its
         * containing segment.
         * @returns Whether the altitude constraint defined for the specified lateral flight plan leg should be used for
         * VNAV.
         */
        static shouldUseConstraint(lateralPlan, lateralLeg, globalLegIndex, segment, segmentLegIndex) {
            // Never use the constraint from the first flight plan leg.
            if (globalLegIndex === 0) {
                return false;
            }
            // Never use constraints from legs prior to an on-route direct-to.
            if (segment.segmentIndex < lateralPlan.directToData.segmentIndex
                || (segment.segmentIndex === lateralPlan.directToData.segmentIndex && segmentLegIndex < lateralPlan.directToData.segmentLegIndex + FmsUtils.DTO_LEG_OFFSET)) {
                return false;
            }
            // Always use the constraint on an on-route direct-to leg.
            if (msfsSdk.BitFlags.isAll(lateralLeg.flags, msfsSdk.LegDefinitionFlags.DirectTo)
                && lateralPlan.directToData.segmentIndex === segment.segmentIndex
                && lateralPlan.directToData.segmentLegIndex + FmsUtils.DTO_LEG_OFFSET === segmentLegIndex) {
                return true;
            }
            // Always use the constraint on a VTF faf leg.
            if (msfsSdk.BitFlags.isAll(lateralLeg.flags, msfsSdk.LegDefinitionFlags.VectorsToFinalFaf)) {
                return true;
            }
            // Never use constraints from legs that immediately follow a discontinuity.
            const prevLeg = lateralPlan.getLeg(globalLegIndex - 1);
            if (msfsSdk.FlightPlanUtils.isDiscontinuityLeg(prevLeg.leg.type) || msfsSdk.FlightPlanUtils.isManualDiscontinuityLeg(prevLeg.leg.type)) {
                return false;
            }
            return true;
        }
        /**
         * A function which checks whether a climb constraint should be invalidated.
         * @param constraint A descent constraint.
         * @param index The index of the constraint to check if it is already in the vertical flight plan. If the constraint
         * is not already in the vertical flight plan (i.e. the constraint has been previously invalidated), then this value
         * equals `-(index + 1)` where `index` is the index at which the constraint would appear in the vertical flight plan
         * if it were included.
         * @param constraints The array of VNAV constraints currently in the vertical flight plan.
         * @param firstDescentConstraintIndex The index of the first descent constraint in the vertical flight plan, if one
         * exists.
         * @param priorMinAltitude The most recent minimum altitude, in meters, defined by a VNAV constraint prior to the
         * constraint to check. Only prior constraints connected to the constraint to check by a contiguous sequence of
         * constraints of the same category (climb or missed approach) are included.
         * @param priorMaxAltitude The most recent maximum altitude, in meters, defined by a VNAV constraint prior to the
         * constraint to check. Only prior constraints connected to the constraint to check by a contiguous sequence of
         * constraints of the same category (climb or missed approach) are included.
         * @returns Whether the specified climb constraint should be invalidated.
         */
        static invalidateClimbConstraint(constraint, index, constraints, firstDescentConstraintIndex, priorMinAltitude, priorMaxAltitude) {
            if (constraint.type === 'climb' && firstDescentConstraintIndex >= 0 && index < firstDescentConstraintIndex) {
                return true;
            }
            if (isFinite(constraint.minAltitude) &&
                isFinite(priorMinAltitude) && msfsSdk.MathUtils.round(constraint.minAltitude, 10) < msfsSdk.MathUtils.round(priorMinAltitude, 10)) {
                return true;
            }
            if (isFinite(constraint.maxAltitude) && ((isFinite(priorMinAltitude) && msfsSdk.MathUtils.round(constraint.maxAltitude, 10) < msfsSdk.MathUtils.round(priorMinAltitude, 10))
                || (isFinite(priorMaxAltitude) && msfsSdk.MathUtils.round(constraint.maxAltitude, 10) < msfsSdk.MathUtils.round(priorMaxAltitude, 10)))) {
                return true;
            }
            return false;
        }
        /**
         * A function which checks whether a descent constraint should be invalidated.
         * @param constraint A descent constraint.
         * @param index The index of the constraint to check if it is already in the vertical flight plan. If the constraint
         * is not already in the vertical flight plan, then this value equals `-(index + 1)`
         * if it is not, where `index`.
         * @param constraints The array of VNAV constraints currently in the vertical flight plan.
         * @param priorMinAltitude The most recent minimum altitude, in meters, defined by a VNAV constraint prior to the
         * constraint to check. Only prior constraints connected to the constraint to check by a contiguous sequence of
         * descent constraints are included.
         * @param priorMaxAltitude The most recent maximum altitude, in meters, defined by a VNAV constraint prior to the
         * constraint to check. Only prior constraints connected to the constraint to check by a contiguous sequence of
         * descent constraints are included.
         * @param requiredFpa The minimum flight path angle, in degrees, required to meet the maximum altitude of the
         * constraint to check, assuming a descent starting from the constraint defining the most recent prior minimum
         * altitude. Positive values indicate a descending path. If there is no required FPA because there is no defined
         * prior minimum altitude or maximum altitude for the constraint to check, or if the constraint to check is higher
         * than the prior minimum altitude, then this value will equal zero.
         * @param maxFpa The maximum allowed flight path angle, in degrees. Positive values indicate a descending path.
         * @returns Whether the specified descent constraint should be invalidated.
         */
        static invalidateDescentConstraint(constraint, index, constraints, priorMinAltitude, priorMaxAltitude, requiredFpa, maxFpa) {
            if (isFinite(constraint.minAltitude) && ((isFinite(priorMinAltitude) && msfsSdk.MathUtils.round(constraint.minAltitude, 10) > msfsSdk.MathUtils.round(priorMinAltitude, 10))
                || (isFinite(priorMaxAltitude) && msfsSdk.MathUtils.round(constraint.minAltitude, 10) > msfsSdk.MathUtils.round(priorMaxAltitude, 10)))) {
                return true;
            }
            if (isFinite(constraint.maxAltitude) &&
                isFinite(priorMaxAltitude) && msfsSdk.MathUtils.round(constraint.maxAltitude, 10) > msfsSdk.MathUtils.round(priorMaxAltitude, 10)) {
                return true;
            }
            return requiredFpa > maxFpa;
        }
        /**
         * Gets the VNAV TOD/BOD details for a vertical flight plan.
         * @param verticalPlan The vertical flight plan.
         * @param activeConstraintIndex The index of the VNAV constraint containing the active flight plan leg.
         * @param activeLegIndex The global index of the active flight plan leg.
         * @param distanceAlongLeg The along-track distance from the start of the active flight plan leg to the airplane's
         * position, in meters.
         * @param currentAltitude The current indicated altitude in meters.
         * @param currentVS The current vertical speed in meters per minute.
         * @param out The object to which to write the TOD/BOD details.
         * @returns The VNAV TOD/BOD details.
         */
        static getTodBodDetails(verticalPlan, activeConstraintIndex, activeLegIndex, distanceAlongLeg, currentAltitude, currentVS, out) {
            var _a;
            out.todLegIndex = -1;
            out.bodLegIndex = -1;
            out.todLegDistance = 0;
            out.distanceFromTod = 0;
            out.distanceFromBod = 0;
            out.bodConstraintIndex = -1;
            out.todConstraintIndex = -1;
            const activeConstraint = verticalPlan.constraints[activeConstraintIndex];
            // There is no TOD/BOD if...
            if (
            // ... there is no active VNAV constraint.
            !activeConstraint
                // ... the active constraint contains a VNAV-ineligible leg after the active leg.
                || ((activeConstraint === null || activeConstraint === void 0 ? void 0 : activeConstraint.nextVnavEligibleLegIndex) !== undefined && activeConstraint.nextVnavEligibleLegIndex > activeLegIndex)) {
                return out;
            }
            // Find the next BOD, which will be at the end of the earliest non-flat descent constraint subsequent to and
            // including the active constraint that ends in a level-off at a lower altitude than the aircraft's current
            // altitude. Note that we are guaranteed to not go through a VNAV discontinuity, since all constraints that end in
            // a discontinuity also end in a level-off.
            // lag altitude by ~3 seconds so that we aren't continuously pushing TOD in front of the plane while descending.
            const altitude = currentAltitude - currentVS / 20;
            let bodConstraintIndex = -1;
            let bodConstraint;
            for (let i = activeConstraintIndex; i >= 0; i--) {
                const constraint = verticalPlan.constraints[i];
                // If we encounter a climb constraint, skip it.
                if (constraint.type === 'climb' || constraint.type === 'missed') {
                    continue;
                }
                if (constraint.fpa > 0 && ((_a = constraint.legs[0]) === null || _a === void 0 ? void 0 : _a.isBod) && constraint.targetAltitude <= altitude) {
                    bodConstraintIndex = i;
                    bodConstraint = constraint;
                    break;
                }
            }
            if (!bodConstraint) {
                return out;
            }
            out.bodConstraintIndex = bodConstraintIndex;
            out.bodLegIndex = bodConstraint.index;
            // Find the TOD associated with the BOD. To do this, we need to first find the earliest non-flat descent constraint
            // between the active constraint and the BOD constraint (inclusive) that is connected to the BOD constraint with no
            // intervening flat constraints or VNAV path discontinuities and whose target altitude is less than the aircraft's
            // current altitude.
            let todConstraintIndex = bodConstraintIndex;
            for (let i = todConstraintIndex; i < verticalPlan.constraints.length; i++) {
                const prevConstraint = verticalPlan.constraints[i + 1];
                if (!prevConstraint
                    || prevConstraint.index < activeLegIndex
                    || prevConstraint.type === 'climb'
                    || prevConstraint.type === 'missed'
                    || prevConstraint.targetAltitude > altitude
                    || prevConstraint.fpa <= 0
                    || prevConstraint.isPathEnd) {
                    todConstraintIndex = i;
                    break;
                }
            }
            out.todConstraintIndex = todConstraintIndex;
            const todConstraint = verticalPlan.constraints[todConstraintIndex];
            // Now that we have found the TOD constraint, we need to find the TOD leg: the leg on which the TOD actually lies.
            // To do this, we calculate the along-track distance from the end of the TOD constraint to the TOD, then iterate
            // through the legs in the constraint backwards while keeping track of the total along-track distance covered by
            // each leg.
            let distance = msfsSdk.VNavUtils.distanceForAltitude(todConstraint.fpa, altitude - todConstraint.targetAltitude);
            let constraintIndex = todConstraintIndex;
            let todLegIndex = todConstraint.index;
            let todLegDistance = 0;
            let todLeg = todConstraint.legs[0];
            while (distance > 0 && constraintIndex < verticalPlan.constraints.length) {
                const constraint = verticalPlan.constraints[constraintIndex];
                // Remember that flight plan legs in a VNAV constraint appear in reverse order relative to how they are ordered
                // in the flight plan.
                for (let i = 0; i < constraint.legs.length; i++) {
                    if (!constraint.legs[i].isEligible) {
                        // We've encounted a VNAV-ineligible leg. Since we cannot calculate a vertical path through this leg, we have
                        // to stop iterating now so that the TOD gets set to the most recent VNAV-eligible leg.
                        constraintIndex = verticalPlan.constraints.length;
                        break;
                    }
                    todLeg = constraint.legs[i];
                    distance -= todLeg.distance;
                    if (distance <= 0) {
                        todLegIndex = constraint.index - i;
                        todLegDistance = todLeg.distance + distance;
                        break;
                    }
                }
                constraintIndex++;
            }
            if (distance > 0) {
                // If we still haven't found the TOD yet, set it to the beginning of the earliest VNAV leg that was iterated.
                todLegIndex = verticalPlan.segments[todLeg.segmentIndex].offset + todLeg.legIndex;
                todLegDistance = todLeg.distance;
            }
            out.todLegIndex = todLegIndex;
            out.todLegDistance = todLegDistance;
            // calculate distance to TOD/BOD
            let globalLegIndex = bodConstraint.index;
            let distanceToBOD = 0, distanceToTOD = 0;
            let hasReachedTOD = false;
            let isDone = false;
            for (let i = bodConstraintIndex; i < verticalPlan.constraints.length; i++) {
                const constraint = verticalPlan.constraints[i];
                for (let j = 0; j < constraint.legs.length; j++) {
                    const leg = constraint.legs[j];
                    if (globalLegIndex === todLegIndex) {
                        distanceToTOD -= todLegDistance;
                        hasReachedTOD = true;
                    }
                    if (globalLegIndex > activeLegIndex) {
                        distanceToBOD += leg.distance;
                        if (hasReachedTOD) {
                            distanceToTOD += leg.distance;
                        }
                    }
                    else if (globalLegIndex === activeLegIndex) {
                        distanceToBOD += leg.distance - distanceAlongLeg;
                        if (hasReachedTOD) {
                            distanceToTOD += leg.distance - distanceAlongLeg;
                            isDone = true;
                        }
                        else {
                            distanceToTOD -= distanceAlongLeg;
                        }
                    }
                    else {
                        if (hasReachedTOD) {
                            isDone = true;
                        }
                        else {
                            distanceToTOD -= leg.distance;
                        }
                    }
                    if (isDone) {
                        break;
                    }
                    else {
                        globalLegIndex--;
                    }
                }
                if (isDone) {
                    break;
                }
            }
            out.distanceFromBod = distanceToBOD;
            out.distanceFromTod = distanceToTOD;
            return out;
        }
        /**
         * Gets the VNAV TOC/BOC details for a vertical flight plan.
         * @param verticalPlan The vertical flight plan.
         * @param activeConstraintIndex The index of the vertical constraint containing the active flight plan leg.
         * @param activeLegIndex The index of the active flight plan leg.
         * @param distanceAlongLeg The distance the plane is along the active flight plan leg in meters.
         * @param currentGroundSpeed The current ground speed, in knots.
         * @param currentAltitude The current indicated altitude in meters.
         * @param currentVS The current vertical speed in meters per minute.
         * @param isMapr Whether to get TOC/BOC details for the missed approach.
         * @param out The object to which to write the TOC/BOC details.
         * @returns The VNAV TOC/BOC details.
         */
        static getTocBocDetails(verticalPlan, activeConstraintIndex, activeLegIndex, distanceAlongLeg, currentGroundSpeed, currentAltitude, currentVS, isMapr, out) {
            var _a, _b;
            out.bocLegIndex = -1;
            out.tocLegIndex = -1;
            out.tocLegDistance = 0;
            out.distanceFromBoc = 0;
            out.distanceFromToc = 0;
            out.tocConstraintIndex = -1;
            out.tocAltitude = -1;
            const activeConstraint = verticalPlan.constraints[activeConstraintIndex];
            // There is no BOC/TOC if there is no active VNAV constraint.
            if (!activeConstraint) {
                return out;
            }
            const constraintType = isMapr ? 'missed' : 'climb';
            // Find the TOC. To do this, we need to first find the earliest climb constraint subsequent to and including the
            // active constraint that has a maximum altitude (i.e. is an AT, AT OR BELOW, or BETWEEN constraint).
            let tocConstraintIndex, tocConstraint;
            for (let i = activeConstraintIndex; i >= 0; i--) {
                const constraint = verticalPlan.constraints[i];
                if (constraint.type === constraintType && isFinite(constraint.maxAltitude)) {
                    tocConstraintIndex = i;
                    tocConstraint = constraint;
                    break;
                }
            }
            // If there is no next TOC, there also can be no next BOC since the next BOC must follow the next TOC.
            if (!tocConstraint) {
                return out;
            }
            out.tocConstraintIndex = tocConstraintIndex;
            out.tocAltitude = tocConstraint.maxAltitude;
            // Calculate distance to TOC.
            const deltaAltitude = tocConstraint.maxAltitude - currentAltitude;
            const timeToTocMin = deltaAltitude / Math.max(0, currentVS);
            let distanceRemaining = currentGroundSpeed === 0 ? 0 : timeToTocMin * msfsSdk.UnitType.KNOT.convertTo(currentGroundSpeed, msfsSdk.UnitType.MPM);
            // Find the leg on which the TOC lies.
            const activeLeg = activeConstraint.legs[activeConstraint.index - activeLegIndex];
            let tocLegIndex;
            let currentConstraintIndex = activeConstraintIndex;
            let currentConstraint;
            let currentConstraintLegIndex = activeConstraint.index - activeLegIndex;
            let currentLeg = activeLeg;
            const activeLegDistanceRemaining = ((_a = activeLeg === null || activeLeg === void 0 ? void 0 : activeLeg.distance) !== null && _a !== void 0 ? _a : 0) - distanceAlongLeg;
            if (distanceRemaining > activeLegDistanceRemaining) {
                distanceRemaining -= activeLegDistanceRemaining;
                if (currentConstraintLegIndex <= 0) {
                    --currentConstraintIndex;
                }
                else {
                    currentLeg = activeConstraint.legs[--currentConstraintLegIndex];
                }
                while (currentConstraintIndex >= tocConstraintIndex) {
                    currentConstraint = verticalPlan.constraints[currentConstraintIndex];
                    currentLeg = currentConstraint.legs[currentConstraintLegIndex];
                    if (currentLeg !== undefined) {
                        if (distanceRemaining > currentLeg.distance) {
                            out.distanceFromToc += currentLeg.distance;
                            distanceRemaining -= currentLeg.distance;
                        }
                        else {
                            out.distanceFromToc += distanceRemaining;
                            tocLegIndex = currentConstraint.index - currentConstraintLegIndex;
                            distanceRemaining -= currentLeg.distance;
                            break;
                        }
                    }
                    if (currentConstraintLegIndex <= 0) {
                        --currentConstraintIndex;
                    }
                    else {
                        currentLeg = currentConstraint.legs[--currentConstraintLegIndex];
                    }
                }
            }
            else {
                out.distanceFromToc = distanceRemaining;
                tocLegIndex = activeLegIndex;
                distanceRemaining -= activeLegDistanceRemaining;
            }
            if (tocLegIndex === undefined) {
                // If we still haven't found the TOC yet, set it to the end of the last leg of the TOC constraint.
                out.tocLegIndex = tocConstraint.index;
                out.tocLegDistance = 0;
            }
            else {
                out.tocLegIndex = tocLegIndex;
                out.tocLegDistance = -distanceRemaining;
            }
            // Find the next BOC, which is located at the beginning of the earliest climb constraint subsequent to (and not
            // including) the TOC constraint with a maximum altitude greater than the TOC constraint. Additionally, the BOC
            // must not be separated from the TOC constraint by a constraint of the incorrect type.
            let lastClimbConstraintIndex = tocConstraintIndex;
            let bocConstraintIndex, bocConstraint;
            for (let i = tocConstraintIndex - 1; i >= 0; i--) {
                const constraint = verticalPlan.constraints[i];
                if (constraint.type !== constraintType) {
                    break;
                }
                if (constraint.maxAltitude > tocConstraint.maxAltitude) {
                    bocConstraintIndex = i;
                    bocConstraint = constraint;
                    break;
                }
                lastClimbConstraintIndex = i;
            }
            let bocDistanceStopConstraintIndex = undefined;
            if (bocConstraint) {
                out.bocLegIndex = bocConstraint.index - (bocConstraint.legs.length - 1);
                bocDistanceStopConstraintIndex = bocConstraintIndex;
            }
            else {
                // If we did not find a climb constraint subsequent to the TOC constraint with a maximum altitude greater than the
                // the TOC constraint, then the BOC will be located at the last climb constraint.
                const lastClimbConstraint = verticalPlan.constraints[lastClimbConstraintIndex];
                if (lastClimbConstraint && lastClimbConstraint.index + 1 < verticalPlan.length) {
                    out.bocLegIndex = lastClimbConstraint.index + 1;
                    bocDistanceStopConstraintIndex = lastClimbConstraintIndex - 1;
                }
            }
            // Calculate distance to BOC
            if (bocDistanceStopConstraintIndex !== undefined) {
                let distanceToEndOfActiveConstraint = ((_b = activeLeg === null || activeLeg === void 0 ? void 0 : activeLeg.distance) !== null && _b !== void 0 ? _b : 0) - distanceAlongLeg;
                for (let i = Math.min(activeConstraint.index - activeLegIndex, activeConstraint.legs.length) - 1; i >= 0; i--) {
                    distanceToEndOfActiveConstraint += activeConstraint.legs[i].distance;
                }
                out.distanceFromBoc = distanceToEndOfActiveConstraint;
                for (let i = activeConstraintIndex - 1; i > bocDistanceStopConstraintIndex; i--) {
                    out.distanceFromBoc += verticalPlan.constraints[i].distance;
                }
            }
            return out;
        }
        /**
         * Gets the along-track distance, in meters, from a point along the flight plan to the end of a VNAV constraint.
         * @param verticalPlan The vertical flight plan.
         * @param constraintIndex The index of the VNAV constraint to calculate the distance to.
         * @param globalLegIndex The global index of the flight plan leg along which the point to check lies.
         * @param distanceAlongLeg The distance, in meters, from the start of the flight plan leg to the point to check.
         * @returns The along-track distance, in meters, from the specified point to the end of the VNAV constraint.
         */
        static getDistanceToConstraint(verticalPlan, constraintIndex, globalLegIndex, distanceAlongLeg) {
            const currentConstraintIndex = constraintIndex;
            const currentConstraint = verticalPlan.constraints[currentConstraintIndex];
            if (currentConstraint === undefined) {
                return 0;
            }
            const constraintLegIndex = currentConstraint.index;
            const startIndex = Math.min(constraintLegIndex + 1, globalLegIndex);
            const endIndex = Math.max(constraintLegIndex, globalLegIndex - 1) + 1;
            let distance = 0;
            for (let i = 0; i < verticalPlan.segments.length; i++) {
                const segment = verticalPlan.segments[i];
                const end = Math.min(segment.legs.length, endIndex - segment.offset);
                for (let j = Math.max(0, startIndex - segment.offset); j < end; j++) {
                    const leg = segment.legs[j];
                    distance += leg.distance * (segment.offset + j < globalLegIndex ? -1 : 1);
                }
            }
            distance -= distanceAlongLeg;
            return distance;
        }
        // eslint-disable-next-line jsdoc/require-jsdoc
        static getPathDesiredAltitude(verticalPlan, pathConstraintIndex, arg3, arg4) {
            const pathConstraint = verticalPlan.constraints[pathConstraintIndex];
            const distance = arg4 === undefined
                ? arg3
                : GarminVNavUtils.getDistanceToConstraint(verticalPlan, pathConstraintIndex, arg3, arg4);
            return pathConstraint.targetAltitude
                + msfsSdk.VNavUtils.altitudeForDistance(pathConstraint.fpa, distance);
        }
    }

    /**
     * A computer that calculates Garmin vertical navigation guidance for an active flight plan.
     */
    class GarminVNavComputer {
        /**
         * Creates a new instance of GarminVNavComputer.
         * @param index The index of this computer.
         * @param bus The event bus.
         * @param flightPlanner The flight planner containing the flight plan for which this computer provides guidance.
         * @param calculator The VNAV path calculator providing the vertical flight path for which this computer provides
         * guidance.
         * @param apValues Autopilot values for the autopilot associated with this computer.
         * @param options Options with which to configure the computer.
         */
        constructor(index, bus, flightPlanner, calculator, apValues, options) {
            var _a, _b, _c;
            this.index = index;
            this.bus = bus;
            this.flightPlanner = flightPlanner;
            this.calculator = calculator;
            this.apValues = apValues;
            this.publisher = this.bus.getPublisher();
            this.isLNavIndexValid = false;
            this.simRate = msfsSdk.ConsumerValue.create(null, 1);
            this.isEnabled = true;
            this.state = msfsSdk.VNavState.Disabled;
            this.isActive = false;
            this.pathMode = msfsSdk.Subject.create(msfsSdk.VNavPathMode.None);
            this.planePos = new msfsSdk.GeoPoint(0, 0);
            this.currentAltitude = 0;
            this.currentGpsAltitude = 0;
            this.preselectedAltitude = 0;
            this.currentGroundSpeed = 0;
            this.currentVS = 0;
            this.trueTrack = 0;
            this.apSelectedAltitude = 0;
            this.apSelectedVs = 0;
            this.apLateralActiveMode = msfsSdk.APLateralModes.NONE;
            this.apVerticalActiveMode = msfsSdk.APVerticalModes.NONE;
            this.apVerticalArmedMode = msfsSdk.APVerticalModes.NONE;
            this.isVNavUnavailable = msfsSdk.Subject.create(false);
            this.isAwaitingAltCapture = false;
            this.altitudeToCapture = 0;
            this.isAltCaptured = false;
            this.capturedAltitude = 0;
            this.stagedIsClimbArmed = false;
            this.isClimbArmed = false;
            this.shouldActivateClimbMode = false;
            this.stagedIsAwaitingPathRearm = false;
            this.stagedPathRearmIndex = -1;
            this.isAwaitingPathRearm = false;
            this.pathRearmIndex = -1;
            this.isPathActivationInhibited = false;
            this.pathReactivationTimeRemaining = 0;
            this.pathReactivationDeviationStage = 'armed';
            /** The highest VNAV constraint altitude that appears in the primary flight plan. */
            this.highestConstraintAltitude = 0;
            /** The global index of the last leg in the last non-missed approach climb constraint in the primary flight plan. */
            this.lastClimbConstraintLegIndex = -1;
            /** The global index of the last leg in the first descent constraint in the primary flight plan. */
            this.firstDescentConstraintLegIndex = -1;
            this.activePathConstraintIndex = -1;
            this.todBodDetails = {
                todLegIndex: -1,
                bodLegIndex: -1,
                todLegDistance: 0,
                distanceFromTod: 0,
                distanceFromBod: 0,
                bodConstraintIndex: -1,
                todConstraintIndex: -1
            };
            this.tocBocDetails = {
                tocLegIndex: -1,
                bocLegIndex: -1,
                tocLegDistance: 0,
                distanceFromToc: 0,
                distanceFromBoc: 0,
                tocConstraintIndex: -1,
                tocAltitude: -1
            };
            this.todBodDetailsSubject = msfsSdk.ObjectSubject.create(Object.assign({}, this.todBodDetails));
            this.tocBocDetailsSubject = msfsSdk.ObjectSubject.create(Object.assign({}, this.tocBocDetails));
            // Subjects for each vnav var to be set
            this.vnavState = msfsSdk.Subject.create(msfsSdk.VNavState.Enabled_Inactive);
            this.pathAvailable = msfsSdk.Subject.create(false);
            this.currentConstraintLegIndex = msfsSdk.Subject.create(-1);
            this.vnavActiveConstraintLegIndex = msfsSdk.Subject.create(-1);
            this.targetAltitude = msfsSdk.Subject.create(null);
            this.fpa = msfsSdk.Subject.create(null);
            this.verticalDeviation = msfsSdk.Subject.create(null);
            this.requiredVS = msfsSdk.Subject.create(null);
            this.captureType = msfsSdk.Subject.create(msfsSdk.VNavAltCaptureType.None);
            this.cruiseAltitude = msfsSdk.Subject.create(0);
            this.vnavFlightPhase = msfsSdk.Subject.create(exports.GarminVNavFlightPhase.None);
            this.vnavTrackingPhase = msfsSdk.Subject.create(exports.GarminVNavTrackingPhase.None);
            this.currentAltitudeConstraintDetailsWorking = {
                type: msfsSdk.AltitudeRestrictionType.Unused,
                altitude: 0
            };
            this.currentAltitudeConstraintDetails = msfsSdk.Subject.create({ type: msfsSdk.AltitudeRestrictionType.Unused, altitude: 0 }, msfsSdk.VNavUtils.altitudeConstraintDetailsEquals);
            // LNAV Consumer Subjects
            this.lnavLegIndex = msfsSdk.ConsumerValue.create(null, 0);
            this.lnavLegDistanceAlong = msfsSdk.ConsumerValue.create(null, 0);
            this.lnavXtk = msfsSdk.ConsumerValue.create(null, 0);
            this.lnavDtk = msfsSdk.ConsumerValue.create(null, 0);
            this.lnavDataCdiScale = msfsSdk.ConsumerValue.create(null, 4);
            this.activateMaprState = msfsSdk.ConsumerValue.create(null, false);
            this.noVNavTae = msfsSdk.Subject.create(false);
            this.noVNavXtk = msfsSdk.Subject.create(false);
            // PATH Error Subjects
            this.pathArmedError = msfsSdk.Subject.create(false);
            this.pathBelowAircraft = msfsSdk.Subject.create(false);
            this.noPathThisLeg = msfsSdk.Subject.create(false);
            this.noPathConditionDisco = msfsSdk.Subject.create(false);
            this.noPathVectors = msfsSdk.Subject.create(false);
            this.checkAltSel = msfsSdk.Subject.create(false);
            this.withinOneMinuteTod = msfsSdk.Subject.create(false);
            this.withinFiveSecondsTod = msfsSdk.Subject.create(false);
            this.checkFplnAlt = msfsSdk.Subject.create(false);
            this.guidanceBuffer = msfsSdk.ArrayUtils.create(2, () => {
                return {
                    state: msfsSdk.VNavState.Disabled,
                    isActive: false,
                    pathMode: msfsSdk.VNavPathMode.None,
                    armedClimbMode: msfsSdk.APVerticalModes.NONE,
                    shouldActivateClimbMode: false,
                    altitudeCaptureType: msfsSdk.VNavAltCaptureType.None,
                    shouldCaptureAltitude: false,
                    altitudeToCapture: 0,
                    approachHasGlidepath: false
                };
            });
            this._guidance = msfsSdk.Subject.create(this.guidanceBuffer[0], (a, b) => {
                return a.state === b.state
                    && a.isActive === b.isActive
                    && a.pathMode === b.pathMode
                    && a.armedClimbMode === b.armedClimbMode
                    && a.shouldActivateClimbMode === b.shouldActivateClimbMode
                    && a.altitudeCaptureType === b.altitudeCaptureType
                    && a.shouldCaptureAltitude === b.shouldCaptureAltitude
                    && a.altitudeToCapture === b.altitudeToCapture;
            });
            /** The VNAV guidance calculated by this computer. */
            this.guidance = this._guidance;
            this.pathGuidanceBuffer = msfsSdk.ArrayUtils.create(2, () => {
                return {
                    isValid: false,
                    fpa: 0,
                    deviation: 0
                };
            });
            this._pathGuidance = msfsSdk.Subject.create(this.pathGuidanceBuffer[0], (a, b) => {
                if (!a.isValid && !b.isValid) {
                    return true;
                }
                return a.isValid === b.isValid
                    && a.fpa === b.fpa
                    && a.deviation === b.deviation;
            });
            /** The vertical path guidance calculated by this computer. */
            this.pathGuidance = this._pathGuidance;
            if (!msfsSdk.VNavUtils.isValidVNavIndex(index)) {
                throw new Error(`GarminVNavComputer: invalid index (${index}) specified (must be a non-negative integer)`);
            }
            const simVarSuffix = this.index === 0 ? '' : `:${this.index}`;
            this.simVarMap = {};
            for (const simVar of Object.values(msfsSdk.VNavVars)) {
                this.simVarMap[simVar] = `${simVar}${simVarSuffix}`;
            }
            const eventBusTopicSuffix = msfsSdk.VNavUtils.getEventBusTopicSuffix(this.index);
            this.vnavTopicMap = {
                // VNAV events
                'vnav_path_calculated': `vnav_path_calculated${eventBusTopicSuffix}`,
                'vnav_availability': `vnav_availability${eventBusTopicSuffix}`,
                'vnav_altitude_constraint_details': `vnav_altitude_constraint_details${eventBusTopicSuffix}`,
                'vnav_is_enabled': `vnav_is_enabled${eventBusTopicSuffix}`,
                // VNAV data events
                'vnav_cruise_altitude': `vnav_cruise_altitude${eventBusTopicSuffix}`,
                'vnav_flight_phase': `vnav_flight_phase${eventBusTopicSuffix}`,
                'vnav_tracking_phase': `vnav_tracking_phase${eventBusTopicSuffix}`,
                'vnav_active_constraint_global_leg_index': `vnav_active_constraint_global_leg_index${eventBusTopicSuffix}`,
                // VNAV control events
                'vnav_set_state': `vnav_set_state${eventBusTopicSuffix}`,
            };
            this.lnavIndex = msfsSdk.SubscribableUtils.toSubscribable((_a = options === null || options === void 0 ? void 0 : options.lnavIndex) !== null && _a !== void 0 ? _a : 0, true);
            this.primaryPlanIndex = (_b = options === null || options === void 0 ? void 0 : options.primaryPlanIndex) !== null && _b !== void 0 ? _b : 0;
            this.enableAdvancedVNav = (_c = options === null || options === void 0 ? void 0 : options.enableAdvancedVNav) !== null && _c !== void 0 ? _c : false;
            const sub = this.bus.getSubscriber();
            this.lnavIndex.sub(lnavIndex => {
                this.isLNavIndexValid = msfsSdk.LNavUtils.isValidLNavIndex(lnavIndex);
                if (this.isLNavIndexValid) {
                    const suffix = msfsSdk.LNavUtils.getEventBusTopicSuffix(lnavIndex);
                    this.lnavLegIndex.setConsumer(sub.on(`lnav_tracked_leg_index${suffix}`));
                    this.lnavLegDistanceAlong.setConsumer(sub.on(`lnav_leg_distance_along${suffix}`));
                    this.lnavXtk.setConsumer(sub.on(`lnav_xtk${suffix}`));
                    this.lnavDtk.setConsumer(sub.on(`lnav_dtk${suffix}`));
                    this.lnavDataCdiScale.setConsumer(sub.on(`lnavdata_cdi_scale${suffix}`));
                    this.activateMaprState.setConsumer(sub.on(`activate_missed_approach${suffix}`));
                }
                else {
                    this.lnavLegIndex.setConsumer(null);
                    this.lnavLegDistanceAlong.setConsumer(null);
                    this.lnavXtk.setConsumer(null);
                    this.lnavDtk.setConsumer(null);
                    this.lnavDataCdiScale.setConsumer(null);
                    this.activateMaprState.setConsumer(null);
                }
            }, true);
            sub.on('ap_altitude_selected').handle(selected => this.preselectedAltitude = selected);
            sub.on('indicated_alt').handle(alt => this.currentAltitude = alt);
            sub.on('vertical_speed').whenChangedBy(1).handle(vs => this.currentVS = vs);
            sub.on('track_deg_true').whenChangedBy(1).handle(trueTrack => this.trueTrack = trueTrack);
            this.simRate.setConsumer(sub.on('simRate'));
            sub.on('gps-position').handle(lla => {
                this.planePos.set(lla.lat, lla.long);
                this.currentGpsAltitude = msfsSdk.UnitType.METER.convertTo(lla.alt, msfsSdk.UnitType.FOOT);
            });
            sub.on('ground_speed').handle(gs => this.currentGroundSpeed = gs);
            this.publisher.pub(this.vnavTopicMap['vnav_is_enabled'], this.isEnabled, true, true);
            sub.on(this.vnavTopicMap['vnav_set_state']).handle(this.setEnabled.bind(this));
            // Update the highest constraint altitude in the flight plan (excluding the missed approach)
            this.calculator.planBuilt.on((sender, planIndex) => {
                var _a;
                if (planIndex === this.primaryPlanIndex) {
                    const verticalPlan = this.calculator.getVerticalFlightPlan(planIndex);
                    const missedApproachStartIndex = (_a = verticalPlan.missedApproachStartIndex) !== null && _a !== void 0 ? _a : Infinity;
                    this.highestConstraintAltitude = verticalPlan.constraints.reduce((highestAlt, constraint) => {
                        if (constraint.index >= missedApproachStartIndex) {
                            return highestAlt;
                        }
                        return Math.max(highestAlt, isFinite(constraint.minAltitude) ? constraint.minAltitude : 0, isFinite(constraint.maxAltitude) ? constraint.maxAltitude : 0);
                    }, 0);
                }
            });
            // Update last climb/first descent constraint indexes
            this.calculator.vnavCalculated.on((sender, planIndex) => {
                var _a;
                const verticalPlan = this.calculator.getVerticalFlightPlan(planIndex);
                const missedApproachStartIndex = (_a = verticalPlan.missedApproachStartIndex) !== null && _a !== void 0 ? _a : Infinity;
                const verticalPlanConstraints = verticalPlan.constraints;
                this.lastClimbConstraintLegIndex = -1;
                let lastClimbConstraintIndex = -1;
                for (let i = 0; i < verticalPlanConstraints.length; i++) {
                    const constraint = verticalPlanConstraints[i];
                    if (constraint.index < missedApproachStartIndex && constraint.type === 'climb') {
                        this.lastClimbConstraintLegIndex = constraint.index;
                        lastClimbConstraintIndex = i;
                        break;
                    }
                }
                this.firstDescentConstraintLegIndex = -1;
                for (let i = (lastClimbConstraintIndex < 0 ? verticalPlanConstraints.length : lastClimbConstraintIndex) - 1; i >= 0; i--) {
                    const constraint = verticalPlanConstraints[i];
                    if ((constraint.type === 'descent' || constraint.type === 'manual' || constraint.type === 'direct') && constraint.fpa > 0) {
                        this.firstDescentConstraintLegIndex = constraint.index;
                        break;
                    }
                }
            });
            // Publish TOD/BOD and BOC/TOC details.
            this.todBodDetailsSubject.sub(this.publishTodBodDetails.bind(this), true);
            this.tocBocDetailsSubject.sub(this.publishBocTocDetails.bind(this), true);
            this.isVNavUnavailable.sub(v => {
                this.publisher.pub(this.vnavTopicMap['vnav_availability'], v ? msfsSdk.VNavAvailability.InvalidLegs : msfsSdk.VNavAvailability.Available, true, true);
            }, true);
            this.monitorVars();
            this.monitorMessages();
            this.setState(msfsSdk.VNavState.Enabled_Inactive);
        }
        /**
         * Attempts to activate VNAV.
         */
        tryActivate() {
            if (this.state !== msfsSdk.VNavState.Enabled_Active) {
                if (this.enableAdvancedVNav || this.calculator.getFlightPhase(this.primaryPlanIndex) === msfsSdk.VerticalFlightPhase.Descent) {
                    this.state = msfsSdk.VNavState.Enabled_Active;
                    SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.VNAVState], msfsSdk.SimVarValueType.Number, this.state);
                    this.pathMode.set(msfsSdk.VNavPathMode.None);
                }
            }
        }
        /**
         * Attempts to deactivate VNAV.
         */
        tryDeactivate() {
            if (this.state === msfsSdk.VNavState.Enabled_Active) {
                this.state = msfsSdk.VNavState.Enabled_Inactive;
                SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.VNAVState], msfsSdk.SimVarValueType.Number, this.state);
                this.disarmPath();
                this.disarmClimb();
                this.cancelAltCap();
                this.isAltCaptured = false;
                this.isAwaitingPathRearm = false;
                this.pathRearmIndex = -1;
                this.clearAllMessages();
            }
        }
        /**
         * Sets whether VNAV is enabled.
         * @param enabled Whether VNAV is enabled.
         */
        setEnabled(enabled) {
            if (this.isEnabled === enabled) {
                return;
            }
            this.isEnabled = enabled;
            this.publisher.pub(this.vnavTopicMap['vnav_is_enabled'], enabled, true, true);
        }
        /**
         * Sets this computer's VNAV state.
         * @param vnavState The state to set.
         */
        setState(vnavState) {
            if (vnavState !== this.state) {
                this.state = vnavState;
                switch (this.state) {
                    case msfsSdk.VNavState.Disabled:
                        this.disarmPath();
                        this.disarmClimb();
                        this.cancelAltCap();
                        this.isAltCaptured = false;
                        this.isAwaitingPathRearm = false;
                        this.pathRearmIndex = -1;
                        this.clearAllMessages();
                        break;
                    case msfsSdk.VNavState.Enabled_Active:
                        this.tryActivate();
                        break;
                    case msfsSdk.VNavState.Enabled_Inactive:
                        this.tryDeactivate();
                        break;
                }
                SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.VNAVState], msfsSdk.SimVarValueType.Number, this.state);
            }
        }
        /**
         * Applies the failed state to this computer's VNAV calculations.
         */
        failVNav() {
            this.isActive = false;
            this.disarmPath();
            this.disarmClimb();
            this.resetVNavTrackingVars();
            this.resetTodBodVars();
            this.resetTocBocVars();
            this.activePathConstraintIndex = -1;
            this.isAwaitingPathRearm = false;
            this.pathRearmIndex = -1;
        }
        /**
         * Activates altitude capture mode.
         * @param altitude The altitude to capture, in feet.
         * @param flightPhase The vertical flight phase for which to arm a vertical mode once the altitude has been
         * captured, or `undefined` if no vertical mode should be armed. If the flight phase is `VerticalFlightPhase.Climb`,
         * then FLC will be armed. If the flight phase is `VerticalFlightPhase.Descent`, then PATH will be armed.
         * @param pathRearmIndex The global flight plan leg index at which PATH mode can be armed. Ignored if `flightPhase`
         * is not `VerticalFlightPhase.Descent`. Defaults to the index of the leg after the active flight plan leg at the
         * time the method is called.
         */
        activateAltCap(altitude, flightPhase, pathRearmIndex) {
            this.isAwaitingAltCapture = true;
            this.altitudeToCapture = Math.round(altitude);
            if (flightPhase !== undefined) {
                switch (flightPhase) {
                    case msfsSdk.VerticalFlightPhase.Climb:
                        this.stagedIsClimbArmed = true;
                        break;
                    case msfsSdk.VerticalFlightPhase.Descent:
                        this.stagedIsAwaitingPathRearm = true;
                        this.stagedPathRearmIndex = pathRearmIndex !== null && pathRearmIndex !== void 0 ? pathRearmIndex : this.lnavLegIndex.get() + 1;
                        break;
                }
            }
            else {
                this.stagedIsClimbArmed = false;
                this.stagedIsAwaitingPathRearm = false;
                this.stagedPathRearmIndex = -1;
            }
        }
        /**
         * Cancels altitude capture mode.
         */
        cancelAltCap() {
            this.isAwaitingAltCapture = false;
            this.altitudeToCapture = 0;
            this.stagedIsAwaitingPathRearm = false;
            this.stagedPathRearmIndex = -1;
        }
        /**
         * Disarms climb (FLC) mode.
         */
        disarmClimb() {
            this.stagedIsClimbArmed = false;
            this.isClimbArmed = false;
            this.shouldActivateClimbMode = false;
        }
        /**
         * Arms PATH mode.
         */
        armPath() {
            if (this.pathMode.get() !== msfsSdk.VNavPathMode.PathArmed) {
                this.pathMode.set(msfsSdk.VNavPathMode.PathArmed);
            }
            this.isAltCaptured = false;
            this.isAwaitingPathRearm = false;
            this.pathRearmIndex = -1;
        }
        /**
         * Activates PATH mode.
         */
        activatePath() {
            if (this.pathMode.get() !== msfsSdk.VNavPathMode.PathActive) {
                this.pathMode.set(msfsSdk.VNavPathMode.PathActive);
            }
            this.resetPathReactivationInhibit();
            this.isAwaitingPathRearm = false;
            this.pathRearmIndex = -1;
        }
        /**
         * Deactivates and disarms PATH mode.
         */
        disarmPath() {
            if (this.pathMode.get() !== msfsSdk.VNavPathMode.None) {
                this.pathMode.set(msfsSdk.VNavPathMode.None);
                this.isAltCaptured = false;
                this.checkAltSel.set(false);
                this.resetPathReactivationInhibit();
            }
        }
        /**
         * Resets this computer's PATH mode re-activation inhibit state.
         */
        resetPathReactivationInhibit() {
            this.isPathActivationInhibited = false;
            this.pathReactivationTimeRemaining = 0;
            this.pathReactivationDeviationStage = 'armed';
        }
        /**
         * Updates this computer.
         */
        update() {
            const realTime = Date.now();
            const dt = this.lastUpdateTime === undefined ? 0 : Math.max((realTime - this.lastUpdateTime) * this.simRate.get(), 0);
            this.lastUpdateTime = realTime;
            this.updateAPValues();
            // Update cruise altitude
            this.cruiseAltitude.set(msfsSdk.MathUtils.round(Math.max(this.currentAltitude, this.preselectedAltitude, this.highestConstraintAltitude), 10));
            if (!this.isLNavIndexValid || !this.flightPlanner.hasFlightPlan(this.primaryPlanIndex)) {
                this.failVNav();
                this.resetPathReactivationInhibit();
                this.resetVNavPhase();
                this.resetVNavConstraintVars();
                this.updateGuidance();
                this.updatePathGuidance();
                return;
            }
            const lateralPlan = this.flightPlanner.getFlightPlan(this.primaryPlanIndex);
            const verticalPlan = this.calculator.getVerticalFlightPlan(this.primaryPlanIndex);
            const alongLegDistance = msfsSdk.UnitType.NMILE.convertTo(this.lnavLegDistanceAlong.get(), msfsSdk.UnitType.METER);
            const lateralLegIndex = this.lnavLegIndex.get();
            if (lateralPlan.length > 0
                && lateralLegIndex < lateralPlan.length
                && msfsSdk.VNavUtils.verticalPlanHasLeg(verticalPlan, lateralLegIndex)) {
                if (this.apVerticalActiveMode === msfsSdk.APVerticalModes.GS || this.apVerticalActiveMode === msfsSdk.APVerticalModes.GP) {
                    this.failVNav();
                    this.resetVNavPhase();
                    this.resetVNavConstraintVars();
                }
                else {
                    const currentConstraintIndex = msfsSdk.VNavUtils.getConstraintIndexFromLegIndex(verticalPlan, lateralLegIndex);
                    const currentConstraint = verticalPlan.constraints[currentConstraintIndex];
                    const firstDescentConstraintIndex = this.firstDescentConstraintLegIndex < 0
                        ? -1
                        : msfsSdk.VNavUtils.getConstraintIndexFromLegIndex(verticalPlan, this.firstDescentConstraintLegIndex);
                    const isInDeparture = lateralPlan.getSegment(lateralPlan.getSegmentIndex(lateralLegIndex)).segmentType === msfsSdk.FlightPlanSegmentType.Departure;
                    const isInMapr = this.activateMaprState.get() || msfsSdk.BitFlags.isAll(lateralPlan.getLeg(lateralLegIndex).flags, msfsSdk.LegDefinitionFlags.MissedApproach);
                    if (this.currentGroundSpeed >= 30) {
                        this.updateTrackErrorState();
                        const trackError = this.noVNavTae.get() || this.noVNavXtk.get();
                        // If there is no active constraint (meaning we've passed the last constraint in the flight plan or there are no
                        // constraints in the flight plan), then switch to FLC mode if it is armed.
                        if (!currentConstraint
                            && this.isClimbArmed
                            && this.apSelectedAltitude > this.capturedAltitude) {
                            this.cancelAltCap();
                            this.isAltCaptured = false;
                            this.capturedAltitude = Number.POSITIVE_INFINITY;
                            this.shouldActivateClimbMode = true;
                        }
                        if (!this.isEnabled || !currentConstraint) {
                            this.isActive = false;
                            !this.isEnabled && this.disarmClimb();
                            this.disarmPath();
                            this.resetVNavConstraintVars();
                            this.resetVNavTrackingVars();
                            this.resetTodBodVars();
                            this.resetTocBocVars();
                            this.activePathConstraintIndex = -1;
                            this.isAwaitingPathRearm = false;
                            this.pathRearmIndex = -1;
                            // Need to handle phase logic if VNAV is not disabled
                            if (this.isEnabled) {
                                const hasClimbConstraint = this.lastClimbConstraintLegIndex >= 0;
                                const hasDescentConstraint = this.firstDescentConstraintLegIndex >= 0;
                                if (!hasClimbConstraint && !hasDescentConstraint) {
                                    // There are no constraints in the flight plan.
                                    this.resetVNavPhase();
                                }
                                else {
                                    if (isInDeparture) {
                                        // We are in the departure (SID).
                                        this.vnavFlightPhase.set(exports.GarminVNavFlightPhase.Climb);
                                        this.vnavTrackingPhase.set(hasDescentConstraint ? exports.GarminVNavTrackingPhase.Descent : exports.GarminVNavTrackingPhase.Climb);
                                    }
                                    else if (hasDescentConstraint) {
                                        // There is at least one descent constraint in the flight plan and we must be past it.
                                        this.vnavFlightPhase.set(exports.GarminVNavFlightPhase.Descent);
                                        this.vnavTrackingPhase.set(isInMapr ? exports.GarminVNavTrackingPhase.MissedApproach : exports.GarminVNavTrackingPhase.Descent);
                                    }
                                    else {
                                        // There are no descent constraints in the flight plan but at least one climb constraint and we must be past it.
                                        if (this.currentAltitude >= this.cruiseAltitude.get() - GarminVNavComputer.CRUISE_PHASE_ALTITUDE_THRESHOLD) {
                                            this.vnavFlightPhase.set(exports.GarminVNavFlightPhase.Cruise);
                                            this.vnavTrackingPhase.set(isInMapr ? exports.GarminVNavTrackingPhase.MissedApproach : exports.GarminVNavTrackingPhase.Cruise);
                                        }
                                        else {
                                            this.vnavFlightPhase.set(exports.GarminVNavFlightPhase.Climb);
                                            this.vnavTrackingPhase.set(isInMapr ? exports.GarminVNavTrackingPhase.MissedApproach : exports.GarminVNavTrackingPhase.Climb);
                                        }
                                    }
                                }
                            }
                            else {
                                this.resetVNavPhase();
                            }
                        }
                        else {
                            const currentAltitudeMetric = msfsSdk.UnitType.FOOT.convertTo(this.currentAltitude, msfsSdk.UnitType.METER);
                            const currentVSMetric = msfsSdk.UnitType.FPM.convertTo(this.currentVS, msfsSdk.UnitType.MPM);
                            // Compute TOD/BOD and (if supported) TOC/BOC details.
                            const todBodDetails = GarminVNavUtils.getTodBodDetails(verticalPlan, currentConstraintIndex, lateralLegIndex, alongLegDistance, currentAltitudeMetric, currentVSMetric, this.todBodDetails);
                            // Update VNAV flight and tracking phases.
                            const isPastTod = this.firstDescentConstraintLegIndex >= 0 && firstDescentConstraintIndex >= 0 // There is at least one descent constraint and...
                                && (
                                // ... we are past the first descent constraint...
                                lateralLegIndex > this.firstDescentConstraintLegIndex
                                    // ... or we are within threshold distance of TOD
                                    || (todBodDetails.todLegIndex >= 0 && todBodDetails.distanceFromTod <= GarminVNavComputer.DESCENT_PHASE_TOD_DISTANCE)
                                    // ... or we are within threshold distance of the first descent constraint
                                    || (GarminVNavUtils.getDistanceToConstraint(verticalPlan, firstDescentConstraintIndex, lateralLegIndex, alongLegDistance) <= GarminVNavComputer.DESCENT_PHASE_TOD_DISTANCE));
                            const isInCruise = lateralLegIndex > this.lastClimbConstraintLegIndex // We have sequenced all climb constraints and...
                                // ... we are within threshold vertical distance of the cruise altitude
                                && this.currentAltitude >= this.cruiseAltitude.get() - GarminVNavComputer.CRUISE_PHASE_ALTITUDE_THRESHOLD;
                            // VNAV flight phase:
                            // Climb: in departure segment or airplane has not yet sequenced all climb constraints (excluding the ones in
                            //        the missed approach) or airplane is more than 500 feet below the cruise altitude.
                            // Cruise: airplane is not in the departure segment and has sequenced all climb constraints (excluding the ones
                            //         in the missed approach) and the airplane is higher than 500 feet below the cruise altitude.
                            // Descent: airplane is at or past 10 NM to go to the first TOD in the flight plan.
                            if (isInDeparture) {
                                this.vnavFlightPhase.set(exports.GarminVNavFlightPhase.Climb);
                            }
                            else if (isPastTod) {
                                this.vnavFlightPhase.set(exports.GarminVNavFlightPhase.Descent);
                            }
                            else if (isInCruise) {
                                this.vnavFlightPhase.set(exports.GarminVNavFlightPhase.Cruise);
                            }
                            else {
                                this.vnavFlightPhase.set(exports.GarminVNavFlightPhase.Climb);
                            }
                            // VNAV tracking phase (determines whether VNAV will track climb or descent):
                            if (this.enableAdvancedVNav) {
                                // Climb: airplane is not past 10 NM to go to the first TOD in the flight plan and has not sequenced all
                                //        climb constraints or is lower than 500 feet below the cruise altitude.
                                // Cruise: airplane has sequenced all climb constraints (excluding the ones in the missed approach) and the
                                //         airplane is higher than 500 feet below the cruise altitude.
                                // Descent: airplane is at or past 10 NM to go to the first TOD in the flight plan and is not in the missed
                                //          approach.
                                // Missed approach: airplane is in the missed approach.
                                if (isInMapr) {
                                    this.vnavTrackingPhase.set(exports.GarminVNavTrackingPhase.MissedApproach);
                                }
                                else if (isPastTod) {
                                    this.vnavTrackingPhase.set(exports.GarminVNavTrackingPhase.Descent);
                                }
                                else if (isInCruise) {
                                    this.vnavTrackingPhase.set(exports.GarminVNavTrackingPhase.Cruise);
                                }
                                else {
                                    this.vnavTrackingPhase.set(exports.GarminVNavTrackingPhase.Climb);
                                }
                            }
                            else {
                                // Cruise: default phase.
                                // Descent: airplane is at or past 10 NM to go to the first TOD in the flight plan.
                                if (isPastTod) {
                                    this.vnavTrackingPhase.set(exports.GarminVNavTrackingPhase.Descent);
                                }
                                else {
                                    this.vnavTrackingPhase.set(exports.GarminVNavTrackingPhase.Cruise);
                                }
                            }
                            const vnavTrackingPhase = this.vnavTrackingPhase.get();
                            const inClimb = vnavTrackingPhase === exports.GarminVNavTrackingPhase.Climb || vnavTrackingPhase === exports.GarminVNavTrackingPhase.MissedApproach;
                            this.currentConstraintLegIndex.set(currentConstraint.index);
                            // Find the constraint matching the current tracking phase that applies to the active flight plan leg.
                            let currentPhaseConstraintIndex;
                            if (isInMapr) {
                                currentPhaseConstraintIndex = currentConstraint.type === 'missed'
                                    ? currentConstraintIndex
                                    : msfsSdk.VNavUtils.getNextMaprConstraintIndex(verticalPlan, lateralLegIndex);
                            }
                            else if (inClimb) {
                                currentPhaseConstraintIndex = currentConstraint.type === 'climb'
                                    ? currentConstraintIndex
                                    : msfsSdk.VNavUtils.getNextClimbConstraintIndex(verticalPlan, lateralLegIndex);
                            }
                            else {
                                currentPhaseConstraintIndex = currentConstraint.type !== 'climb' && currentConstraint.type !== 'missed'
                                    ? currentConstraintIndex
                                    : msfsSdk.VNavUtils.getNextDescentConstraintIndex(verticalPlan, lateralLegIndex);
                            }
                            const currentPhaseConstraint = verticalPlan.constraints[currentPhaseConstraintIndex];
                            if (trackError) {
                                // If one or more LNAV track error limits have been exceeded, reset all vertical tracking data but still
                                // publish constraint data (current + active constraint and FPA).
                                this.isActive = false;
                                this.disarmClimb();
                                this.disarmPath();
                                this.resetVNavTrackingVars();
                                this.resetTodBodVars();
                                this.resetTocBocVars();
                                if (inClimb) {
                                    // The active constraint is the current phase constraint.
                                    this.vnavActiveConstraintLegIndex.set(currentPhaseConstraint === undefined ? -1 : currentPhaseConstraint.index);
                                    this.fpa.set(null);
                                }
                                else {
                                    // The active constraint and FPA are based on the last known active path constraint, if one exists and does
                                    // not precede (in flight plan order) the current constraint (the constraint containing the active flight
                                    // plan leg). Otherwise, they are based on the current phase constraint.
                                    const activeConstraintIndex = this.activePathConstraintIndex < 0 || this.activePathConstraintIndex > currentConstraintIndex
                                        ? currentPhaseConstraintIndex
                                        : this.activePathConstraintIndex;
                                    const activeConstraint = verticalPlan.constraints[activeConstraintIndex];
                                    if (activeConstraint) {
                                        this.vnavActiveConstraintLegIndex.set(activeConstraint.index);
                                        this.fpa.set(activeConstraint.fpa);
                                    }
                                    else {
                                        this.vnavActiveConstraintLegIndex.set(-1);
                                        this.fpa.set(null);
                                    }
                                }
                            }
                            else {
                                // If we are in the climb or missed approach tracking phases and advanced VNAV is supported, calculate
                                // TOC/BOC details. Otherwise blank the TOC/BOC details.
                                const tocBocDetails = this.tocBocDetails;
                                if (inClimb && this.enableAdvancedVNav) {
                                    GarminVNavUtils.getTocBocDetails(verticalPlan, currentConstraintIndex, lateralLegIndex, alongLegDistance, this.currentGroundSpeed, currentAltitudeMetric, currentVSMetric, isInMapr, this.tocBocDetails);
                                    // If TOC and BOC are defined, check if the airplane's current altitude is above the BOC suppression threshold
                                    // and suppress the BOC as appropriate.
                                    if (tocBocDetails.tocLegIndex >= 0
                                        && tocBocDetails.bocLegIndex >= 0
                                        && currentAltitudeMetric > tocBocDetails.tocAltitude + GarminVNavComputer.BOC_SUPPRESS_ALTITUDE_THRESHOLD) {
                                        tocBocDetails.bocLegIndex = -1;
                                        tocBocDetails.distanceFromBoc = 0;
                                    }
                                    // If TOC is defined, check if the airplane's current altitude is above the TOC suppression threshold and
                                    // suppress the TOC as appropriate.
                                    if (tocBocDetails.tocLegIndex >= 0
                                        && currentAltitudeMetric >= tocBocDetails.tocAltitude - GarminVNavComputer.TOC_SUPPRESS_ALTITUDE_THRESHOLD) {
                                        tocBocDetails.tocLegIndex = -1;
                                        tocBocDetails.distanceFromToc = 0;
                                        tocBocDetails.tocLegDistance = 0;
                                        tocBocDetails.tocConstraintIndex = -1;
                                        tocBocDetails.tocAltitude = -1;
                                    }
                                }
                                else {
                                    GarminVNavUtils.getTocBocDetails(verticalPlan, -1, lateralLegIndex, alongLegDistance, this.currentGroundSpeed, currentAltitudeMetric, currentVSMetric, isInMapr, this.tocBocDetails);
                                }
                                // Publish TOD/BOD and TOC/BOC data.
                                this.manageTodBodTocBocDetails(lateralLegIndex, todBodDetails, tocBocDetails);
                                let activeConstraintIndex = -1;
                                let activePathConstraintIndex = -1;
                                let timeToTodSeconds;
                                // If we are in a descent and have a valid TOD (i.e. there exists a valid descent path that is capturable by
                                // V PATH), then we need to designate the active path constraint. If we are within 1 min of the TOD or past
                                // the TOD, then the active path constraint is the constraint defining the descent path that the TOD lies on.
                                // Otherwise, the active path constraint is the current constraint (the constraint containing the active
                                // flight plan leg).
                                if (!inClimb && todBodDetails.todConstraintIndex >= 0) {
                                    timeToTodSeconds = msfsSdk.UnitType.METER.convertTo(todBodDetails.distanceFromTod, msfsSdk.UnitType.NMILE) / this.currentGroundSpeed * 3600;
                                    if (currentConstraintIndex <= todBodDetails.todConstraintIndex) {
                                        activePathConstraintIndex = currentConstraintIndex;
                                    }
                                    else {
                                        const activePathTodTimeThreshold = GarminVNavComputer.ACTIVE_PATH_TOD_TIME_THRESHOLD
                                            + (this.activePathConstraintIndex === todBodDetails.todConstraintIndex ? GarminVNavComputer.ACTIVE_PATH_TOD_TIME_HYSTERESIS : 0);
                                        if (timeToTodSeconds <= activePathTodTimeThreshold) {
                                            activePathConstraintIndex = todBodDetails.todConstraintIndex;
                                        }
                                    }
                                }
                                this.activePathConstraintIndex = activePathConstraintIndex;
                                // If we are tracking a climb or there is no active path constraint, the current phase constraint is the active
                                // constraint; otherwise the active path constraint is the active constraint.
                                if (inClimb || activePathConstraintIndex < 0) {
                                    if (currentPhaseConstraint) {
                                        activeConstraintIndex = currentPhaseConstraintIndex;
                                        this.vnavActiveConstraintLegIndex.set(currentPhaseConstraint.index);
                                    }
                                    else {
                                        activeConstraintIndex = -1;
                                        this.vnavActiveConstraintLegIndex.set(-1);
                                    }
                                }
                                else {
                                    activeConstraintIndex = activePathConstraintIndex;
                                    this.vnavActiveConstraintLegIndex.set(verticalPlan.constraints[activePathConstraintIndex].index);
                                }
                                if (activePathConstraintIndex >= 0) {
                                    this.disarmClimb();
                                }
                                this.isActive = this.state === msfsSdk.VNavState.Enabled_Active;
                                if (!inClimb && !this.isClimbArmed) {
                                    // If there is no active descent constraint, then VNAV remains inactive and PATH cannot be armed.
                                    if (activeConstraintIndex < 0) {
                                        this.isActive = false;
                                        this.disarmPath();
                                    }
                                    const activeConstraint = verticalPlan.constraints[activeConstraintIndex];
                                    this.fpa.set(activeConstraint === undefined ? null : activeConstraint.fpa);
                                    this.setCurrentConstraintDetails(verticalPlan, activeConstraintIndex, lateralLegIndex);
                                    this.pathAvailable.set(true);
                                    this.trackDescent(dt, verticalPlan, lateralPlan, todBodDetails, activeConstraintIndex, activePathConstraintIndex);
                                }
                                else if (this.enableAdvancedVNav && (inClimb || this.isClimbArmed)) {
                                    this.disarmPath();
                                    this.fpa.set(null);
                                    this.setCurrentConstraintDetails(verticalPlan, activeConstraintIndex, lateralLegIndex);
                                    this.pathAvailable.set(false);
                                    this.lastCapturedPathDesiredAltitude = undefined;
                                    this.trackClimb(verticalPlan, lateralPlan, tocBocDetails, activeConstraintIndex);
                                }
                                else {
                                    this.disarmPath();
                                    this.fpa.set(null);
                                    this.resetVNavTrackingVars();
                                }
                            }
                        }
                    }
                    else {
                        // Ground speed is less than 30 knots. In this case we will fail VNAV, but still publish phase data and
                        // current/active constraint data if VNAV is not disabled.
                        this.failVNav();
                        if (!this.isEnabled) {
                            this.resetVNavPhase();
                            this.resetVNavConstraintVars();
                        }
                        else {
                            const isPastTod = this.firstDescentConstraintLegIndex >= 0 && firstDescentConstraintIndex >= 0 // There is at least one descent constraint and...
                                && (
                                // ... we are past the first descent constraint...
                                lateralLegIndex > this.firstDescentConstraintLegIndex
                                    // ... or we are within threshold distance of the first descent constraint
                                    || (GarminVNavUtils.getDistanceToConstraint(verticalPlan, firstDescentConstraintIndex, lateralLegIndex, alongLegDistance) <= GarminVNavComputer.DESCENT_PHASE_TOD_DISTANCE));
                            const isCurrentConstraintClimb = currentConstraint && (currentConstraint.type === 'climb' || currentConstraint.type === 'missed');
                            // VNAV flight phase:
                            // Climb: in departure segment or the current constraint is a climb constraint.
                            // Cruise: airplane is not in the departure segment and the current constraint is not a climb constraint.
                            // Descent: airplane is at or past 10 NM to go to the first descent constraint in the flight plan or is in the
                            //          missed approach.
                            if (isInDeparture) {
                                this.vnavFlightPhase.set(exports.GarminVNavFlightPhase.Climb);
                            }
                            else if (isPastTod || isInMapr) {
                                this.vnavFlightPhase.set(exports.GarminVNavFlightPhase.Descent);
                            }
                            else if (isCurrentConstraintClimb) {
                                this.vnavFlightPhase.set(exports.GarminVNavFlightPhase.Climb);
                            }
                            else {
                                this.vnavFlightPhase.set(exports.GarminVNavFlightPhase.Cruise);
                            }
                            // VNAV tracking phase:
                            if (this.enableAdvancedVNav) {
                                // Climb: airplane is not past 10 NM to go to the first descent constraint and the current constraint
                                //        constraint is a climb constraint.
                                // Cruise: airplane is not past 10 NM to go to the first descent constraint and the current constraint is a
                                //         climb constraint.
                                // Descent: airplane is at or past 10 NM to go to the first descent constraint in the flight plan and is not
                                //          in the missed approach.
                                // Missed approach: airplane is in the missed approach.
                                if (isInMapr) {
                                    this.vnavTrackingPhase.set(exports.GarminVNavTrackingPhase.MissedApproach);
                                }
                                else if (isPastTod) {
                                    this.vnavTrackingPhase.set(exports.GarminVNavTrackingPhase.Descent);
                                }
                                else if (isCurrentConstraintClimb) {
                                    this.vnavTrackingPhase.set(exports.GarminVNavTrackingPhase.Climb);
                                }
                                else {
                                    this.vnavTrackingPhase.set(exports.GarminVNavTrackingPhase.Cruise);
                                }
                            }
                            else {
                                // Cruise: default phase.
                                // Descent: airplane is at or past 10 NM to go to the first descent constraint in the flight plan.
                                if (isPastTod) {
                                    this.vnavTrackingPhase.set(exports.GarminVNavTrackingPhase.Descent);
                                }
                                else {
                                    this.vnavTrackingPhase.set(exports.GarminVNavTrackingPhase.Cruise);
                                }
                            }
                            if (currentConstraint) {
                                this.currentConstraintLegIndex.set(currentConstraint.index);
                                // The active constraint is the current constraint unless we are not in the missed approach and the current
                                // constraint is a missed approach constraint.
                                this.vnavActiveConstraintLegIndex.set((!isInMapr && currentConstraint.type === 'missed') ? -1 : currentConstraint.index);
                                this.fpa.set(isCurrentConstraintClimb ? null : currentConstraint.fpa);
                            }
                            else {
                                this.resetVNavConstraintVars();
                            }
                        }
                    }
                }
            }
            else {
                this.failVNav();
                this.resetVNavPhase();
                this.resetVNavConstraintVars();
            }
            this.updateGuidance();
            this.updatePathGuidance();
            // evaluate tod time remaining for annunciation
            const todDistanceNM = msfsSdk.UnitType.METER.convertTo(this.todBodDetails.distanceFromTod, msfsSdk.UnitType.NMILE);
            const timeRemainingSecs = msfsSdk.UnitType.HOUR.convertTo(todDistanceNM / this.currentGroundSpeed, msfsSdk.UnitType.SECOND);
            this.withinOneMinuteTod.set(this.todBodDetails.distanceFromTod > 100 && timeRemainingSecs <= 60
                && this.state === msfsSdk.VNavState.Enabled_Active && this.pathMode.get() !== msfsSdk.VNavPathMode.PathActive);
            this.withinFiveSecondsTod.set(this.withinOneMinuteTod.get() && timeRemainingSecs <= 10);
        }
        /**
         * Updates the autopilot values used by this computer.
         */
        updateAPValues() {
            this.apSelectedAltitude = this.apValues.selectedAltitude.get();
            this.apSelectedVs = this.apValues.selectedVerticalSpeed.get();
            this.updateApLateralActiveMode(this.apValues.lateralActive.get());
            this.updateApVerticalActiveMode(this.apValues.verticalActive.get());
            this.updateApVerticalArmedMode(this.apValues.verticalArmed.get());
        }
        /**
         * Updates the autopilot's active lateral mode.
         * @param mode The active lateral mode.
         */
        updateApLateralActiveMode(mode) {
            if (mode === this.apLateralActiveMode) {
                return;
            }
            this.apLateralActiveMode = mode;
            if (mode === msfsSdk.APLateralModes.LOC && this.pathMode.get() === msfsSdk.VNavPathMode.PathArmed) {
                this.tryDeactivate();
            }
        }
        /**
         * Updates the autopilot's active vertical mode.
         * @param mode The active vertical mode.
         */
        updateApVerticalActiveMode(mode) {
            if (mode === this.apVerticalActiveMode) {
                return;
            }
            const oldMode = this.apVerticalActiveMode;
            this.apVerticalActiveMode = mode;
            if (this.isAwaitingAltCapture && (mode === msfsSdk.APVerticalModes.CAP || mode === msfsSdk.APVerticalModes.ALT)) {
                this.isAwaitingAltCapture = false;
                this.isAltCaptured = true;
                this.capturedAltitude = this.altitudeToCapture;
                this.isClimbArmed = this.stagedIsClimbArmed;
                this.isAwaitingPathRearm = this.stagedIsAwaitingPathRearm;
                this.pathRearmIndex = this.stagedPathRearmIndex;
            }
            if (this.isAwaitingPathRearm && mode !== msfsSdk.APVerticalModes.ALT && mode !== msfsSdk.APVerticalModes.CAP) {
                this.isAwaitingPathRearm = false;
                this.pathRearmIndex = -1;
            }
            if (mode === msfsSdk.APVerticalModes.FLC) {
                this.isClimbArmed = false;
                this.shouldActivateClimbMode = false;
            }
            if (oldMode === msfsSdk.APVerticalModes.PATH) {
                this.onPathDirectorDeactivated();
            }
        }
        /**
         * Updates the autopilot's armed vertical mode.
         * @param mode The armed vertical mode.
         */
        updateApVerticalArmedMode(mode) {
            if (mode === this.apVerticalArmedMode) {
                return;
            }
            const oldMode = this.apVerticalArmedMode;
            this.apVerticalArmedMode = mode;
            if (oldMode === msfsSdk.APVerticalModes.PATH) {
                this.onPathDirectorDeactivated();
            }
        }
        /**
         * Responds to when the autopilot's PATH director is deactivated.
         */
        onPathDirectorDeactivated() {
            if (this.apVerticalActiveMode === msfsSdk.APVerticalModes.GP || this.apVerticalActiveMode === msfsSdk.APVerticalModes.GS) {
                this.pathMode.set(msfsSdk.VNavPathMode.None);
            }
            else if (this.pathMode.get() === msfsSdk.VNavPathMode.PathActive || this.pathMode.get() === msfsSdk.VNavPathMode.PathArmed) {
                this.pathMode.set(msfsSdk.VNavPathMode.None);
                this.isAltCaptured = false;
                this.checkAltSel.set(false);
                if (this.apVerticalActiveMode === msfsSdk.APVerticalModes.VS
                    || this.apVerticalActiveMode === msfsSdk.APVerticalModes.FLC
                    || this.apVerticalActiveMode === msfsSdk.APVerticalModes.ALT) {
                    // The only way we can go from PATH to VS/FLC/ALT is if the latter was manually selected by the user.
                    // Therefore, we will inhibit PATH activation so that if PATH is re-armed, we don't immediately recapture the
                    // path. Note that other modes can also be manually selected to override PATH (e.g. TO/GA), but PATH arming
                    // is inhibited in those modes.
                    this.isPathActivationInhibited = true;
                    this.pathReactivationDeviationStage = 'armed';
                    this.pathReactivationTimeRemaining = 10000;
                }
            }
        }
        /**
         * Updates the guidance provided by this computer.
         */
        updateGuidance() {
            const guidanceBufferActiveIndex = this._guidance.get() === this.guidanceBuffer[0] ? 0 : 1;
            const guidance = this.guidanceBuffer[(guidanceBufferActiveIndex + 1) % 2];
            guidance.state = this.state;
            guidance.isActive = this.isActive;
            guidance.pathMode = this.pathMode.get();
            guidance.armedClimbMode = this.isClimbArmed ? msfsSdk.APVerticalModes.FLC : msfsSdk.APVerticalModes.NONE;
            guidance.shouldActivateClimbMode = this.shouldActivateClimbMode;
            guidance.altitudeCaptureType = this.captureType.get();
            guidance.shouldCaptureAltitude = this.isAwaitingAltCapture;
            guidance.altitudeToCapture = this.altitudeToCapture;
            this._guidance.set(guidance);
        }
        /**
         * Updates the vertical path guidance provided by this computer.
         */
        updatePathGuidance() {
            const guidanceBufferActiveIndex = this._pathGuidance.get() === this.pathGuidanceBuffer[0] ? 0 : 1;
            const guidance = this.pathGuidanceBuffer[(guidanceBufferActiveIndex + 1) % 2];
            const fpa = this.fpa.get();
            const deviation = this.verticalDeviation.get();
            guidance.isValid = fpa !== null && deviation !== null;
            guidance.fpa = fpa !== null && fpa !== void 0 ? fpa : 0;
            guidance.deviation = deviation !== null && deviation !== void 0 ? deviation : 0;
            this._pathGuidance.set(guidance);
        }
        /**
         * Updates vertical tracking for climb.
         * @param verticalPlan The vertical flight plan.
         * @param lateralPlan The lateral flight plan.
         * @param tocBocDetails The computed TOC/BOC details.
         * @param activeConstraintIndex The index of the VNAV constraint containing the active flight plan leg, or `-1` if
         * there is no such constraint.
         */
        trackClimb(verticalPlan, lateralPlan, tocBocDetails, activeConstraintIndex) {
            const lateralLegIndex = this.lnavLegIndex.get();
            const currentAlongLegDistance = msfsSdk.UnitType.NMILE.convertTo(this.lnavLegDistanceAlong.get(), msfsSdk.UnitType.METER);
            this.requiredVS.set(activeConstraintIndex < 0
                ? null
                : this.getClimbRequiredVs(verticalPlan, activeConstraintIndex, lateralLegIndex, currentAlongLegDistance));
            const isMapr = msfsSdk.BitFlags.isAll(lateralPlan.getLeg(lateralLegIndex).flags, msfsSdk.LegDefinitionFlags.MissedApproach);
            const currentClimbConstraint = isMapr
                ? msfsSdk.VNavUtils.getNextMaprTargetConstraint(verticalPlan, lateralLegIndex)
                : msfsSdk.VNavUtils.getNextClimbTargetConstraint(verticalPlan, lateralLegIndex);
            const constraintAltitudeFeet = currentClimbConstraint !== undefined ? msfsSdk.UnitType.METER.convertTo(currentClimbConstraint.maxAltitude, msfsSdk.UnitType.FOOT)
                : Number.POSITIVE_INFINITY;
            this.targetAltitude.set(currentClimbConstraint !== undefined ? constraintAltitudeFeet : null);
            this.checkAltSel.set(this.isActive
                && currentClimbConstraint !== undefined
                && this.isClimbArmed
                && lateralLegIndex === currentClimbConstraint.index
                && msfsSdk.UnitType.METER.convertTo(tocBocDetails.distanceFromBoc, msfsSdk.UnitType.NMILE) / (this.currentGroundSpeed / 60) < 0.75
                && this.apSelectedAltitude <= this.capturedAltitude);
            // If VNAV is not active, we are done here since the rest of this method deals exclusively with AP mode change and
            // altitude capture logic.
            if (!this.isActive) {
                this.captureType.set(msfsSdk.VNavAltCaptureType.None);
                return;
            }
            if (this.isClimbArmed
                && (currentClimbConstraint === undefined || Math.round(constraintAltitudeFeet) > this.capturedAltitude)
                && this.apSelectedAltitude > this.capturedAltitude) {
                this.cancelAltCap();
                this.isAltCaptured = false;
                this.capturedAltitude = Number.POSITIVE_INFINITY;
                this.shouldActivateClimbMode = true;
                return;
            }
            if (currentClimbConstraint === undefined || this.isAltCaptured) {
                this.captureType.set(msfsSdk.VNavAltCaptureType.None);
                return;
            }
            let canArmAltV;
            const constraintAltitudeDelta = constraintAltitudeFeet - this.currentAltitude;
            const constraintAltitudeDeltaSign = Math.sign(constraintAltitudeDelta);
            switch (this.apVerticalActiveMode) {
                case msfsSdk.APVerticalModes.PITCH:
                case msfsSdk.APVerticalModes.TO:
                case msfsSdk.APVerticalModes.GA:
                    // ALTV can arm if current vertical speed is toward the constraint altitude.
                    canArmAltV = constraintAltitudeDeltaSign * Math.sign(this.currentVS) >= 0;
                    break;
                case msfsSdk.APVerticalModes.VS:
                    // ALTV can arm if selected vertical speed is toward the constraint altitude.
                    canArmAltV = constraintAltitudeDeltaSign * Math.sign(this.apSelectedVs) >= 0;
                    break;
                case msfsSdk.APVerticalModes.FLC:
                    // ALTV can arm if preselected altitude is toward the constraint altitude.
                    canArmAltV = constraintAltitudeDeltaSign * Math.sign(this.apSelectedAltitude - this.currentAltitude) >= 0;
                    break;
                default:
                    canArmAltV = false;
            }
            if (canArmAltV) {
                // If ALTV can be armed, we need to make sure that we will not capture the preselected altitude first (if the
                // constraint and preselected altitudes are the same, preselected altitude takes precedence).
                const selectedAltitudeDelta = this.apSelectedAltitude - this.currentAltitude;
                if (constraintAltitudeDeltaSign < 0) {
                    canArmAltV = selectedAltitudeDelta > 0 || selectedAltitudeDelta < constraintAltitudeDelta;
                }
                else if (constraintAltitudeDeltaSign > 0) {
                    canArmAltV = selectedAltitudeDelta < 0 || selectedAltitudeDelta > constraintAltitudeDelta;
                }
            }
            if (canArmAltV) {
                this.captureType.set(msfsSdk.VNavAltCaptureType.VNAV);
                const captureRange = Math.max(Math.abs(this.currentVS / 6), 50);
                if (Math.abs(constraintAltitudeDelta) <= captureRange) {
                    this.activateAltCap(constraintAltitudeFeet, msfsSdk.VerticalFlightPhase.Climb);
                }
                else {
                    this.cancelAltCap();
                }
            }
            else {
                this.captureType.set(msfsSdk.VNavAltCaptureType.None);
                this.cancelAltCap();
            }
        }
        /**
         * Gets the next altitude constraint to be sequenced that defines an altitude to be met for required vertical speed
         * calculations.
         * @param verticalPlan The vertical flight plan.
         * @param activeConstraintIndex The index of the VNAV constraint containing the active flight plan leg.
         * @param globalLegIndex The index of the active flight plan leg.
         * @param distanceAlongLeg The along-track distance from the start of the active flight plan leg to the the
         * airplane's position, in meters.
         * @returns The next altitude constraint to be sequenced that defines an altitude to be met for required vertical
         * speed calculations, or `undefined` if there is no such constraint.
         */
        getClimbRequiredVs(verticalPlan, activeConstraintIndex, globalLegIndex, distanceAlongLeg) {
            let constraintIndex = -1;
            // In climb phase, VSR is defined by the next climb constraint with a minimum altitude.
            for (let i = activeConstraintIndex; i >= 0; i--) {
                const constraint = verticalPlan.constraints[i];
                if (constraint.type === 'climb' || constraint.type === 'missed') {
                    if (constraint.minAltitude > Number.NEGATIVE_INFINITY) {
                        constraintIndex = i;
                        break;
                    }
                }
                else {
                    return null;
                }
            }
            if (constraintIndex < 0) {
                return null;
            }
            const constraint = verticalPlan.constraints[constraintIndex];
            const altitude = constraint.minAltitude;
            const distance = GarminVNavUtils.getDistanceToConstraint(verticalPlan, constraintIndex, globalLegIndex, distanceAlongLeg);
            const requiredVs = this.getRequiredVs(msfsSdk.UnitType.METER.convertTo(distance, msfsSdk.UnitType.NMILE), msfsSdk.UnitType.METER.convertTo(altitude, msfsSdk.UnitType.FOOT));
            if (requiredVs >= 100) {
                return requiredVs;
            }
            else {
                return null;
            }
        }
        /**
         * Updates vertical tracking for descent.
         * @param dt The elapsed simulation time since the last update cycle, in milliseconds.
         * @param verticalPlan The vertical flight Plan.
         * @param lateralPlan The lateral flight Plan.
         * @param todBodDetails The computed TOD/BOD details.
         * @param activeConstraintIndex The index of the VNAV constraint containing the active flight plan leg, or `-1` if
         * there is no such constraint.
         * @param activePathConstraintIndex The index of the constraint defining the active descent path, or `-1` if there is
         * no active descent path.
         */
        trackDescent(dt, verticalPlan, lateralPlan, todBodDetails, activeConstraintIndex, activePathConstraintIndex) {
            const lateralLegIndex = this.lnavLegIndex.get();
            const currentAlongLegDistance = msfsSdk.UnitType.NMILE.convertTo(this.lnavLegDistanceAlong.get(), msfsSdk.UnitType.METER);
            const currentConstraint = verticalPlan.constraints[activeConstraintIndex];
            const currentVerticalLeg = currentConstraint === null || currentConstraint === void 0 ? void 0 : currentConstraint.legs[currentConstraint.index - lateralLegIndex];
            let altitudeToCaptureInPath = null;
            let activePathConstraint;
            let verticalDeviation = null;
            let deviationFromAltitudeToCaptureInPath = Infinity;
            let distanceToActivePathConstraint = 0;
            if (activePathConstraintIndex < 0 || !currentConstraint) {
                this.targetAltitude.set(null);
                this.requiredVS.set(null);
                this.verticalDeviation.set(null);
                this.lastCapturedPathDesiredAltitude = undefined;
                if (this.pathMode.get() == msfsSdk.VNavPathMode.PathActive) {
                    this.disarmPath();
                }
                this.resetPathReactivationInhibit();
                if (this.isActive && currentConstraint) {
                    this.updatePathArmState();
                }
                if (!currentConstraint) {
                    this.captureType.set(msfsSdk.VNavAltCaptureType.None);
                    return;
                }
            }
            else {
                activePathConstraint = verticalPlan.constraints[activePathConstraintIndex];
                distanceToActivePathConstraint = GarminVNavUtils.getDistanceToConstraint(verticalPlan, activePathConstraintIndex, lateralLegIndex, currentAlongLegDistance);
                const vnavTargetAltitude = verticalPlan.constraints[todBodDetails.bodConstraintIndex].targetAltitude;
                const vnavTargetAltitudeFeet = Math.round(msfsSdk.UnitType.METER.convertTo(vnavTargetAltitude, msfsSdk.UnitType.FOOT));
                this.requiredVS.set(this.getDescentRequiredVs(verticalPlan, activePathConstraintIndex, lateralLegIndex, currentAlongLegDistance));
                const pathFpa = activePathConstraint.fpa;
                const desiredAltitude = msfsSdk.UnitType.METER.convertTo(GarminVNavUtils.getPathDesiredAltitude(verticalPlan, activePathConstraintIndex, distanceToActivePathConstraint), msfsSdk.UnitType.FOOT);
                verticalDeviation = desiredAltitude - this.currentAltitude;
                if (this.isActive) {
                    this.updatePathArmState();
                }
                // If path activation is inhibited, then check if the conditions for path reactivation have been met, and if so
                // remove the inhibit.
                if (this.isPathActivationInhibited) {
                    this.pathReactivationTimeRemaining -= dt;
                    switch (this.pathReactivationDeviationStage) {
                        case 'armed':
                            if (Math.abs(verticalDeviation) >= 250) {
                                this.pathReactivationDeviationStage = 'deviated';
                            }
                            break;
                        case 'deviated':
                            if (Math.abs(verticalDeviation) <= 200) {
                                this.pathReactivationDeviationStage = 'captured';
                            }
                            break;
                    }
                    if (this.pathReactivationTimeRemaining <= 0 && this.pathReactivationDeviationStage === 'captured') {
                        this.resetPathReactivationInhibit();
                    }
                }
                if (this.pathMode.get() === msfsSdk.VNavPathMode.PathActive) {
                    if (this.shouldDeactivatePath(desiredAltitude, verticalDeviation)) {
                        this.verticalDeviation.set(verticalDeviation);
                        this.disarmPath();
                        this.lastCapturedPathDesiredAltitude = undefined;
                        return;
                    }
                    else {
                        this.lastCapturedPathDesiredAltitude = desiredAltitude;
                    }
                }
                else {
                    this.lastCapturedPathDesiredAltitude = undefined;
                }
                altitudeToCaptureInPath = this.apSelectedAltitude > this.currentAltitude
                    ? vnavTargetAltitudeFeet
                    : Math.max(vnavTargetAltitudeFeet, this.apSelectedAltitude);
                deviationFromAltitudeToCaptureInPath = this.currentAltitude - altitudeToCaptureInPath;
                this.isActive && this.canPathActivate(pathFpa, verticalDeviation, deviationFromAltitudeToCaptureInPath) && this.activatePath();
            }
            this.verticalDeviation.set(verticalDeviation);
            const pathActive = this.pathMode.get() == msfsSdk.VNavPathMode.PathActive;
            const captureRange = Math.max(Math.abs(this.currentVS / 6), 50);
            if (pathActive) {
                // If PATH is active, then it is our responsibility to capture the next altitude, whether it is a VNAV constraint
                // altitude or a preselected altitude.
                // This should technically never happen since we disarm path above if there is no active path constraint.
                if (activePathConstraint === undefined || altitudeToCaptureInPath === null) {
                    this.targetAltitude.set(null);
                    this.captureType.set(msfsSdk.VNavAltCaptureType.None);
                    return;
                }
                this.targetAltitude.set(altitudeToCaptureInPath);
                // VNAV should always be active if PATH is active, but just in case...
                if (!this.isActive) {
                    this.captureType.set(msfsSdk.VNavAltCaptureType.None);
                    return;
                }
                const altitudeToCaptureIsSelectedAltitude = this.apSelectedAltitude === altitudeToCaptureInPath;
                this.captureType.set(altitudeToCaptureIsSelectedAltitude ? msfsSdk.VNavAltCaptureType.Selected : msfsSdk.VNavAltCaptureType.VNAV);
                // If we are not yet within capture range of the target altitude, we are done and do not need to perform any
                // altitude capture operations.
                if (Math.abs(deviationFromAltitudeToCaptureInPath) > captureRange) {
                    this.cancelAltCap();
                    return;
                }
                const isPathEnd = activePathConstraint.isPathEnd;
                const nextLeg = !isPathEnd ? msfsSdk.VNavUtils.getVerticalLegFromPlan(verticalPlan, activePathConstraint.index + 1) : undefined;
                if (altitudeToCaptureIsSelectedAltitude || nextLeg === undefined || nextLeg.fpa === 0) {
                    if (this.apVerticalActiveMode === msfsSdk.APVerticalModes.PATH) {
                        this.activateAltCap(altitudeToCaptureInPath, !isPathEnd ? msfsSdk.VerticalFlightPhase.Descent : undefined, !altitudeToCaptureIsSelectedAltitude && (currentVerticalLeg === null || currentVerticalLeg === void 0 ? void 0 : currentVerticalLeg.isBod) ? lateralLegIndex + 1 : lateralLegIndex);
                    }
                    return;
                }
                // Now we know path is active, we are approaching a BOD and the next leg is valid and has a non-zero FPA.
                // If the TOD for the next leg is close to the BOD, we will skip altitude capture because we might fly past the
                // TOD by the time PATH rearms. If we skip capture, then PATH will stay active and pick up the deviation from
                // the next leg once the current leg is sequenced. The new deviation should be small and is guaranteed to be such
                // that the airplane is under the path, so the PATH director should have no problem re-establishing.
                const nextLegTodDistance = msfsSdk.VNavUtils.distanceForAltitude(nextLeg.fpa, msfsSdk.UnitType.FOOT.convertTo(altitudeToCaptureInPath, msfsSdk.UnitType.METER) - nextLeg.altitude);
                const distanceToNextTod = distanceToActivePathConstraint + nextLeg.distance - nextLegTodDistance;
                if (distanceToNextTod > 1900) {
                    this.activateAltCap(altitudeToCaptureInPath, msfsSdk.VerticalFlightPhase.Descent);
                }
            }
            else {
                // If we are in a non-PATH vertical mode, ALTV should capture the minimum altitude of the current constraint
                // (VNAV ineligible legs and discontinuities don't matter here since we aren't tracking a path).
                let canArmAltV;
                const constraintAltitudeFeet = Math.round(msfsSdk.UnitType.METER.convertTo(currentConstraint.minAltitude, msfsSdk.UnitType.FOOT));
                const constraintAltitudeDelta = constraintAltitudeFeet - this.currentAltitude;
                const constraintAltitudeDeltaSign = Math.sign(constraintAltitudeDelta);
                switch (this.apVerticalActiveMode) {
                    case msfsSdk.APVerticalModes.PITCH:
                    case msfsSdk.APVerticalModes.TO:
                    case msfsSdk.APVerticalModes.GA:
                        // ALTV can arm if current vertical speed is toward the constraint altitude.
                        canArmAltV = constraintAltitudeDeltaSign * Math.sign(this.currentVS) >= 0;
                        break;
                    case msfsSdk.APVerticalModes.VS:
                        // ALTV can arm if selected vertical speed is toward the constraint altitude.
                        canArmAltV = constraintAltitudeDeltaSign * Math.sign(this.apSelectedVs) >= 0;
                        break;
                    case msfsSdk.APVerticalModes.FLC:
                        // ALTV can arm if preselected altitude is toward the constraint altitude.
                        canArmAltV = constraintAltitudeDeltaSign * Math.sign(this.apSelectedAltitude - this.currentAltitude) >= 0;
                        break;
                    default:
                        canArmAltV = false;
                }
                if (canArmAltV) {
                    // If ALTV can be armed, we need to make sure that we will not capture the preselected altitude first (if the
                    // constraint and preselected altitudes are the same, preselected altitude takes precedence).
                    const selectedAltitudeDelta = this.apSelectedAltitude - this.currentAltitude;
                    if (constraintAltitudeDeltaSign < 0) {
                        canArmAltV = selectedAltitudeDelta > 0 || selectedAltitudeDelta < constraintAltitudeDelta;
                    }
                    else if (constraintAltitudeDeltaSign > 0) {
                        canArmAltV = selectedAltitudeDelta < 0 || selectedAltitudeDelta > constraintAltitudeDelta;
                    }
                }
                this.targetAltitude.set(canArmAltV ? constraintAltitudeFeet : null);
                // If VNAV is not active, we are done here since everything that comes after deals exclusively with altitude capture.
                if (!this.isActive) {
                    this.captureType.set(msfsSdk.VNavAltCaptureType.None);
                    return;
                }
                if (canArmAltV) {
                    this.captureType.set(msfsSdk.VNavAltCaptureType.VNAV);
                    if (Math.abs(constraintAltitudeDelta) <= captureRange) {
                        this.activateAltCap(constraintAltitudeFeet, (activePathConstraint === null || activePathConstraint === void 0 ? void 0 : activePathConstraint.isPathEnd) ? msfsSdk.VerticalFlightPhase.Descent : undefined, (currentVerticalLeg === null || currentVerticalLeg === void 0 ? void 0 : currentVerticalLeg.isBod) ? lateralLegIndex + 1 : lateralLegIndex);
                    }
                    else {
                        this.cancelAltCap();
                    }
                }
                else {
                    this.captureType.set(msfsSdk.VNavAltCaptureType.None);
                    this.cancelAltCap();
                }
            }
        }
        /**
         * Gets the next altitude constraint to be sequenced that defines an altitude to be met for required vertical speed
         * calculations.
         * @param verticalPlan The vertical flight plan.
         * @param pathConstraintIndex The current vertical flight phase.
         * @param globalLegIndex The index of the active flight plan leg.
         * @param distanceAlongLeg The along-track distance from the start of the active flight plan leg to the the
         * airplane's position, in meters.
         * @returns The next altitude constraint to be sequenced that defines an altitude to be met for required vertical
         * speed calculations, or `undefined` if there is no such constraint.
         */
        getDescentRequiredVs(verticalPlan, pathConstraintIndex, globalLegIndex, distanceAlongLeg) {
            let constraintIndex = -1;
            // In descent phase, VSR is defined by the next descent constraint that has a maximum altitude or that ends in a
            // level-off or FPA change (or the last descent constraint, which is treated as a level-off).
            for (let i = pathConstraintIndex; i >= 0; i--) {
                const constraint = verticalPlan.constraints[i];
                const nextConstraint = verticalPlan.constraints[i - 1];
                if ((constraint.type === 'descent' || constraint.type === 'direct' || constraint.type === 'manual')) {
                    if (constraint.maxAltitude < Number.POSITIVE_INFINITY
                        || (constraint.targetAltitude > 0 && (constraint.isTarget
                            || (nextConstraint === undefined || (nextConstraint.type !== 'descent' && nextConstraint.type !== 'manual'))
                            || nextConstraint.fpa !== constraint.fpa))) {
                        constraintIndex = i;
                        break;
                    }
                }
                else {
                    return null;
                }
            }
            if (constraintIndex < 0) {
                return null;
            }
            const constraint = verticalPlan.constraints[constraintIndex];
            const altitude = constraint.maxAltitude < Number.POSITIVE_INFINITY ? constraint.maxAltitude : constraint.targetAltitude;
            const distance = GarminVNavUtils.getDistanceToConstraint(verticalPlan, constraintIndex, globalLegIndex, distanceAlongLeg);
            const requiredVs = this.getRequiredVs(msfsSdk.UnitType.METER.convertTo(distance, msfsSdk.UnitType.NMILE), msfsSdk.UnitType.METER.convertTo(altitude, msfsSdk.UnitType.FOOT));
            if (requiredVs <= -100) {
                return requiredVs;
            }
            else {
                return null;
            }
        }
        /**
         * Checks whether the VNav Path can arm.
         * @returns Whether Path can arm.
         */
        canPathArm() {
            return this.apVerticalActiveMode !== msfsSdk.APVerticalModes.CAP
                && this.apVerticalActiveMode !== msfsSdk.APVerticalModes.TO
                && this.apVerticalActiveMode !== msfsSdk.APVerticalModes.GA
                && !this.isAwaitingPathRearm
                && this.preselectedAltitude + 75 < this.currentAltitude;
        }
        /**
         * Checks whether V PATH can be activated from an armed state.
         * @param pathFpa The flight path angle of the active descent path, in degrees.
         * @param verticalDeviation The vertical deviation from the active descent path, in feet.
         * @param deviationFromTarget The deviation from the target altitude of the descent path, in feet.
         * @returns Whether V PATH can be activated from an armed state.
         */
        canPathActivate(pathFpa, verticalDeviation, deviationFromTarget) {
            return !this.isPathActivationInhibited
                && this.pathMode.get() === msfsSdk.VNavPathMode.PathArmed
                && !this.isAltCaptured
                && pathFpa !== 0
                && !this.noVNavTae.get()
                && !this.noVNavXtk.get()
                && verticalDeviation <= msfsSdk.VNavUtils.getPathErrorDistance(this.currentGroundSpeed)
                && verticalDeviation >= -15
                && deviationFromTarget > 75;
        }
        /**
         * Arms PATH if it is not already armed and can be armed, and disarms PATH if it is already armed and current
         * conditions do not allow it to be armed.
         */
        updatePathArmState() {
            const currentPathMode = this.pathMode.get();
            // Checks if PATH is waiting to be re-armed and the conditions for re-arm have been met, and if so clears the
            // awaiting re-arm state so that PATH can be armed again.
            if (this.isAwaitingPathRearm && this.lnavLegIndex.get() >= this.pathRearmIndex && this.apVerticalArmedMode !== msfsSdk.APVerticalModes.ALT) {
                this.isAwaitingPathRearm = false;
                this.pathRearmIndex = -1;
            }
            const canPathArm = this.canPathArm();
            if (currentPathMode === msfsSdk.VNavPathMode.None) {
                if (canPathArm) {
                    this.armPath();
                }
                else {
                    // TODO: Check if Garmin even has a check alt sel message.
                    // Enable check alt sel message if preselector is not at least 75 feet below current altitude, we have not
                    // yet reached TOD, and we are within 45 seconds of TOD.
                    this.checkAltSel.set(this.preselectedAltitude + 75 >= this.currentAltitude
                        && this.todBodDetails.distanceFromTod > 0
                        && msfsSdk.UnitType.METER.convertTo(this.todBodDetails.distanceFromTod, msfsSdk.UnitType.NMILE) / (this.currentGroundSpeed / 60) < 0.75);
                }
            }
            else if (currentPathMode === msfsSdk.VNavPathMode.PathArmed && !canPathArm) {
                this.disarmPath();
            }
        }
        /**
         * Updates this manager's track error states - whether LNAV track angle error or cross-track error have exceeded
         * the allowed limits for VNAV to provide vertical tracking.
         */
        updateTrackErrorState() {
            // NO_VNAV_TAE: Track angle error exceeds an acceptable threshold
            this.noVNavTae.set(this.isTaeOutsideLimits());
            // NO_VNAV_XTK: Cross track deviation exceeds an acceptable threshold
            this.noVNavXtk.set(this.isXtkOutsideLimits());
        }
        /**
         * Checks whether PATH mode should be deactivated.
         * @param desiredAltitude The current desired altitude, in feet, at the airplane's current position along the
         * vertical path.
         * @param verticalDeviation The current vertical deviation, in feet, of the vertical path from the airplane. Positive
         * values indicate the path lies above the airplane.
         * @returns Whether PATH mode should be deactivated.
         */
        shouldDeactivatePath(desiredAltitude, verticalDeviation) {
            // Check if track angle error or cross-track error are out of limits.
            if (this.noVNavTae.get() || this.noVNavXtk.get()) {
                return true;
            }
            // Check if the vertical path has shifted by more than 200 feet vertically since the last update and the current
            // vertical deviation is greater than 200 feet.
            if (this.lastCapturedPathDesiredAltitude !== undefined
                && Math.abs(desiredAltitude - this.lastCapturedPathDesiredAltitude) > 200
                && Math.abs(verticalDeviation) > 200) {
                return true;
            }
            return false;
        }
        /**
         * Checks whether the XTK is out of limits for vnav.
         * @returns if the XTK is out of limits.
         */
        isXtkOutsideLimits() {
            return Math.abs(this.lnavXtk.get()) > this.lnavDataCdiScale.get();
        }
        /**
         * Chekcs if the TAE is out of limits for vnav.
         * @returns if TAE is out of limits.
         */
        isTaeOutsideLimits() {
            const tae = Math.abs(msfsSdk.NavMath.diffAngle(this.lnavDtk.get(), this.trueTrack));
            const taeErrorLimit = 75;
            return tae > taeErrorLimit;
        }
        /**
         * Method to reset all error messages.
         */
        clearAllMessages() {
            this.pathBelowAircraft.set(false);
            this.checkAltSel.set(false);
            this.noVNavTae.set(false);
            this.noVNavXtk.set(false);
            this.noPathThisLeg.set(false);
            this.noPathConditionDisco.set(false);
            this.noPathVectors.set(false);
            this.checkFplnAlt.set(false);
        }
        /**
         * Method to monitor message state.
         */
        monitorMessages() {
            // init messages
            this.clearAllMessages();
            // monitor messages
            // this.withinOneMinuteTod.sub(v => {
            //   if (v) {
            //     this.messageService.post(FMS_MESSAGE_ID.TOD, undefined, () => this.withinFiveSecondsTod.get());
            //   } else {
            //     this.messageService.clear(FMS_MESSAGE_ID.TOD);
            //   }
            // });
            // this.pathBelowAircraft.sub(v => {
            //   if (v) {
            //     this.messageService.post(FMS_MESSAGE_ID.PATH_BELOW_AC);
            //   } else {
            //     this.messageService.clear(FMS_MESSAGE_ID.PATH_BELOW_AC);
            //   }
            // });
            // this.noPathTae.sub(v => {
            //   if (v) {
            //     this.messageService.post(FMS_MESSAGE_ID.NO_VPATH_TAE);
            //   } else {
            //     this.messageService.clear(FMS_MESSAGE_ID.NO_VPATH_TAE);
            //   }
            // });
            // this.noPathXtk.sub(v => {
            //   if (v) {
            //     this.messageService.post(FMS_MESSAGE_ID.NO_VPATH_XTK);
            //   } else {
            //     this.messageService.clear(FMS_MESSAGE_ID.NO_VPATH_XTK);
            //   }
            // });
            // this.noPathThisLeg.sub(v => {
            //   if (v) {
            //     this.messageService.post(FMS_MESSAGE_ID.NO_VPATH_THIS_LEG);
            //   } else {
            //     this.messageService.clear(FMS_MESSAGE_ID.NO_VPATH_THIS_LEG);
            //   }
            // });
            // this.noPathPilotCmd.sub(v => {
            //   if (v) {
            //     this.messageService.post(FMS_MESSAGE_ID.NO_VPATH_PILOT_CMD);
            //   } else {
            //     this.messageService.clear(FMS_MESSAGE_ID.NO_VPATH_PILOT_CMD);
            //   }
            // });
            // this.noPathConditionDisco.sub(v => {
            //   if (v) {
            //     this.messageService.post(FMS_MESSAGE_ID.NO_VPATH_CONDITION);
            //   } else if (!this.noPathConditionPlanChanged.get()) {
            //     this.messageService.clear(FMS_MESSAGE_ID.NO_VPATH_CONDITION);
            //   }
            // });
            // this.noPathConditionPlanChanged.sub(v => {
            //   if (v) {
            //     this.messageService.post(FMS_MESSAGE_ID.NO_VPATH_CONDITION);
            //   } else if (!this.noPathConditionDisco.get()) {
            //     this.messageService.clear(FMS_MESSAGE_ID.NO_VPATH_CONDITION);
            //   }
            // });
            // this.noPathVectors.sub(v => {
            //   if (v) {
            //     this.messageService.post(FMS_MESSAGE_ID.NO_VPATH_VECTORS);
            //   } else {
            //     this.messageService.clear(FMS_MESSAGE_ID.NO_VPATH_VECTORS);
            //   }
            // });
            // this.checkAltSel.sub(v => {
            //   if (v) {
            //     this.messageService.post(FMS_MESSAGE_ID.CHK_ALT_SEL);
            //   } else {
            //     this.messageService.clear(FMS_MESSAGE_ID.CHK_ALT_SEL);
            //   }
            // });
            // this.checkFplnAlt.sub(v => {
            //   if (v) {
            //     this.messageService.post(FMS_MESSAGE_ID.CHECK_FPLN_ALT);
            //   } else {
            //     this.messageService.clear(FMS_MESSAGE_ID.CHECK_FPLN_ALT);
            //   }
            // });
        }
        /**
         * Method to monitor VNavVars.
         */
        monitorVars() {
            // init vnav vars
            this.initVars();
            this.pathMode.sub(mode => {
                SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.PathMode], msfsSdk.SimVarValueType.Number, mode);
                if (mode === msfsSdk.VNavPathMode.PathArmed || mode === msfsSdk.VNavPathMode.PathActive) {
                    this.checkAltSel.set(false);
                }
            });
            this.vnavState.sub(state => SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.VNAVState], msfsSdk.SimVarValueType.Number, state));
            this.pathAvailable.sub(v => SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.PathAvailable], msfsSdk.SimVarValueType.Bool, v));
            this.currentConstraintLegIndex.sub(index => SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.CurrentConstraintLegIndex], msfsSdk.SimVarValueType.Number, index));
            this.targetAltitude.sub(alt => SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.TargetAltitude], msfsSdk.SimVarValueType.Feet, alt !== null && alt !== void 0 ? alt : -1));
            this.fpa.sub(fpa => SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.FPA], msfsSdk.SimVarValueType.Degree, fpa !== null && fpa !== void 0 ? fpa : 0));
            this.verticalDeviation.sub(dev => SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.VerticalDeviation], msfsSdk.SimVarValueType.Feet, dev !== null && dev !== void 0 ? dev : Number.MAX_SAFE_INTEGER));
            this.requiredVS.sub(vs => SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.RequiredVS], msfsSdk.SimVarValueType.FPM, vs !== null && vs !== void 0 ? vs : 0));
            this.captureType.sub(type => SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.CaptureType], msfsSdk.SimVarValueType.Number, type));
            this.currentAltitudeConstraintDetails.sub(v => {
                this.publisher.pub(this.vnavTopicMap['vnav_altitude_constraint_details'], v, true, true);
            }, true);
            this.cruiseAltitude.sub(v => {
                this.publisher.pub(this.vnavTopicMap['vnav_cruise_altitude'], v, true, true);
            }, true);
            this.vnavFlightPhase.sub(v => {
                this.publisher.pub(this.vnavTopicMap['vnav_flight_phase'], v, true, true);
            }, true);
            this.vnavTrackingPhase.sub(v => {
                this.publisher.pub(this.vnavTopicMap['vnav_tracking_phase'], v, true, true);
            }, true);
            this.vnavActiveConstraintLegIndex.sub(v => {
                this.publisher.pub(this.vnavTopicMap['vnav_active_constraint_global_leg_index'], v, true, true);
            }, true);
        }
        /**
         * Method to reset VNAV Vars.
         */
        initVars() {
            SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.VNAVState], msfsSdk.SimVarValueType.Number, msfsSdk.VNavState.Enabled_Inactive);
            SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.PathMode], msfsSdk.SimVarValueType.Number, msfsSdk.VNavPathMode.None);
            SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.PathAvailable], msfsSdk.SimVarValueType.Bool, false);
            SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.CurrentConstraintLegIndex], msfsSdk.SimVarValueType.Number, -1);
            SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.CurrentConstraintAltitude], msfsSdk.SimVarValueType.Feet, -1);
            SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.NextConstraintAltitude], msfsSdk.SimVarValueType.Feet, -1);
            SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.TargetAltitude], msfsSdk.SimVarValueType.Feet, -1);
            SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.FPA], msfsSdk.SimVarValueType.Degree, 0);
            SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.VerticalDeviation], msfsSdk.SimVarValueType.Feet, Number.MAX_SAFE_INTEGER);
            SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.RequiredVS], msfsSdk.SimVarValueType.FPM, 0);
            SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.CaptureType], msfsSdk.SimVarValueType.Number, msfsSdk.VNavAltCaptureType.None);
            SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.GPServiceLevel], msfsSdk.SimVarValueType.Number, exports.GlidepathServiceLevel.None);
            SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.GPVerticalDeviation], msfsSdk.SimVarValueType.Feet, -1001);
            SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.GPDistance], msfsSdk.SimVarValueType.Meters, -1);
            SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.GPFpa], msfsSdk.SimVarValueType.Degree, 0);
        }
        /**
         * Resets the VNAV constraint simvars.
         */
        resetVNavConstraintVars() {
            this.currentConstraintLegIndex.set(-1);
            this.vnavActiveConstraintLegIndex.set(-1);
            this.fpa.set(null);
        }
        /**
         * Resets the VNAV flight and tracking phases.
         */
        resetVNavPhase() {
            this.vnavFlightPhase.set(exports.GarminVNavFlightPhase.None);
            this.vnavTrackingPhase.set(exports.GarminVNavTrackingPhase.None);
        }
        /**
         * Resets the VNAV tracking simvars.
         */
        resetVNavTrackingVars() {
            this.pathAvailable.set(false);
            this.currentAltitudeConstraintDetails.set({ type: msfsSdk.AltitudeRestrictionType.Unused, altitude: 0 });
            this.targetAltitude.set(null);
            this.verticalDeviation.set(null);
            this.requiredVS.set(null);
            this.captureType.set(msfsSdk.VNavAltCaptureType.None);
            this.lastCapturedPathDesiredAltitude = undefined;
        }
        /**
         * Resets the TOD/BOD simvars.
         */
        resetTodBodVars() {
            this.todBodDetailsSubject.set('bodLegIndex', -1);
            this.todBodDetailsSubject.set('todLegIndex', -1);
            this.todBodDetailsSubject.set('todLegDistance', 0);
            this.todBodDetailsSubject.set('distanceFromBod', 0);
            this.todBodDetailsSubject.set('distanceFromTod', 0);
        }
        /**
         * Resets the TOC/BOC simvars.
         */
        resetTocBocVars() {
            this.tocBocDetailsSubject.set('bocLegIndex', -1);
            this.tocBocDetailsSubject.set('tocLegIndex', -1);
            this.tocBocDetailsSubject.set('tocLegDistance', 0);
            this.tocBocDetailsSubject.set('distanceFromBoc', 0);
            this.tocBocDetailsSubject.set('distanceFromToc', 0);
        }
        /**
         * Manages the TOD/BOD and BOC/TOC details simvars.
         * @param activeLateralLegIndex The index of the active lateral flight plan leg.
         * @param todBodDetails The computed TOD/BOD details.
         * @param bocTocDetails The computed BOC/TOC details.
         */
        manageTodBodTocBocDetails(activeLateralLegIndex, todBodDetails, bocTocDetails) {
            // TODO: Support missed approach
            if (activeLateralLegIndex > this.lastClimbConstraintLegIndex) {
                this.todBodDetailsSubject.set('bodLegIndex', todBodDetails.bodLegIndex);
                this.todBodDetailsSubject.set('todLegIndex', todBodDetails.todLegIndex);
                this.todBodDetailsSubject.set('todLegDistance', todBodDetails.todLegDistance);
                this.todBodDetailsSubject.set('distanceFromBod', todBodDetails.distanceFromBod);
                this.todBodDetailsSubject.set('distanceFromTod', todBodDetails.distanceFromTod);
                this.resetTocBocVars();
            }
            else {
                this.resetTodBodVars();
                this.tocBocDetailsSubject.set('bocLegIndex', bocTocDetails.bocLegIndex);
                this.tocBocDetailsSubject.set('tocLegIndex', bocTocDetails.tocLegIndex);
                this.tocBocDetailsSubject.set('tocLegDistance', bocTocDetails.tocLegDistance);
                this.tocBocDetailsSubject.set('distanceFromBoc', bocTocDetails.distanceFromBoc);
                this.tocBocDetailsSubject.set('distanceFromToc', bocTocDetails.distanceFromToc);
            }
        }
        /**
         * Sets the current constraint details.
         * @param verticalPlan The vertical plan.
         * @param constraintIndex The index of the current constraint.
         * @param activeLegIndex The global index of the active flight plan leg.
         */
        setCurrentConstraintDetails(verticalPlan, constraintIndex, activeLegIndex) {
            this.currentAltitudeConstraintDetailsWorking.type = msfsSdk.AltitudeRestrictionType.Unused;
            this.currentAltitudeConstraintDetailsWorking.altitude = 0;
            const constraint = verticalPlan.constraints[constraintIndex];
            if (constraint !== undefined) {
                if (constraint.type !== 'climb' && constraint.type !== 'missed'
                    && constraint.nextVnavEligibleLegIndex !== undefined
                    && activeLegIndex < constraint.nextVnavEligibleLegIndex) {
                    const priorConstraint = verticalPlan.constraints[constraintIndex + 1];
                    if (priorConstraint) {
                        msfsSdk.VNavUtils.getConstraintDetails(priorConstraint, this.currentAltitudeConstraintDetailsWorking);
                    }
                }
                else {
                    msfsSdk.VNavUtils.getConstraintDetails(constraint, this.currentAltitudeConstraintDetailsWorking);
                }
            }
            if (!msfsSdk.VNavUtils.altitudeConstraintDetailsEquals(this.currentAltitudeConstraintDetails.get(), this.currentAltitudeConstraintDetailsWorking)) {
                this.currentAltitudeConstraintDetails.set(Object.assign({}, this.currentAltitudeConstraintDetailsWorking));
            }
        }
        /**
         * Gets the required vertical speed to meet an altitude constraint.
         * @param distance The distance to the constraint, in nautical miles.
         * @param targetAltitude The target altitude for the constraint, in feet.
         * @param currentAltitude The current altitude, in feet. Defaults to the airplane's current indicated altitude.
         * @returns The required vertical speed, in feet per minute, to meet the altitude constraint.
         */
        getRequiredVs(distance, targetAltitude, currentAltitude = this.currentAltitude) {
            if (targetAltitude > 0) {
                return msfsSdk.VNavUtils.getRequiredVs(distance, targetAltitude, currentAltitude, this.currentGroundSpeed);
            }
            return 0;
        }
        /**
         * Publishes TOD/BOD details to simvars.
         * @param details The TOD/BOD details object.
         * @param key The key to publish.
         * @param value The value to publish.
         */
        publishTodBodDetails(details, key, value) {
            switch (key) {
                case 'bodLegIndex':
                    SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.BODLegIndex], msfsSdk.SimVarValueType.Number, value);
                    break;
                case 'todLegIndex':
                    SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.TODLegIndex], msfsSdk.SimVarValueType.Number, value);
                    break;
                case 'todLegDistance':
                    SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.TODDistanceInLeg], msfsSdk.SimVarValueType.Meters, value);
                    break;
                case 'distanceFromBod':
                    SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.BODDistance], msfsSdk.SimVarValueType.Meters, value);
                    break;
                case 'distanceFromTod':
                    SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.TODDistance], msfsSdk.SimVarValueType.Meters, value);
                    break;
            }
        }
        /**
         * Publishes BOC/TOC details to simvars.
         * @param details The BOC/TOC details object.
         * @param key The key to publish.
         * @param value The value to publish.
         */
        publishBocTocDetails(details, key, value) {
            switch (key) {
                case 'bocLegIndex':
                    SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.BOCLegIndex], msfsSdk.SimVarValueType.Number, value);
                    break;
                case 'tocLegIndex':
                    SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.TOCLegIndex], msfsSdk.SimVarValueType.Number, value);
                    break;
                case 'tocLegDistance':
                    SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.TOCDistanceInLeg], msfsSdk.SimVarValueType.Meters, value);
                    break;
                case 'distanceFromBoc':
                    SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.BOCDistance], msfsSdk.SimVarValueType.Meters, value);
                    break;
                case 'distanceFromToc':
                    SimVar.SetSimVarValue(this.simVarMap[msfsSdk.VNavVars.TOCDistance], msfsSdk.SimVarValueType.Meters, value);
                    break;
            }
        }
    }
    /** The distance, in meters, from the first TOD in the flight plan the airplane must reach in order to enter descent phase. */
    GarminVNavComputer.DESCENT_PHASE_TOD_DISTANCE = msfsSdk.UnitType.NMILE.convertTo(10, msfsSdk.UnitType.METER);
    /** The vertical distance, in feet, below the cruise altitude the airplane must reach in order to enter cruise phase. */
    GarminVNavComputer.CRUISE_PHASE_ALTITUDE_THRESHOLD = 500;
    /**
     * The vertical distance, in meters, above the TOC altitude which defines the BOC suppression threshold. If the
     * airplane's indicated altitude is above this threshold, any existing BOC will be suppressed.
     */
    GarminVNavComputer.BOC_SUPPRESS_ALTITUDE_THRESHOLD = msfsSdk.UnitType.FOOT.convertTo(250, msfsSdk.UnitType.METER);
    /**
     * The vertical distance, in meters, below the TOC altitude which defines the TOC suppression threshold. If the
     * airplane's indicated altitude is above this threshold, any existing TOC will be suppressed.
     */
    GarminVNavComputer.TOC_SUPPRESS_ALTITUDE_THRESHOLD = msfsSdk.UnitType.FOOT.convertTo(250, msfsSdk.UnitType.METER);
    /**
     * The time to TOD threshold, in seconds, below which the descent path on which the TOD lies becomes the active
     * descent path and can be captured by VPATH.
     */
    GarminVNavComputer.ACTIVE_PATH_TOD_TIME_THRESHOLD = 60;
    /**
     * The amount of hysteresis applied to the time to TOD threshold, in seconds, after a descent path has become active.
     */
    GarminVNavComputer.ACTIVE_PATH_TOD_TIME_HYSTERESIS = 30;

    /**
     * A new Garmin VNav Manager.
     */
    class GarminVNavManager2 {
        // eslint-disable-next-line jsdoc/require-returns
        /** Whether VNAV is active. */
        get isActive() {
            return this.guidance ? this.guidance.get().isActive : false;
        }
        // eslint-disable-next-line jsdoc/require-jsdoc
        constructor(bus, arg2, arg3, arg4, arg5, arg6) {
            this.bus = bus;
            this.publisher = this.bus.getPublisher();
            /** @inheritDoc */
            this.state = msfsSdk.VNavState.Disabled;
            this.inhibitPathMode = false;
            if (arg2 instanceof msfsSdk.FlightPlanner) {
                this.apValues = arg4;
                let options;
                if (typeof arg5 === 'number') {
                    options = Object.assign({ primaryPlanIndex: arg5 }, arg6);
                }
                else {
                    options = arg5;
                }
                this.vnavComputer = new GarminVNavComputer(0, bus, arg2, arg3, this.apValues, options);
                this.glidepathComputer = new GarminGlidepathComputer(0, bus, arg2, options);
                this.guidance = this.vnavComputer.guidance;
                this.glidePathGuidance = this.glidepathComputer.glidepathGuidance;
            }
            else {
                this.apValues = arg2;
                this.guidance = arg3;
                this.glidePathGuidance = arg4;
            }
        }
        /** @inheritDoc */
        setState() {
            // noop
        }
        /** @inheritDoc */
        tryActivate() {
            var _a;
            (_a = this.vnavComputer) === null || _a === void 0 ? void 0 : _a.tryActivate();
            this.publisher.pub('vnav_manager_activated', undefined, true, false);
        }
        /** @inheritDoc */
        tryDeactivate() {
            var _a;
            (_a = this.vnavComputer) === null || _a === void 0 ? void 0 : _a.tryDeactivate();
            this.publisher.pub('vnav_manager_deactivated', undefined, true, false);
        }
        /** @inheritDoc */
        canVerticalModeActivate(mode) {
            if (this.guidance) {
                const guidance = this.guidance.get();
                if (guidance.armedClimbMode !== msfsSdk.APVerticalModes.NONE && (mode === msfsSdk.APVerticalModes.FLC || mode === msfsSdk.APVerticalModes.VS)) {
                    return false;
                }
            }
            return true;
        }
        /** @inheritDoc */
        onPathDirectorDeactivated() {
            // When the PATH director is deactivated, we will set a flag to inhibit re-activation. This is to prevent immediate
            // re-activation of the PATH director while the source of the manager's VNAV guidance has not yet had a chance to
            // respond to the deactivation of PATH.
            this.inhibitPathMode = true;
        }
        /**
         * Method called to delegate altitude capture to the Alt Cap Director.
         * @param altitude The altitude to capture.
         */
        delegateAltCap(altitude) {
            const verticalActive = this.apValues.verticalActive.get();
            if (verticalActive === msfsSdk.APVerticalModes.CAP || verticalActive === msfsSdk.APVerticalModes.ALT) {
                return;
            }
            this.apValues.capturedAltitude.set(Math.round(altitude));
            this.activateMode && this.activateMode(msfsSdk.APVerticalModes.CAP);
        }
        /**
         * Arms a climb mode. The mode will be armed only if the currently active vertical mode is an altitude hold mode.
         * @param mode The mode to arm.
         */
        armClimb(mode) {
            if (this.apValues.verticalArmed.get() === mode || this.apValues.verticalActive.get() !== msfsSdk.APVerticalModes.ALT) {
                return;
            }
            this.armMode && this.armMode(mode);
        }
        /**
         * Activates a climb mode.
         * @param mode The mode to activate.
         */
        activateClimb(mode) {
            if (this.apValues.verticalActive.get() === mode) {
                return;
            }
            this.activateMode && this.activateMode(mode);
        }
        /**
         * Disarms climb (FLC) mode.
         */
        disarmClimb() {
            if (this.apValues.verticalArmed.get() === msfsSdk.APVerticalModes.FLC) {
                this.armMode && this.armMode(msfsSdk.APVerticalModes.NONE);
            }
        }
        /**
         * Arms PATH mode. The mode will be armed only if the currently active vertical mode is not an altitude capture mode.
         */
        armPath() {
            if (this.apValues.verticalArmed.get() === msfsSdk.APVerticalModes.PATH || this.apValues.verticalActive.get() === msfsSdk.APVerticalModes.CAP) {
                return;
            }
            this.armMode && this.armMode(msfsSdk.APVerticalModes.PATH);
        }
        /**
         * Activates PATH mode.
         */
        activatePath() {
            if (this.apValues.verticalActive.get() === msfsSdk.APVerticalModes.PATH) {
                return;
            }
            this.activateMode && this.activateMode(msfsSdk.APVerticalModes.PATH);
        }
        /**
         * Deactivates and disarms PATH mode.
         */
        disarmPath() {
            if (this.apValues.verticalArmed.get() === msfsSdk.APVerticalModes.PATH) {
                this.armMode && this.armMode(msfsSdk.APVerticalModes.NONE);
            }
            if (this.apValues.verticalActive.get() === msfsSdk.APVerticalModes.PATH) {
                this.activateMode && this.activateMode(msfsSdk.APVerticalModes.PITCH);
            }
        }
        /** @inheritDoc */
        update() {
            if (this.vnavComputer) {
                this.vnavComputer.update();
            }
            if (this.glidepathComputer) {
                this.glidepathComputer.update();
            }
            if (this.glidePathGuidance) {
                this.apValues.approachHasGP.set(this.glidePathGuidance.get().approachHasGlidepath);
            }
            if (this.guidance) {
                const guidance = this.guidance.get();
                this.state = guidance.state;
                let hasActivatedMode = false;
                if (guidance.shouldCaptureAltitude) {
                    this.delegateAltCap(guidance.altitudeToCapture);
                    hasActivatedMode = true;
                }
                if (this.inhibitPathMode && guidance.pathMode !== msfsSdk.VNavPathMode.PathActive) {
                    this.inhibitPathMode = false;
                }
                if (!hasActivatedMode && !this.inhibitPathMode && guidance.pathMode === msfsSdk.VNavPathMode.PathActive) {
                    this.activatePath();
                    hasActivatedMode = true;
                }
                else if (guidance.pathMode === msfsSdk.VNavPathMode.PathArmed) {
                    this.armPath();
                }
                else {
                    this.disarmPath();
                }
                if (guidance.armedClimbMode !== msfsSdk.APVerticalModes.NONE) {
                    if (!hasActivatedMode && guidance.shouldActivateClimbMode) {
                        this.activateClimb(guidance.armedClimbMode);
                    }
                    else {
                        this.armClimb(guidance.armedClimbMode);
                    }
                }
                else {
                    this.disarmClimb();
                }
            }
        }
    }

    exports.FmaVNavState = void 0;
    (function (FmaVNavState) {
        FmaVNavState[FmaVNavState["OFF"] = 0] = "OFF";
        FmaVNavState[FmaVNavState["ARMED"] = 1] = "ARMED";
        FmaVNavState[FmaVNavState["ACTIVE"] = 2] = "ACTIVE";
    })(exports.FmaVNavState || (exports.FmaVNavState = {}));

    /**
     * A Garmin flight management system.
     */
    class Fms {
        // eslint-disable-next-line jsdoc/require-jsdoc
        constructor(isPrimary, bus, flightPlanner, verticalPathCalculator, arg5, procedureLegMapper, visualApproachOptions) {
            var _a, _b, _c, _d, _e, _f, _g, _h, _j;
            this.isPrimary = isPrimary;
            this.bus = bus;
            this.flightPlanner = flightPlanner;
            this.verticalPathCalculator = verticalPathCalculator;
            this.eventSubscriber = this.bus.getSubscriber();
            this.publisher = this.bus.getPublisher();
            this.ppos = new msfsSdk.GeoPoint(0, 0);
            this.facRepo = msfsSdk.FacilityRepository.getRepository(this.bus);
            /** A facility loader instance. */
            this.facLoader = new msfsSdk.FacilityLoader(this.facRepo);
            this.approachDetails = msfsSdk.ObjectSubject.create({
                isLoaded: false,
                type: ApproachType.APPROACH_TYPE_UNKNOWN,
                isRnpAr: false,
                bestRnavType: msfsSdk.RnavTypeFlags.None,
                rnavTypeFlags: msfsSdk.RnavTypeFlags.None,
                isCircling: false,
                isVtf: false,
                referenceFacility: null,
                runway: null
            });
            this.needPublishApproachDetails = false;
            this.updateApproachDetailsOpId = 0;
            this.flightPhase = msfsSdk.ObjectSubject.create({
                isApproachActive: false,
                isToFaf: false,
                isPastFaf: false,
                isInMissedApproach: false
            });
            this.needPublishFlightPhase = false;
            this.flightPhaseDebounceTimer = new msfsSdk.DebounceTimer();
            this.updateFlightPhaseFunc = this.updateFlightPhase.bind(this);
            this.activateMaprState = msfsSdk.ConsumerSubject.create(null, false);
            this.cdiSource = { type: msfsSdk.NavSourceType.Gps, index: 1 };
            this.lnavTrackedLegIndex = msfsSdk.ConsumerSubject.create(null, 0);
            this.isObsActive = msfsSdk.ConsumerSubject.create(null, false);
            this.obsCourse = msfsSdk.ConsumerSubject.create(null, 0);
            this.needConvertObsToDtoSimVar = `L:Garmin_Need_OBS_Convert_DirectTo${this.flightPlanner.id === '' ? '' : `_${this.flightPlanner.id}`}`;
            this.insertApproachOpId = 0;
            let options;
            if (typeof arg5 === 'object') {
                options = arg5;
            }
            else {
                options = {
                    isAdvancedVnav: arg5,
                    procedureLegMapper,
                    visualApproachOptions
                };
            }
            const fmsTopicSuffix = this.flightPlanner.id === '' ? '' : `_${this.flightPlanner.id}`;
            this.fmsTopicMap = {
                'fms_approach_details': `fms_approach_details${fmsTopicSuffix}`,
                'fms_flight_phase': `fms_flight_phase${fmsTopicSuffix}`,
                'fms_approach_activate': `fms_approach_activate${fmsTopicSuffix}`,
                'approach_supports_gp': `approach_supports_gp${fmsTopicSuffix}`,
                'fms_approach_details_sync': `fms_approach_details_sync${fmsTopicSuffix}`
            };
            this.lnavIndex = (_a = options.lnavIndex) !== null && _a !== void 0 ? _a : 0;
            if (!msfsSdk.LNavUtils.isValidLNavIndex(this.lnavIndex)) {
                throw new Error(`Fms: invalid LNAV index (${this.lnavIndex}) specified (must be a non-negative integer)`);
            }
            this.useSimObsState = (_b = options.useSimObsState) !== null && _b !== void 0 ? _b : true;
            const lnavTopicSuffix = msfsSdk.LNavUtils.getEventBusTopicSuffix(this.lnavIndex);
            this.lnavControlTopicMap = {
                'suspend_sequencing': `suspend_sequencing${lnavTopicSuffix}`,
                'lnav_inhibit_next_sequence': `lnav_inhibit_next_sequence${lnavTopicSuffix}`,
                'activate_missed_approach': `activate_missed_approach${lnavTopicSuffix}`
            };
            this.obsControlTopicMap = {
                'lnav_obs_set_active': `lnav_obs_set_active${lnavTopicSuffix}`,
            };
            this.vnavIndex = this.verticalPathCalculator ? (_c = options.vnavIndex) !== null && _c !== void 0 ? _c : 0 : -1;
            if (this.verticalPathCalculator && !msfsSdk.VNavUtils.isValidVNavIndex(this.vnavIndex)) {
                throw new Error(`Fms: invalid VNAV index (${this.vnavIndex}) specified (must be a non-negative integer)`);
            }
            if (this.vnavIndex >= 0) {
                const vnavTopicSuffix = msfsSdk.VNavUtils.getEventBusTopicSuffix(this.vnavIndex);
                this.vnavControlTopicMap = {
                    'vnav_set_vnav_direct_to': `vnav_set_vnav_direct_to${vnavTopicSuffix}`,
                };
            }
            this.cdiId = (_d = options.cdiId) !== null && _d !== void 0 ? _d : '';
            const cdiTopicSuffix = msfsSdk.CdiUtils.getEventBusTopicSuffix(this.cdiId);
            this.cdiControlTopicMap = {
                'cdi_src_set': `cdi_src_set${cdiTopicSuffix}`,
            };
            this.isAdvancedVnav = (_e = options.isAdvancedVnav) !== null && _e !== void 0 ? _e : false;
            this.procedureLegMapFunc = (_f = options.procedureLegMapper) !== null && _f !== void 0 ? _f : (leg => leg);
            this.visualApproachOptions = Object.assign({}, (_g = options.visualApproachOptions) !== null && _g !== void 0 ? _g : Fms.DEFAULT_VISUAL_APPROACH_OPTIONS);
            this.navRadioIndexes = (_h = options.navRadioIndexes) !== null && _h !== void 0 ? _h : [1, 2];
            this.disableApproachAvailablePublish = (_j = options.disableApproachAvailablePublish) !== null && _j !== void 0 ? _j : false;
            const sub = this.bus.getSubscriber();
            sub.on('gps-position').atFrequency(1).handle(pos => this.ppos.set(pos.lat, pos.long));
            sub.on(`cdi_select${cdiTopicSuffix}`).handle(source => this.cdiSource = source);
            this.flightPlanner.onEvent('fplIndexChanged').handle(() => { this.scheduleUpdateFlightPhase(); });
            this.flightPlanner.onEvent('fplActiveLegChange').handle(e => {
                if (e.planIndex === this.flightPlanner.activePlanIndex && e.type === msfsSdk.ActiveLegType.Lateral) {
                    this.scheduleUpdateFlightPhase();
                }
            });
            this.flightPlanner.onEvent('fplSegmentChange').handle(e => {
                if (e.planIndex === this.flightPlanner.activePlanIndex) {
                    this.scheduleUpdateFlightPhase();
                }
            });
            this.flightPlanner.onEvent('fplLegChange').handle(e => {
                if (e.planIndex === this.flightPlanner.activePlanIndex) {
                    this.scheduleUpdateFlightPhase();
                }
            });
            this.flightPlanner.onEvent('fplLoaded').handle(e => {
                if (e.planIndex === this.flightPlanner.activePlanIndex) {
                    this.updateApproachDetails();
                    this.scheduleUpdateFlightPhase();
                }
            });
            this.flightPlanner.onEvent('fplCopied').handle(e => {
                if (e.planIndex === this.flightPlanner.activePlanIndex) {
                    this.updateApproachDetails();
                    this.scheduleUpdateFlightPhase();
                }
            });
            if (this.isPrimary) {
                this.initObsDeactivationListener();
            }
            this.navActiveFreqs = {
                1: msfsSdk.ConsumerValue.create(sub.on('nav_active_frequency_1'), 0),
                2: msfsSdk.ConsumerValue.create(sub.on('nav_active_frequency_2'), 0),
                3: msfsSdk.ConsumerValue.create(sub.on('nav_active_frequency_3'), 0),
                4: msfsSdk.ConsumerValue.create(sub.on('nav_active_frequency_3'), 0)
            };
            this.activateMaprState.setConsumer(sub.on(this.lnavControlTopicMap['activate_missed_approach']));
            this.activateMaprState.sub(activate => {
                if (activate) {
                    this.publisher.pub(this.lnavControlTopicMap['suspend_sequencing'], false, true, false);
                }
            }, true);
            this.indicatedAlt = msfsSdk.ConsumerValue.create(sub.on('indicated_alt'), 0);
            this.lnavTrackedLegIndex.setConsumer(sub.on(`lnav_tracked_leg_index${lnavTopicSuffix}`));
            this.lnavLegDistanceRemaining = msfsSdk.ConsumerValue.create(sub.on(`lnav_leg_distance_remaining${lnavTopicSuffix}`), 0);
            this.isObsActive.setConsumer(sub.on(this.useSimObsState ? 'gps_obs_active' : `lnav_obs_active${lnavTopicSuffix}`));
            this.obsCourse.setConsumer(sub.on(this.useSimObsState ? 'gps_obs_value' : `lnav_obs_course${lnavTopicSuffix}`));
            this.approachDetails.sub(() => { this.needPublishApproachDetails = true; });
            this.flightPhase.sub(() => { this.needPublishFlightPhase = true; });
            // Publish initial approach details and flight phase to have cached values available.
            this.publisher.pub(this.fmsTopicMap['fms_approach_details'], this.approachDetails.get(), false, true);
            this.publisher.pub(this.fmsTopicMap['fms_flight_phase'], this.flightPhase.get(), false, true);
            sub.on(this.fmsTopicMap['fms_approach_details_sync']).handle(this.onApproachDetailsSet.bind(this));
        }
        /**
         * Initializes a listener which listens for OBS deactivation and converts the deactivated OBS to an on-route
         * Direct-To.
         */
        initObsDeactivationListener() {
            this.isObsActive.sub(isActive => {
                if (isActive) {
                    SimVar.SetSimVarValue(this.needConvertObsToDtoSimVar, msfsSdk.SimVarValueType.Bool, true);
                }
                else if (SimVar.GetSimVarValue(this.needConvertObsToDtoSimVar, msfsSdk.SimVarValueType.Bool) !== 0) {
                    SimVar.SetSimVarValue(this.needConvertObsToDtoSimVar, msfsSdk.SimVarValueType.Bool, false);
                    this.convertObsToDirectTo();
                }
            }, true);
        }
        /**
         * Gets an event bus subscriber for topics published by this FMS.
         * @returns An event bus subscriber for topics published by this flight planner.
         */
        getEventSubscriber() {
            return this.eventSubscriber;
        }
        /**
         * Subscribes to one of the event bus topics published by this FMS.
         * @param baseTopic The base name of the topic to which to subscribe.
         * @returns A consumer for the specified event bus topic.
         */
        onEvent(baseTopic) {
            return this.eventSubscriber.on(this.fmsTopicMap[baseTopic]);
        }
        /**
         * Initializes the primary flight plan. Does nothing if the primary flight plan already exists.
         *
         * @param force Whether to force a new primary flight plan to be created, even if one already exists
         */
        async initPrimaryFlightPlan(force = false) {
            if (!force && this.flightPlanner.hasFlightPlan(Fms.PRIMARY_PLAN_INDEX)) {
                return;
            }
            this.flightPlanner.createFlightPlan(Fms.PRIMARY_PLAN_INDEX);
            await this.emptyPrimaryFlightPlan();
        }
        /**
         * Checks whether an indexed flight plan exists.
         * @param index A flight plan index.
         * @returns Whether a flight plan at the specified index exists.
         */
        hasFlightPlan(index) {
            return this.flightPlanner.hasFlightPlan(index);
        }
        /**
         * Gets a specified flightplan, or by default the primary flight plan.
         * @param index The index of the flight plan.
         * @returns the requested flight plan
         * @throws Error if no flight plan exists at the specified index.
         */
        getFlightPlan(index = Fms.PRIMARY_PLAN_INDEX) {
            return this.flightPlanner.getFlightPlan(index);
        }
        /**
         * Checks whether the primary flight plan exists.
         * @returns Whether the primary flight plan exists.
         */
        hasPrimaryFlightPlan() {
            return this.flightPlanner.hasFlightPlan(Fms.PRIMARY_PLAN_INDEX);
        }
        /**
         * Gets the primary flight plan.
         * @returns The primary flight plan.
         * @throws Error if the primary flight plan does not exist.
         */
        getPrimaryFlightPlan() {
            return this.flightPlanner.getFlightPlan(Fms.PRIMARY_PLAN_INDEX);
        }
        /**
         * Checks whether the Direct To Random flight plan exists.
         * @returns Whether the Direct To Random flight plan exists.
         */
        hasDirectToFlightPlan() {
            return this.flightPlanner.hasFlightPlan(Fms.DTO_RANDOM_PLAN_INDEX);
        }
        /**
         * Gets the Direct To Random flight plan.
         * @returns The Direct To Random flight plan.
         * @throws Error if the Direct To Random flight plan does not exist.
         */
        getDirectToFlightPlan() {
            return this.flightPlanner.getFlightPlan(Fms.DTO_RANDOM_PLAN_INDEX);
        }
        /**
         * Gets the approach runway:
         * @returns Selected approach runway
         */
        getApproachRunway() {
            return this.approachDetails.get().runway;
        }
        /**
         * Sets the name of the flight plan.
         * @param planIndex The index of the plan the change the name for.
         * @param name The new name for the flight plan.
         */
        setFlightPlanName(planIndex, name) {
            this.getFlightPlan(planIndex).setUserData(exports.FmsFplUserDataKey.Name, name);
        }
        /**
         * Clears the name of the flight plan.
         * @param planIndex The index of the plan the clear the name for.
         */
        deleteFlightPlanName(planIndex) {
            this.getFlightPlan(planIndex).deleteUserData(exports.FmsFplUserDataKey.Name);
        }
        /**
         * Schedules a flight phase update operation if one is not already pending.
         */
        scheduleUpdateFlightPhase() {
            // Debounce the update so we aren't spamming it every time we get a long sequence of consecutive flight plan events.
            if (this.flightPhaseDebounceTimer.isPending()) {
                return;
            }
            this.flightPhaseDebounceTimer.schedule(this.updateFlightPhaseFunc, 0);
        }
        /**
         * Updates flight phase information.
         */
        updateFlightPhase() {
            var _a, _b, _c, _d;
            let isApproachActive = false;
            let isToFaf = false;
            let isPastFaf = false;
            let isInMissedApproach = false;
            if (this.flightPlanner.hasActiveFlightPlan()) {
                const activePlan = this.flightPlanner.getActiveFlightPlan();
                if (activePlan.length > 0) {
                    const activeSegment = activePlan.getSegment(activePlan.getSegmentIndex(Math.min(activePlan.length - 1, activePlan.activeLateralLeg)));
                    if (activeSegment.segmentType === msfsSdk.FlightPlanSegmentType.Approach && activePlan.activeLateralLeg - activeSegment.offset > 0) {
                        isApproachActive = true;
                        if (activePlan.activeLateralLeg - activeSegment.offset < activeSegment.legs.length
                            ? msfsSdk.BitFlags.isAll((_b = (_a = activeSegment.legs[activePlan.activeLateralLeg - activeSegment.offset]) === null || _a === void 0 ? void 0 : _a.flags) !== null && _b !== void 0 ? _b : 0, msfsSdk.LegDefinitionFlags.MissedApproach)
                            : msfsSdk.BitFlags.isAll((_d = (_c = activeSegment.legs[activeSegment.legs.length - 1]) === null || _c === void 0 ? void 0 : _c.flags) !== null && _d !== void 0 ? _d : 0, msfsSdk.LegDefinitionFlags.MissedApproach)) {
                            isPastFaf = true;
                            isInMissedApproach = true;
                        }
                        else {
                            // Find the faf
                            let fafSegmentLegIndex = -1;
                            for (let i = activeSegment.legs.length - 1; i >= 0; i--) {
                                if (msfsSdk.BitFlags.isAll(activeSegment.legs[i].leg.fixTypeFlags, msfsSdk.FixTypeFlags.FAF)) {
                                    fafSegmentLegIndex = i;
                                    break;
                                }
                            }
                            const fafGlobalLegIndex = activeSegment.offset + fafSegmentLegIndex;
                            isToFaf = activePlan.activeLateralLeg === fafGlobalLegIndex;
                            isPastFaf = activePlan.activeLateralLeg > fafGlobalLegIndex;
                        }
                    }
                }
            }
            this.flightPhase.set('isApproachActive', isApproachActive);
            this.flightPhase.set('isToFaf', isToFaf);
            this.flightPhase.set('isPastFaf', isPastFaf);
            this.flightPhase.set('isInMissedApproach', isInMissedApproach);
            if (this.needPublishFlightPhase) {
                this.needPublishFlightPhase = false;
                this.publisher.pub(this.fmsTopicMap['fms_flight_phase'], Object.assign({}, this.flightPhase.get()), false, true);
            }
            const flightPhase = this.flightPhase.get();
            if (this.isPrimary) {
                if (!this.disableApproachAvailablePublish) {
                    this.publisher.pub('approach_available', flightPhase.isApproachActive, true, true);
                }
                this.publisher.pub(this.fmsTopicMap['approach_supports_gp'], this.doesApproachSupportGp(), true, true);
                // If we are in the missed approach, make sure that the activate missed approach state reflects this.
                if (flightPhase.isInMissedApproach && !this.activateMaprState.get()) {
                    this.publisher.pub(this.lnavControlTopicMap['activate_missed_approach'], true, true, true);
                }
            }
        }
        /**
         * A method to check the current approach state.
         */
        async updateApproachDetails() {
            var _a, _b, _c, _d, _e;
            const opId = ++this.updateApproachDetailsOpId;
            const plan = this.getFlightPlan();
            let approachLoaded = false;
            let approachType = ApproachType.APPROACH_TYPE_UNKNOWN;
            let approachRnavType = msfsSdk.RnavTypeFlags.None;
            let approachRnavTypeFlags = msfsSdk.RnavTypeFlags.None;
            let approachIsCircling = false;
            let approachIsVtf = false;
            let referenceFacility = null;
            let approachRunway = null;
            const visualApproachData = plan.getUserData(exports.FmsFplUserDataKey.VisualApproach);
            const vfrApproachData = plan.getUserData(exports.FmsFplUserDataKey.VfrApproach);
            if (plan.destinationAirport && (plan.procedureDetails.approachIndex > -1 || visualApproachData !== undefined || vfrApproachData)) {
                approachLoaded = true;
                const facility = await this.facLoader.getFacility(msfsSdk.FacilityType.Airport, plan.destinationAirport);
                if (opId !== this.updateApproachDetailsOpId) {
                    return;
                }
                if (plan.procedureDetails.approachIndex > -1) {
                    const approach = facility.approaches[plan.procedureDetails.approachIndex];
                    if (approach) {
                        approachType = approach.approachType;
                        approachRnavType = FmsUtils.getBestRnavType(approach.rnavTypeFlags);
                        approachRnavTypeFlags = approach.rnavTypeFlags;
                        approachIsCircling = !approach.runway;
                        approachIsVtf = plan.procedureDetails.approachTransitionIndex < 0;
                        if (FmsUtils.approachHasNavFrequency(approach)) {
                            referenceFacility = (_a = await msfsSdk.ApproachUtils.getReferenceFacility(approach, this.facLoader)) !== null && _a !== void 0 ? _a : null;
                        }
                        approachRunway = (_b = msfsSdk.RunwayUtils.matchOneWayRunway(facility, approach.runwayNumber, approach.runwayDesignator)) !== null && _b !== void 0 ? _b : null;
                    }
                }
                else if (visualApproachData) {
                    approachType = msfsSdk.AdditionalApproachType.APPROACH_TYPE_VISUAL;
                    approachRunway = (_c = msfsSdk.RunwayUtils.matchOneWayRunwayFromDesignation(facility, visualApproachData.runwayDesignation)) !== null && _c !== void 0 ? _c : null;
                    approachIsVtf = visualApproachData.isVtf;
                }
                else if (vfrApproachData) {
                    const approach = facility.approaches[vfrApproachData.approachIndex];
                    if (approach) {
                        if (FmsUtils.approachHasNavFrequency(approach)) {
                            referenceFacility = (_d = await msfsSdk.ApproachUtils.getReferenceFacility(approach, this.facLoader)) !== null && _d !== void 0 ? _d : null;
                        }
                        approachRunway = approach.runway
                            ? (_e = msfsSdk.RunwayUtils.matchOneWayRunway(facility, approach.runwayNumber, approach.runwayDesignator)) !== null && _e !== void 0 ? _e : null
                            : null;
                    }
                    approachType = exports.GarminAdditionalApproachType.APPROACH_TYPE_VFR;
                    approachIsCircling = !approach.runway;
                    approachIsVtf = vfrApproachData.isVtf;
                }
                if (opId !== this.updateApproachDetailsOpId) {
                    return;
                }
            }
            this.setApproachDetails(false, approachLoaded, approachType, approachRnavType, approachRnavTypeFlags, approachIsCircling, approachIsVtf, referenceFacility, approachRunway);
        }
        /**
         * Removes the direct to existing legs from the primary flight plan. If a direct to existing is currently active,
         * this will effectively cancel it.
         * @param lateralLegIndex The index of the leg to set as the active lateral leg after the removal operation. Defaults
         * to the index of the current active primary flight plan leg.
         */
        removeDirectToExisting(lateralLegIndex) {
            const plan = this.getFlightPlan();
            const directToData = plan.directToData;
            if (directToData && directToData.segmentIndex > -1) {
                // Removing a lateral direct-to also cancels any existing vertical direct-to
                this.publishCancelVerticalDirectTo(FmsUtils.PRIMARY_PLAN_INDEX);
                plan.removeLeg(directToData.segmentIndex, directToData.segmentLegIndex + 1, true);
                plan.removeLeg(directToData.segmentIndex, directToData.segmentLegIndex + 1, true);
                plan.removeLeg(directToData.segmentIndex, directToData.segmentLegIndex + 1, true);
                const activateIndex = lateralLegIndex !== null && lateralLegIndex !== void 0 ? lateralLegIndex : plan.activeLateralLeg;
                const adjustedActivateIndex = activateIndex - msfsSdk.MathUtils.clamp(activateIndex - (plan.getSegment(directToData.segmentIndex).offset + directToData.segmentLegIndex), 0, 3);
                plan.setDirectToData(-1, true);
                plan.setCalculatingLeg(adjustedActivateIndex);
                plan.setLateralLeg(adjustedActivateIndex);
                plan.calculate(0);
            }
        }
        /**
         * Checks whether a leg in the primary flight plan can be manually activated.
         * @param segmentIndex The index of the segment in which the leg resides.
         * @param segmentLegIndex The index of the leg in its segment.
         * @returns Whether the leg can be manually activated.
         */
        canActivateLeg(segmentIndex, segmentLegIndex) {
            const plan = this.hasPrimaryFlightPlan() && this.getPrimaryFlightPlan();
            if (!plan) {
                return false;
            }
            const leg = plan.tryGetLeg(segmentIndex, segmentLegIndex);
            if (!leg || msfsSdk.BitFlags.isAll(leg.flags, msfsSdk.LegDefinitionFlags.DirectTo) || leg === plan.getLeg(0)) {
                return false;
            }
            if (msfsSdk.BitFlags.isAll(leg.flags, msfsSdk.LegDefinitionFlags.VectorsToFinalFaf)) {
                return true;
            }
            switch (leg.leg.type) {
                case msfsSdk.LegType.CF:
                case msfsSdk.LegType.FC:
                case msfsSdk.LegType.FD:
                    return true;
                case msfsSdk.LegType.CI:
                case msfsSdk.LegType.VI:
                case msfsSdk.LegType.FA:
                case msfsSdk.LegType.CA:
                case msfsSdk.LegType.VA:
                case msfsSdk.LegType.VM:
                    return false;
            }
            const prevLeg = plan.getPrevLeg(segmentIndex, segmentLegIndex);
            switch (prevLeg.leg.type) {
                case msfsSdk.LegType.VA:
                case msfsSdk.LegType.CA:
                case msfsSdk.LegType.VM:
                case msfsSdk.LegType.Discontinuity:
                case msfsSdk.LegType.ThruDiscontinuity:
                    return false;
            }
            return true;
        }
        /**
         * Checks whether a leg in the primary flight plan is a valid direct to target.
         * @param segmentIndex The index of the segment in which the leg resides.
         * @param segmentLegIndex The index of the leg in its segment.
         * @returns Whether the leg is a valid direct to target.
         * @throws Error if a leg could not be found at the specified location.
         */
        canDirectTo(segmentIndex, segmentLegIndex) {
            const plan = this.hasPrimaryFlightPlan() && this.getPrimaryFlightPlan();
            if (!plan) {
                return false;
            }
            const leg = plan.tryGetLeg(segmentIndex, segmentLegIndex);
            if (!leg || leg.leg.fixIcao === '' || leg.leg.fixIcao === msfsSdk.ICAO.emptyIcao || msfsSdk.BitFlags.isAny(leg.flags, msfsSdk.LegDefinitionFlags.DirectTo)) {
                return false;
            }
            switch (leg.leg.type) {
                case msfsSdk.LegType.IF:
                case msfsSdk.LegType.TF:
                case msfsSdk.LegType.DF:
                case msfsSdk.LegType.CF:
                case msfsSdk.LegType.AF:
                case msfsSdk.LegType.RF:
                    return true;
            }
            return false;
        }
        /**
         * Gets the current Direct To State.
         * @returns the DirectToState.
         */
        getDirectToState() {
            if (this.flightPlanner.activePlanIndex == 1) {
                const plan = this.getDirectToFlightPlan();
                if (plan.segmentCount > 0 && plan.getSegment(0).segmentType === msfsSdk.FlightPlanSegmentType.RandomDirectTo) {
                    return exports.DirectToState.TORANDOM;
                }
            }
            else if (this.hasPrimaryFlightPlan()) {
                const plan = this.getPrimaryFlightPlan();
                const directDataExists = plan.directToData.segmentIndex > -1 && plan.directToData.segmentLegIndex > -1;
                if (directDataExists && plan.segmentCount >= plan.directToData.segmentIndex
                    && plan.getLegIndexFromLeg(plan.getSegment(plan.directToData.segmentIndex).legs[plan.directToData.segmentLegIndex]) === plan.activeLateralLeg - 3) {
                    return exports.DirectToState.TOEXISTING;
                }
            }
            return exports.DirectToState.NONE;
        }
        /**
         * Gets the ICAO string of the current Direct To target.
         * @returns The ICAO string of the current Direct To target, or undefined if Direct To is not active.
         */
        getDirectToTargetIcao() {
            var _a;
            return (_a = this.getDirectToLeg()) === null || _a === void 0 ? void 0 : _a.fixIcao;
        }
        /**
         * Gets the current DTO Target Flight Plan Leg.
         * @returns the FlightPlanLeg.
         */
        getDirectToLeg() {
            switch (this.getDirectToState()) {
                case exports.DirectToState.TORANDOM: {
                    const plan = this.getDirectToFlightPlan();
                    return plan.getSegment(0).legs[2].leg;
                }
                case exports.DirectToState.TOEXISTING: {
                    const plan = this.getFlightPlan();
                    return plan.getSegment(plan.directToData.segmentIndex).legs[plan.directToData.segmentLegIndex + FmsUtils.DTO_LEG_OFFSET].leg;
                }
            }
            return undefined;
        }
        /**
         * Checks if a segment is the first enroute segment that is not an airway.
         * @param segmentIndex is the segment index of the segment to check
         * @returns whether or not the segment is the first enroute segment that is not an airway.
         */
        isFirstEnrouteSegment(segmentIndex) {
            const plan = this.getFlightPlan();
            for (let i = 0; i < plan.segmentCount; i++) {
                const segment = plan.getSegment(i);
                if (segment.segmentType === msfsSdk.FlightPlanSegmentType.Enroute && !segment.airway) {
                    return i === segmentIndex;
                }
            }
            return false;
        }
        /**
         * Adds a user facility.
         * @param userFacility the facility to add.
         */
        addUserFacility(userFacility) {
            this.facRepo.add(userFacility);
        }
        /**
         * Removes a user facility.
         * @param userFacility the facility to remove.
         */
        removeUserFacility(userFacility) {
            this.facRepo.remove(userFacility);
        }
        /**
         * Adds a visual or runway facility from the FlightPlanLeg.
         * @param leg the leg to build the facility from.
         * @param visualRunwayDesignation is the visual runway this facility belongs to.
         */
        addVisualFacilityFromLeg(leg, visualRunwayDesignation) {
            const fac = {
                icao: leg.fixIcao,
                lat: leg.lat !== undefined ? leg.lat : 0,
                lon: leg.lon !== undefined ? leg.lon : 0,
                approach: `VISUAL ${visualRunwayDesignation}`,
                city: '',
                name: `${visualRunwayDesignation} - ${msfsSdk.ICAO.getIdent(leg.fixIcao)}`,
                region: '',
                magvar: 0
            };
            this.facRepo.add(fac);
        }
        /**
         * Inserts a waypoint into the primary flight plan.
         * @param segmentIndex The index of the flight plan segment into which to insert the waypoint.
         * @param facility The waypoint facility to insert.
         * @param legIndex The index in the segment at which to insert the waypoint. If a leg already exists at the index,
         * the existing leg and all subsequent legs will be shifted to the right. If not defined, the waypoint will be
         * inserted at the end of the segment.
         * @returns The leg that was inserted into the flight plan, or `undefined` if the insertion operation could not be
         * carried out.
         */
        insertWaypoint(segmentIndex, facility, legIndex) {
            var _a, _b, _c, _d;
            const plan = this.getFlightPlan();
            // Check to see if we are trying to insert a leg between a direct-to target leg and the direct-to leg, which is
            // an illegal operation.
            if (segmentIndex === plan.directToData.segmentIndex && legIndex !== undefined) {
                const legIndexDelta = legIndex - plan.directToData.segmentLegIndex;
                if (legIndexDelta > 0 && legIndexDelta <= FmsUtils.DTO_LEG_OFFSET) {
                    return undefined;
                }
            }
            const leg = msfsSdk.FlightPlan.createLeg({
                type: msfsSdk.LegType.TF,
                fixIcao: facility.icao
            });
            //PM Modif add lat, long if this is a user waypoint
            if (!facility.icao || facility.icao == msfsSdk.ICAO.emptyIcao || msfsSdk.ICAO.getFacilityType(facility.icao) == msfsSdk.FacilityType.USR) {
                leg.lat = facility.lat;
                leg.lon = facility.lon;
            }
            //End PM Modif
            let segment = plan.getSegment(segmentIndex);
            const prevLeg = plan.getPrevLeg(segmentIndex, legIndex !== null && legIndex !== void 0 ? legIndex : Infinity);
            const nextLeg = plan.getNextLeg(segmentIndex, legIndex === undefined ? Infinity : legIndex - 1);
            // Make sure we are not inserting a duplicate leg
            if ((prevLeg && this.isDuplicateLeg(prevLeg.leg, leg)) || (nextLeg && this.isDuplicateLeg(leg, nextLeg.leg))) {
                return undefined;
            }
            // Editing the plan prior to an existing vertical direct-to cancels the vertical direct-to
            if (this.verticalPathCalculator) {
                const globalLegIndex = segment.offset + (legIndex !== null && legIndex !== void 0 ? legIndex : segment.legs.length);
                const verticalPlan = this.verticalPathCalculator.getVerticalFlightPlan(Fms.PRIMARY_PLAN_INDEX);
                if (verticalPlan.verticalDirectIndex !== undefined && verticalPlan.verticalDirectIndex >= globalLegIndex) {
                    this.publishCancelVerticalDirectTo(FmsUtils.PRIMARY_PLAN_INDEX);
                }
            }
            // Check if we are trying to insert a waypoint after an airway entry or exit. If so, change the leg indexes so that
            // we are inserting at the beginning of the next segment instead (which is an equivalent operation) in order to
            // correctly trigger (or not) the airway handling section below.
            if (legIndex === undefined || legIndex >= segment.legs.length) {
                const airwayLegType = this.getAirwayLegType(plan, segmentIndex, segment.legs.length - 1);
                switch (airwayLegType) {
                    case exports.AirwayLegType.ENTRY:
                    case exports.AirwayLegType.EXIT_ENTRY:
                    case exports.AirwayLegType.EXIT:
                        segment = plan.getSegment(++segmentIndex);
                        legIndex = 0;
                        break;
                }
            }
            // Deal with whether this insert is in an airway segment
            if (segment.airway) {
                return this.handleAirwayInsertLeg(plan, segmentIndex, leg, legIndex);
            }
            // If we are inserting a leg before a VTF faf leg, we need to check whether the leg terminates at the same
            // facility as the leg prior to the faf in the published procedure.
            if (legIndex !== undefined && segment.segmentType === msfsSdk.FlightPlanSegmentType.Approach && FmsUtils.isVtfApproachLoaded(plan)) {
                const vtfFafLeg = FmsUtils.getApproachVtfLeg(plan);
                if (vtfFafLeg !== undefined) {
                    const vtfFafLegIndex = segment.legs.indexOf(vtfFafLeg);
                    const discoLegExists = msfsSdk.BitFlags.isAll((_b = (_a = segment.legs[vtfFafLegIndex - 1]) === null || _a === void 0 ? void 0 : _a.flags) !== null && _b !== void 0 ? _b : 0, msfsSdk.LegDefinitionFlags.VectorsToFinal);
                    if (vtfFafLeg !== undefined && legIndex === vtfFafLegIndex - (discoLegExists ? 1 : 0)) {
                        const publishedLegIcao = (_c = plan.getUserData(Fms.VTF_FAF_DATA_KEY)) !== null && _c !== void 0 ? _c : '';
                        const legTerminatorIcao = (_d = msfsSdk.FlightPlanUtils.getTerminatorIcao(leg)) !== null && _d !== void 0 ? _d : '';
                        const needDisco = !msfsSdk.ICAO.isFacility(publishedLegIcao) || !msfsSdk.ICAO.isFacility(legTerminatorIcao) || publishedLegIcao !== legTerminatorIcao;
                        if (needDisco && !discoLegExists) {
                            plan.addLeg(segmentIndex, msfsSdk.FlightPlan.createLeg({ type: msfsSdk.LegType.ThruDiscontinuity }), vtfFafLegIndex, msfsSdk.LegDefinitionFlags.VectorsToFinal);
                            if (plan.activeLateralLeg >= segment.offset + vtfFafLegIndex) {
                                plan.setLateralLeg(plan.activeLateralLeg + 1);
                                plan.setCalculatingLeg(plan.activeCalculatingLeg + 1);
                            }
                        }
                        else if (!needDisco && discoLegExists) {
                            plan.removeLeg(segmentIndex, vtfFafLegIndex - 1);
                            if (plan.activeLateralLeg >= segment.offset + vtfFafLegIndex) {
                                plan.setLateralLeg(plan.activeLateralLeg - 1);
                                plan.setCalculatingLeg(plan.activeCalculatingLeg - 1);
                            }
                        }
                    }
                }
            }
            return this.planAddLeg(segmentIndex, leg, legIndex);
        }
        /**
         * Handles inserting a flight plan leg into an airway segment.
         * @param plan The flight plan into which to insert the leg.
         * @param segmentIndex The index of the airway segment.
         * @param leg The leg to insert.
         * @param segmentLegIndex The index in the airway segment at which to insert the leg. If not defined, the leg will be
         * inserted at the end of the segment.
         * @returns The leg that was inserted into the airway segment, or `undefined` if the segment does not exist or is not
         * an airway segment.
         */
        handleAirwayInsertLeg(plan, segmentIndex, leg, segmentLegIndex) {
            var _a, _b;
            const segment = plan.tryGetSegment(segmentIndex);
            if (segment === null || segment.airway === undefined) {
                return undefined;
            }
            segmentLegIndex !== null && segmentLegIndex !== void 0 ? segmentLegIndex : (segmentLegIndex = segment.legs.length - 1);
            // Get the displaced legs from the airway segment
            const legsToMove = [];
            const legsLength = segment.legs.length;
            for (let i = segmentLegIndex; i < legsLength; i++) {
                legsToMove.push(segment.legs[i].leg);
            }
            // Save the airway name
            const airway = (_b = (_a = segment.airway) === null || _a === void 0 ? void 0 : _a.split('.')[0]) !== null && _b !== void 0 ? _b : '';
            const prevSegment = plan.getSegment(segmentIndex - 1);
            const nextSegment = plan.getSegment(segmentIndex + 1);
            const isPrevSegmentEnroute = prevSegment.segmentType === msfsSdk.FlightPlanSegmentType.Enroute && prevSegment.airway === undefined;
            const needFirstAirwaySegment = segmentLegIndex > 0; // We don't need to keep the original airway segment around if we've displaced all of its enroute waypoints.
            const needSecondAirwaySegment = legsToMove.length > 2; // Only create a second airway segment if we've displaced at least three waypoints in the original airway
            const needNewEnrouteSegment = !isPrevSegmentEnroute || (needFirstAirwaySegment
                && (needSecondAirwaySegment || nextSegment.airway !== undefined || nextSegment.segmentType !== msfsSdk.FlightPlanSegmentType.Enroute));
            const firstAirwaySegmentIndex = needFirstAirwaySegment ? segmentIndex : -1;
            let enrouteSegment;
            let secondAirwaySegment;
            if (needSecondAirwaySegment) {
                secondAirwaySegment = plan.getSegment(this.planInsertSegmentOfType(msfsSdk.FlightPlanSegmentType.Enroute, segmentIndex + 1));
            }
            if (needNewEnrouteSegment) {
                enrouteSegment = plan.getSegment(this.planInsertSegmentOfType(msfsSdk.FlightPlanSegmentType.Enroute, segmentIndex + 1));
            }
            else {
                if (needFirstAirwaySegment) {
                    enrouteSegment = plan.getSegment(segmentIndex + 1);
                }
                else {
                    enrouteSegment = plan.getSegment(segmentIndex - 1);
                }
            }
            // Add the inserted leg to its enroute segment
            const legDefinition = this.planAddLeg(enrouteSegment.segmentIndex, leg);
            if (!needFirstAirwaySegment) {
                this.planRemoveSegment(segmentIndex);
            }
            else {
                for (let i = legsLength - 1; i >= segmentLegIndex; i--) {
                    this.planRemoveLeg(segmentIndex, i, true, true);
                }
            }
            if (legsToMove.length > 0) {
                this.planAddLeg(enrouteSegment.segmentIndex, legsToMove[0]); // Always add first displaced waypoint to the enroute segment in case it is an airway entry
                const toAddSegmentIndex = secondAirwaySegment ? secondAirwaySegment.segmentIndex : enrouteSegment.segmentIndex;
                for (let i = 1; i < legsToMove.length; i++) {
                    this.planAddLeg(toAddSegmentIndex, legsToMove[i]);
                }
            }
            // Update names of the airway segments as appropriate.
            if (firstAirwaySegmentIndex >= 0) {
                segment.airway = airway + '.' + segment.legs[segmentLegIndex - 1].name;
                plan.setAirway(firstAirwaySegmentIndex, segment.airway);
            }
            if (secondAirwaySegment) {
                secondAirwaySegment.airway = airway + '.' + secondAirwaySegment.legs[secondAirwaySegment.legs.length - 1].name;
                plan.setAirway(secondAirwaySegment.segmentIndex, secondAirwaySegment.airway);
            }
            return legDefinition;
        }
        /**
         * Removes a leg to a waypoint from the primary flight plan.
         * @param segmentIndex The index of the segment containing the leg to remove.
         * @param segmentLegIndex The index of the leg to remove in its segment.
         * @returns Whether the waypoint was successfully removed.
         */
        removeWaypoint(segmentIndex, segmentLegIndex) {
            var _a, _b, _c, _d;
            const plan = this.hasPrimaryFlightPlan() && this.getPrimaryFlightPlan();
            if (!plan) {
                return false;
            }
            const leg = plan.tryGetLeg(segmentIndex, segmentLegIndex);
            if (!leg || msfsSdk.BitFlags.isAny(leg.leg.fixTypeFlags, msfsSdk.FixTypeFlags.FAF | msfsSdk.FixTypeFlags.MAP)) {
                return false;
            }
            const wasActiveLegInApproach = this.getDirectToState() === exports.DirectToState.NONE && plan.activeLateralLeg >= ((_b = (_a = FmsUtils.getApproachSegment(plan)) === null || _a === void 0 ? void 0 : _a.offset) !== null && _b !== void 0 ? _b : Infinity);
            const legDeleted = this.planRemoveLeg(segmentIndex, segmentLegIndex);
            const nextLeg = plan.tryGetLeg(segmentIndex, segmentLegIndex);
            if (legDeleted && nextLeg && (nextLeg.leg.type === msfsSdk.LegType.HA || nextLeg.leg.type === msfsSdk.LegType.HM || nextLeg.leg.type === msfsSdk.LegType.HF)) {
                if (plan.tryGetLeg(segmentIndex, segmentLegIndex)) {
                    this.planRemoveLeg(segmentIndex, segmentLegIndex, true, true, true);
                }
            }
            // If removing the leg caused the active leg to move from before the approach into the approach, activate the
            // approach instead.
            if (!wasActiveLegInApproach) {
                const isActiveLegInApproach = this.getDirectToState() === exports.DirectToState.NONE && plan.activeLateralLeg >= ((_d = (_c = FmsUtils.getApproachSegment(plan)) === null || _c === void 0 ? void 0 : _c.offset) !== null && _d !== void 0 ? _d : Infinity);
                if (isActiveLegInApproach) {
                    if (this.isApproachVtf()) {
                        this.activateVtf();
                    }
                    else {
                        this.activateApproach();
                    }
                }
            }
            return legDeleted;
        }
        /**
         * Gets the airway leg type of a flight plan leg.
         * @param plan The flight plan containing the query leg.
         * @param segmentIndex The index of the flight plan segment containing the query leg.
         * @param segmentLegIndex The index of the query leg in its segment.
         * @returns The airway leg type of the query leg.
         */
        getAirwayLegType(plan, segmentIndex, segmentLegIndex) {
            const isLegDtoTarget = plan.directToData.segmentIndex === segmentIndex && plan.directToData.segmentLegIndex === segmentLegIndex;
            const segment = plan.getSegment(segmentIndex);
            const segmentIsAirway = segment.airway !== undefined;
            const nextSegmentIsAirway = segmentIndex + 1 < plan.segmentCount && plan.getSegment(segmentIndex + 1).airway !== undefined;
            const legIsLast = segmentLegIndex + (isLegDtoTarget ? FmsUtils.DTO_LEG_OFFSET : 0) === segment.legs.length - 1;
            if (legIsLast && nextSegmentIsAirway) {
                return segmentIsAirway ? exports.AirwayLegType.EXIT_ENTRY : exports.AirwayLegType.ENTRY;
            }
            if (segmentIsAirway) {
                return legIsLast ? exports.AirwayLegType.EXIT : exports.AirwayLegType.ONROUTE;
            }
            return exports.AirwayLegType.NONE;
        }
        /**
         * Method to get the distance of an airway segment.
         * @param segmentIndex is the index of the segment of the airway.
         * @returns the cumulative distance for the airway segment.
         */
        getAirwayDistance(segmentIndex) {
            var _a, _b, _c, _d;
            const plan = this.getFlightPlan();
            const segment = plan.getSegment(segmentIndex);
            const entrySegment = plan.getSegment(segmentIndex - 1);
            const entryCumulativeDistance = (_b = (_a = entrySegment.legs[entrySegment.legs.length - 1]) === null || _a === void 0 ? void 0 : _a.calculated) === null || _b === void 0 ? void 0 : _b.cumulativeDistance;
            const exitCumulativeDistance = (_d = (_c = segment.legs[segment.legs.length - 1]) === null || _c === void 0 ? void 0 : _c.calculated) === null || _d === void 0 ? void 0 : _d.cumulativeDistance;
            return exitCumulativeDistance && entryCumulativeDistance ? exitCumulativeDistance - entryCumulativeDistance : -1;
        }
        /**
         * Sets the primary flight plan's origin airport and runway. Any departure procedure loaded in the flight plan will
         * be removed.
         * @param airport The origin airport to set or its ICAO, or `undefined` if the origin airport should be cleared from
         * the flight plan.
         * @param runway The origin runway to set, or `undefined` if the origin runway should be cleared from the flight
         * plan. Ignored if `airport` is `undefined`.
         */
        setOrigin(airport, runway) {
            const airportIcao = typeof airport === 'object' ? airport.icao : airport;
            const plan = this.getPrimaryFlightPlan();
            const segmentIndex = this.ensureOnlyOneSegmentOfType(msfsSdk.FlightPlanSegmentType.Departure);
            plan.setDeparture();
            if (airportIcao !== undefined && msfsSdk.ICAO.isFacility(airportIcao, msfsSdk.FacilityType.Airport)) {
                plan.setOriginAirport(airportIcao);
                plan.setOriginRunway(runway);
                this.planClearSegment(segmentIndex, msfsSdk.FlightPlanSegmentType.Departure);
                this.planAddOriginDestinationLeg(true, segmentIndex, airportIcao, runway);
                const prevLeg = plan.getPrevLeg(segmentIndex, 1);
                const nextLeg = plan.getNextLeg(segmentIndex, 0);
                if (prevLeg && nextLeg && this.isDuplicateLeg(prevLeg.leg, nextLeg.leg)) {
                    this.planRemoveDuplicateLeg(prevLeg, nextLeg);
                }
            }
            else {
                plan.removeOriginAirport();
                this.planClearSegment(segmentIndex, msfsSdk.FlightPlanSegmentType.Departure);
            }
            plan.calculate(0);
        }
        /**
         * Sets the primary flight plan's destination airport and runway.
         * @param airport The destination airport to set or its ICAO, or `undefined` if the destination airport should be
         * cleared from the flight plan.
         * @param runway The destination runway to set, or `undefined` if the destination runway should be cleared from the
         * flight plan. Ignored if `airport` is `undefined`.
         */
        setDestination(airport, runway) {
            const airportIcao = typeof airport === 'object' ? airport.icao : airport;
            const plan = this.getFlightPlan();
            const destSegmentIndex = this.ensureOnlyOneSegmentOfType(msfsSdk.FlightPlanSegmentType.Destination);
            if (airportIcao !== undefined && msfsSdk.ICAO.isFacility(airportIcao, msfsSdk.FacilityType.Airport)) {
                plan.setDestinationAirport(airportIcao);
                plan.setDestinationRunway(runway);
                this.planClearSegment(destSegmentIndex, msfsSdk.FlightPlanSegmentType.Destination);
                const hasArrival = plan.procedureDetails.arrivalIndex > -1;
                const hasApproach = plan.procedureDetails.approachIndex > -1;
                if (!hasArrival && !hasApproach) {
                    this.planAddOriginDestinationLeg(false, destSegmentIndex, airportIcao, runway);
                }
            }
            else {
                plan.removeDestinationAirport();
                this.planClearSegment(destSegmentIndex, msfsSdk.FlightPlanSegmentType.Destination);
            }
            plan.calculate(0);
        }
        /**
         * Moves any current legs in the destination segment to the end of the last enroute segment.
         */
        moveCurrentDestinationLegToEnroute() {
            const plan = this.getFlightPlan();
            const destinationSegmentIndex = this.ensureOnlyOneSegmentOfType(msfsSdk.FlightPlanSegmentType.Destination);
            const destinationSegment = plan.getSegment(destinationSegmentIndex);
            if (destinationSegment.legs.length > 0) {
                const lastEnrouteSegmentIndex = this.findLastEnrouteSegmentIndex(plan);
                for (let l = 0; l < destinationSegment.legs.length; l++) {
                    const newLeg = Object.assign({}, destinationSegment.legs[l].leg);
                    this.planRemoveLeg(destinationSegmentIndex, l);
                    this.planAddLeg(lastEnrouteSegmentIndex, newLeg);
                }
            }
        }
        /**
         * Method to remove runway or airport legs from segments where they shouldn't exist.
         */
        removeDestLegFromSegments() {
            const plan = this.getFlightPlan();
            const destination = plan.destinationAirport;
            const hasArrival = plan.procedureDetails.arrivalIndex > -1;
            const hasApproach = FmsUtils.isApproachLoaded(plan);
            const destinationSegmentIndex = this.ensureOnlyOneSegmentOfType(msfsSdk.FlightPlanSegmentType.Destination);
            const destinationSegment = plan.getSegment(destinationSegmentIndex);
            if (hasApproach && destination) {
                if (hasArrival) {
                    const arrivalSegmentIndex = this.ensureOnlyOneSegmentOfType(msfsSdk.FlightPlanSegmentType.Arrival);
                    const arrival = plan.getSegment(arrivalSegmentIndex);
                    const lastArrivalLegIcao = arrival.legs[arrival.legs.length - 1].leg.fixIcao;
                    if (lastArrivalLegIcao === destination || lastArrivalLegIcao.search('R') === 0) {
                        this.planRemoveLeg(arrivalSegmentIndex, arrival.legs.length - 1);
                    }
                }
                if (destinationSegment.legs.length > 0) {
                    this.planClearSegment(destinationSegmentIndex, msfsSdk.FlightPlanSegmentType.Destination);
                }
            }
            else if (hasArrival && destination) {
                if (destinationSegment.legs.length > 0) {
                    this.planClearSegment(destinationSegmentIndex, msfsSdk.FlightPlanSegmentType.Destination);
                }
            }
        }
        /**
         * Method to ensure only one segment of a specific type exists in the flight plan and optionally insert it if needed.
         * @param segmentType is the segment type we want to evaluate.
         * @param insert is whether to insert the segment if missing
         * @returns segmentIndex of the only segment of this type in the flight plan.
         */
        ensureOnlyOneSegmentOfType(segmentType, insert = true) {
            const plan = this.getFlightPlan();
            let segmentIndex;
            const selectedSegments = plan.segmentsOfType(segmentType);
            const segmentIndexArray = [];
            for (const element of selectedSegments) {
                segmentIndexArray.push(element.segmentIndex);
            }
            if (segmentIndexArray.length === 0) {
                if (insert) {
                    segmentIndex = this.planInsertSegmentOfType(segmentType);
                }
                else {
                    segmentIndex = -1;
                }
            }
            else if (segmentIndexArray.length > 1) {
                for (let i = 0; i < segmentIndexArray.length; i++) {
                    this.planRemoveSegment(segmentIndexArray[i]);
                }
                segmentIndex = this.planInsertSegmentOfType(segmentType);
            }
            else {
                segmentIndex = segmentIndexArray[0];
            }
            return segmentIndex;
        }
        /**
         * Method to invert the flightplan.
         */
        invertFlightplan() {
            var _a;
            const plan = this.getFlightPlan();
            this.removeDirectToExisting();
            const newOriginIcao = plan.destinationAirport;
            const newDestinationIcao = plan.originAirport;
            const lastEnrouteSegmentIndex = this.findLastEnrouteSegmentIndex(plan);
            if (lastEnrouteSegmentIndex === 1 && plan.getSegment(1).legs.length > 0) {
                //case for when there is only 1 enroute segment and it has at least 1 waypoint, a simple reversal is all that's required.
                const segment = Object.assign({}, plan.getSegment(1));
                this.emptyPrimaryFlightPlan();
                for (let l = segment.legs.length - 1; l >= 0; l--) {
                    plan.addLeg(1, segment.legs[l].leg);
                }
            }
            else if (lastEnrouteSegmentIndex > 1) {
                //case for when there is more than 1 enroute segment we know we have to deal with airways
                const legs = [];
                for (let i = 1; i <= lastEnrouteSegmentIndex; i++) {
                    //create a temporary list of legs that looks like what a flight plan import looks like with ICAO and the airway
                    //we fly FROM the leg on.
                    const oldSegment = plan.getSegment(i);
                    const airway = oldSegment.airway ? (_a = oldSegment.airway) === null || _a === void 0 ? void 0 : _a.split('.')[0] : undefined;
                    for (const leg of oldSegment.legs) {
                        const legListItem = { icao: leg.leg.fixIcao, airway: airway };
                        legs.push(legListItem);
                    }
                }
                //after the array of legs is complete, we just reverse it
                legs.reverse();
                this.emptyPrimaryFlightPlan();
                let currentSegment = 1;
                let lastLegWasAirway = false;
                //last we go through each leg and use the same logic we use for the flight plan import to go through each leg and create airway
                //segments as appropriate for these legs.
                for (let i = 0; i < legs.length; i++) {
                    const wpt = legs[i];
                    const segment = plan.getSegment(currentSegment);
                    if (wpt.airway) {
                        const leg = msfsSdk.FlightPlan.createLeg({
                            type: msfsSdk.LegType.TF,
                            fixIcao: wpt.icao
                        });
                        plan.addLeg(currentSegment, leg);
                        if (!lastLegWasAirway) {
                            plan.insertSegment(currentSegment + 1, msfsSdk.FlightPlanSegmentType.Enroute, wpt.airway);
                            currentSegment += 1;
                        }
                        for (let j = i + 1; j < legs.length; j++) {
                            i++;
                            const airwayLeg = msfsSdk.FlightPlan.createLeg({
                                type: msfsSdk.LegType.TF,
                                fixIcao: legs[j].icao
                            });
                            plan.addLeg(currentSegment, airwayLeg);
                            if (legs[j].airway !== wpt.airway) {
                                lastLegWasAirway = legs[j].airway ? true : false;
                                break;
                            }
                        }
                        plan.setAirway(currentSegment, wpt.airway + '.' + msfsSdk.ICAO.getIdent(legs[i].icao));
                        currentSegment += 1;
                        plan.insertSegment(currentSegment, msfsSdk.FlightPlanSegmentType.Enroute, lastLegWasAirway ? legs[i].airway : undefined);
                    }
                    else {
                        let leg = undefined;
                        leg = msfsSdk.FlightPlan.createLeg({
                            type: msfsSdk.LegType.TF,
                            fixIcao: wpt.icao
                        });
                        if (leg) {
                            plan.addLeg(currentSegment, leg);
                            if (lastLegWasAirway) {
                                plan.setAirway(currentSegment, segment.airway + '.' + msfsSdk.ICAO.getIdent(wpt.icao));
                                currentSegment += 1;
                                plan.insertSegment(currentSegment, msfsSdk.FlightPlanSegmentType.Enroute);
                            }
                            lastLegWasAirway = false;
                        }
                    }
                }
                if (plan.getSegment(currentSegment).airway) {
                    currentSegment += 1;
                    plan.insertSegment(currentSegment, msfsSdk.FlightPlanSegmentType.Enroute);
                }
            }
            else {
                this.emptyPrimaryFlightPlan();
            }
            if (newOriginIcao) {
                this.facLoader.getFacility(msfsSdk.FacilityType.Airport, newOriginIcao).then((facility) => {
                    this.setOrigin(facility);
                });
            }
            if (newDestinationIcao) {
                this.facLoader.getFacility(msfsSdk.FacilityType.Airport, newDestinationIcao).then((facility) => {
                    this.setDestination(facility);
                });
            }
            ++this.updateApproachDetailsOpId;
            this.setApproachDetails(true, false, ApproachType.APPROACH_TYPE_UNKNOWN, msfsSdk.RnavTypeFlags.None, msfsSdk.RnavTypeFlags.None, false, false, null, null);
            plan.deleteUserData(Fms.VTF_FAF_DATA_KEY);
            plan.calculate(0);
        }
        /**
         * Method to add or replace a departure procedure in the flight plan.
         * @param facility is the facility that contains the procedure to add.
         * @param departureIndex is the index of the departure
         * @param departureRunwayIndex is the index of the runway transition
         * @param enrouteTransitionIndex is the index of the enroute transition
         * @param oneWayRunway is the one way runway to set as the origin leg.
         */
        insertDeparture(facility, departureIndex, departureRunwayIndex, enrouteTransitionIndex, oneWayRunway) {
            const plan = this.getFlightPlan();
            plan.setDeparture(facility.icao, departureIndex, enrouteTransitionIndex, departureRunwayIndex);
            let departureSegment = plan.getSegment(this.ensureOnlyOneSegmentOfType(msfsSdk.FlightPlanSegmentType.Departure));
            this.planClearSegment(departureSegment.segmentIndex, msfsSdk.FlightPlanSegmentType.Departure);
            // planClearSegment() actually removes and replaces the segment, so we need to get a reference to the new segment.
            departureSegment = FmsUtils.getDepartureSegment(plan);
            const insertProcedureObject = this.buildDepartureLegs(facility, departureIndex, enrouteTransitionIndex, departureRunwayIndex, oneWayRunway);
            if (oneWayRunway) {
                plan.setOriginAirport(facility.icao);
                plan.setOriginRunway(oneWayRunway);
            }
            else if (plan.originAirport == facility.icao && plan.procedureDetails.originRunway) {
                const originLeg = FmsUtils.buildRunwayLeg(facility, plan.procedureDetails.originRunway, true);
                insertProcedureObject.procedureLegs.splice(0, 1, originLeg);
            }
            else {
                plan.setOriginAirport(facility.icao);
            }
            insertProcedureObject.procedureLegs.forEach(l => this.planAddLeg(departureSegment.segmentIndex, l));
            const nextLeg = plan.getNextLeg(departureSegment.segmentIndex, Infinity);
            const lastDepLeg = departureSegment.legs[departureSegment.legs.length - 1];
            if (nextLeg && lastDepLeg && this.isDuplicateLeg(lastDepLeg.leg, nextLeg.leg)) {
                this.planRemoveDuplicateLeg(lastDepLeg, nextLeg);
            }
            this.autoDesignateProcedureConstraints(plan, departureSegment.segmentIndex);
            plan.calculate(0);
        }
        /**
         * Method to insert the arrival legs.
         * @param facility is the facility to build legs from.
         * @param procedureIndex is the procedure index to build legs from.
         * @param enrouteTransitionIndex is the enroute transition index to build legs from.
         * @param runwayTransitionIndex is the runway transition index to build legs from.
         * @param oneWayRunway is the one way runway, if one is specified in the procedure.
         * @returns InsertProcedureObject to insert into the flight plan.
         */
        buildDepartureLegs(facility, procedureIndex, enrouteTransitionIndex, runwayTransitionIndex, oneWayRunway) {
            const departure = facility.departures[procedureIndex];
            const enRouteTransition = departure.enRouteTransitions[enrouteTransitionIndex];
            const runwayTransition = departure.runwayTransitions[runwayTransitionIndex];
            const insertProcedureObject = { procedureLegs: [] };
            let originLeg;
            if (oneWayRunway) {
                originLeg = FmsUtils.buildRunwayLeg(facility, oneWayRunway, true);
            }
            else {
                originLeg = msfsSdk.FlightPlan.createLeg({
                    lat: facility.lat,
                    lon: facility.lon,
                    type: msfsSdk.LegType.IF,
                    fixIcao: facility.icao
                });
            }
            insertProcedureObject.procedureLegs.push(originLeg);
            if (runwayTransition !== undefined && runwayTransition.legs.length > 0) {
                runwayTransition.legs.forEach((leg) => {
                    const insertLeg = this.procedureLegMapFunc(leg);
                    insertLeg && insertProcedureObject.procedureLegs.push(msfsSdk.FlightPlan.createLeg(insertLeg));
                });
            }
            for (let i = 0; i < departure.commonLegs.length; i++) {
                const leg = msfsSdk.FlightPlan.createLeg(departure.commonLegs[i]);
                if (i == 0 && insertProcedureObject.procedureLegs.length > 0 &&
                    this.isDuplicateIFLeg(insertProcedureObject.procedureLegs[insertProcedureObject.procedureLegs.length - 1], leg)) {
                    insertProcedureObject.procedureLegs[insertProcedureObject.procedureLegs.length - 1] =
                        this.mergeDuplicateLegData(insertProcedureObject.procedureLegs[insertProcedureObject.procedureLegs.length - 1], leg);
                    continue;
                }
                const insertLeg = this.procedureLegMapFunc(leg);
                insertLeg && insertProcedureObject.procedureLegs.push(msfsSdk.FlightPlan.createLeg(insertLeg));
            }
            if (enRouteTransition) {
                for (let i = 0; i < enRouteTransition.legs.length; i++) {
                    const leg = msfsSdk.FlightPlan.createLeg(enRouteTransition.legs[i]);
                    if (i == 0 && insertProcedureObject.procedureLegs.length > 0 &&
                        this.isDuplicateIFLeg(insertProcedureObject.procedureLegs[insertProcedureObject.procedureLegs.length - 1], leg)) {
                        insertProcedureObject.procedureLegs[insertProcedureObject.procedureLegs.length - 1] =
                            this.mergeDuplicateLegData(insertProcedureObject.procedureLegs[insertProcedureObject.procedureLegs.length - 1], leg);
                        continue;
                    }
                    const insertLeg = this.procedureLegMapFunc(enRouteTransition.legs[i]);
                    insertLeg && insertProcedureObject.procedureLegs.push(msfsSdk.FlightPlan.createLeg(insertLeg));
                }
            }
            return insertProcedureObject;
        }
        /**
         * Method to add or replace an arrival procedure in the flight plan.
         * @param facility is the facility that contains the procedure to add.
         * @param arrivalIndex is the index of the arrival procedure.
         * @param arrivalRunwayTransitionIndex is the index of the arrival runway transition.
         * @param enrouteTransitionIndex is the index of the enroute transition.
         * @param arrivalRunway is the one way runway to set as the destination leg.
         */
        insertArrival(facility, arrivalIndex, arrivalRunwayTransitionIndex, enrouteTransitionIndex, arrivalRunway) {
            const plan = this.getFlightPlan();
            plan.setArrival(facility.icao, arrivalIndex, enrouteTransitionIndex, arrivalRunwayTransitionIndex, arrivalRunway);
            if (plan.length > 0 && plan.procedureDetails.approachIndex < 0 && plan.destinationAirport) {
                if (!this.moveDirectToDestinationLeg(plan, msfsSdk.FlightPlanSegmentType.Enroute)) {
                    if (plan.getLeg(plan.activeLateralLeg).leg.fixIcao === plan.destinationAirport && plan.destinationAirport !== facility.icao && plan.activeLateralLeg === plan.length - 1) {
                        const lastEnrouteSegmentIndex = this.findLastEnrouteSegmentIndex(plan);
                        const newDestinationLeg = msfsSdk.FlightPlan.createLeg({ fixIcao: plan.destinationAirport, type: msfsSdk.LegType.TF });
                        this.planAddLeg(lastEnrouteSegmentIndex, newDestinationLeg);
                    }
                }
            }
            if (plan.procedureDetails.approachIndex < 0) {
                plan.setDestinationAirport(facility.icao);
                plan.setDestinationRunway(arrivalRunway);
            }
            let arrivalSegment = plan.getSegment(this.ensureOnlyOneSegmentOfType(msfsSdk.FlightPlanSegmentType.Arrival));
            if (arrivalSegment.legs.length > 0) {
                this.planClearSegment(arrivalSegment.segmentIndex, msfsSdk.FlightPlanSegmentType.Arrival);
                // planClearSegment() actually removes and replaces the segment, so we need to get a reference to the new segment.
                arrivalSegment = FmsUtils.getArrivalSegment(plan);
            }
            const insertProcedureObject = this.buildArrivalLegs(facility, arrivalIndex, enrouteTransitionIndex, arrivalRunwayTransitionIndex, arrivalRunway);
            let directTargetLeg;
            let handleDirectToDestination = false;
            const directToState = this.getDirectToState();
            if (plan.procedureDetails.approachIndex > -1) {
                insertProcedureObject.procedureLegs.pop();
            }
            else if (directToState === exports.DirectToState.TOEXISTING) {
                directTargetLeg = this.getDirectToLeg();
                if ((directTargetLeg === null || directTargetLeg === void 0 ? void 0 : directTargetLeg.fixIcao) === plan.destinationAirport &&
                    (directTargetLeg === null || directTargetLeg === void 0 ? void 0 : directTargetLeg.fixIcao) === insertProcedureObject.procedureLegs[insertProcedureObject.procedureLegs.length - 1].fixIcao) {
                    insertProcedureObject.procedureLegs.pop();
                    handleDirectToDestination = true;
                }
            }
            insertProcedureObject.procedureLegs.forEach(l => this.planAddLeg(arrivalSegment.segmentIndex, l));
            const prevLeg = plan.getPrevLeg(arrivalSegment.segmentIndex, 0);
            const firstArrLeg = arrivalSegment.legs[0];
            if (prevLeg && firstArrLeg && this.isDuplicateLeg(prevLeg.leg, firstArrLeg.leg)) {
                this.planRemoveDuplicateLeg(prevLeg, firstArrLeg);
            }
            this.removeDestLegFromSegments();
            const nextLeg = plan.getNextLeg(arrivalSegment.segmentIndex, Infinity);
            const lastArrLeg = arrivalSegment.legs[arrivalSegment.legs.length - 1];
            if (nextLeg && lastArrLeg && this.isDuplicateLeg(lastArrLeg.leg, nextLeg.leg)) {
                this.planRemoveDuplicateLeg(lastArrLeg, nextLeg);
            }
            if (handleDirectToDestination) {
                this.moveDirectToDestinationLeg(plan, msfsSdk.FlightPlanSegmentType.Arrival, arrivalSegment.segmentIndex);
            }
            else if (directToState === exports.DirectToState.TOEXISTING && directTargetLeg && directTargetLeg.fixIcao === plan.destinationAirport) {
                this.removeDirectToExisting();
                this.createDirectToRandom(plan.destinationAirport);
            }
            this.autoDesignateProcedureConstraints(plan, arrivalSegment.segmentIndex);
            plan.calculate(0);
        }
        /**
         * Method to insert the arrival legs.
         * @param facility is the facility to build legs from.
         * @param procedureIndex is the procedure index to build legs from.
         * @param enrouteTransitionIndex is the enroute transition index to build legs from.
         * @param runwayTransitionIndex is the runway transition index to build legs from.
         * @param arrivalRunway is the one way runway, if one is specified in the procedure.
         * @returns InsertProcedureObject to insert into the flight plan.
         */
        buildArrivalLegs(facility, procedureIndex, enrouteTransitionIndex, runwayTransitionIndex, arrivalRunway) {
            const arrival = facility.arrivals[procedureIndex];
            const enRouteTransition = arrival.enRouteTransitions[enrouteTransitionIndex];
            const runwayTransition = arrival.runwayTransitions[runwayTransitionIndex];
            const insertProcedureObject = { procedureLegs: [] };
            if (enRouteTransition !== undefined && enRouteTransition.legs.length > 0) {
                enRouteTransition.legs.forEach((leg) => {
                    const insertLeg = this.procedureLegMapFunc(leg);
                    insertLeg && insertProcedureObject.procedureLegs.push(msfsSdk.FlightPlan.createLeg(insertLeg));
                });
            }
            for (let i = 0; i < arrival.commonLegs.length; i++) {
                const leg = msfsSdk.FlightPlan.createLeg(arrival.commonLegs[i]);
                if (i == 0 && insertProcedureObject.procedureLegs.length > 0 &&
                    this.isDuplicateIFLeg(insertProcedureObject.procedureLegs[insertProcedureObject.procedureLegs.length - 1], leg)) {
                    insertProcedureObject.procedureLegs[insertProcedureObject.procedureLegs.length - 1] =
                        this.mergeDuplicateLegData(insertProcedureObject.procedureLegs[insertProcedureObject.procedureLegs.length - 1], leg);
                    continue;
                }
                const insertLeg = this.procedureLegMapFunc(leg);
                insertLeg && insertProcedureObject.procedureLegs.push(msfsSdk.FlightPlan.createLeg(insertLeg));
            }
            if (runwayTransition) {
                for (let i = 0; i < runwayTransition.legs.length; i++) {
                    const leg = msfsSdk.FlightPlan.createLeg(runwayTransition.legs[i]);
                    if (i == 0 && insertProcedureObject.procedureLegs.length > 0 &&
                        this.isDuplicateIFLeg(insertProcedureObject.procedureLegs[insertProcedureObject.procedureLegs.length - 1], leg)) {
                        insertProcedureObject.procedureLegs[insertProcedureObject.procedureLegs.length - 1] =
                            this.mergeDuplicateLegData(insertProcedureObject.procedureLegs[insertProcedureObject.procedureLegs.length - 1], leg);
                        continue;
                    }
                    const insertLeg = this.procedureLegMapFunc(leg);
                    insertLeg && insertProcedureObject.procedureLegs.push(msfsSdk.FlightPlan.createLeg(insertLeg));
                }
            }
            const destinationLeg = arrivalRunway
                ? FmsUtils.buildRunwayLeg(facility, arrivalRunway, false)
                : msfsSdk.FlightPlan.createLeg({
                    lat: facility.lat,
                    lon: facility.lon,
                    type: msfsSdk.LegType.TF,
                    fixIcao: facility.icao
                });
            insertProcedureObject.procedureLegs.push(destinationLeg);
            this.tryInsertIFLeg(insertProcedureObject);
            return insertProcedureObject;
        }
        /**
         * Method to move a direct to destination to a specified target segment.
         * @param plan is the primary flight plan.
         * @param targetSegmentType is the target segment type.
         * @param arrivalSegmentIndex is the arrival segment index
         * @returns whether a direct to destination was moved.
         */
        moveDirectToDestinationLeg(plan, targetSegmentType, arrivalSegmentIndex) {
            if (this.getDirectToState() === exports.DirectToState.TOEXISTING) {
                const directTargetSegmentIndex = targetSegmentType === msfsSdk.FlightPlanSegmentType.Arrival ? arrivalSegmentIndex : this.findLastEnrouteSegmentIndex(plan);
                if (directTargetSegmentIndex !== undefined && directTargetSegmentIndex > 0 && plan.getLeg(plan.activeLateralLeg).leg.fixIcao === plan.destinationAirport) {
                    const destinationLeg = Object.assign({}, plan.getSegment(plan.directToData.segmentIndex).legs[plan.directToData.segmentLegIndex].leg);
                    const directTargetLeg = Object.assign({}, plan.getLeg(plan.activeLateralLeg).leg);
                    const directOriginLeg = Object.assign({}, plan.getLeg(plan.activeLateralLeg - 1).leg);
                    const discoLeg = Object.assign({}, plan.getLeg(plan.activeLateralLeg - 2).leg);
                    const newDirectLegIndex = plan.getSegment(directTargetSegmentIndex).legs.length;
                    plan.removeLeg(plan.directToData.segmentIndex, plan.directToData.segmentLegIndex);
                    plan.removeLeg(plan.directToData.segmentIndex, plan.directToData.segmentLegIndex);
                    plan.removeLeg(plan.directToData.segmentIndex, plan.directToData.segmentLegIndex);
                    plan.removeLeg(plan.directToData.segmentIndex, plan.directToData.segmentLegIndex);
                    plan.setDirectToData(directTargetSegmentIndex, newDirectLegIndex);
                    plan.addLeg(directTargetSegmentIndex, destinationLeg);
                    plan.addLeg(directTargetSegmentIndex, discoLeg, undefined, msfsSdk.LegDefinitionFlags.DirectTo);
                    plan.addLeg(directTargetSegmentIndex, directOriginLeg, undefined, msfsSdk.LegDefinitionFlags.DirectTo);
                    const newActiveLeg = plan.addLeg(directTargetSegmentIndex, directTargetLeg, undefined, msfsSdk.LegDefinitionFlags.DirectTo);
                    const newActiveLegIndex = plan.getLegIndexFromLeg(newActiveLeg);
                    plan.setCalculatingLeg(newActiveLegIndex);
                    plan.setLateralLeg(newActiveLegIndex);
                    plan.planIndex !== this.flightPlanner.activePlanIndex && plan.calculate(newActiveLegIndex);
                    return true;
                }
            }
            return false;
        }
        /**
         * Method to find the first enroute segment of the supplied flight plan.
         * @param plan is the flight plan to find the first enroute segment in.
         * @returns a segment index.
         */
        findFirstEnrouteSegmentIndex(plan) {
            for (let i = 1; i < plan.segmentCount; i++) {
                const segment = plan.getSegment(i);
                if (segment.segmentType === msfsSdk.FlightPlanSegmentType.Enroute) {
                    return i;
                }
            }
            return 1;
        }
        /**
         * Method to find the last enroute segment of the supplied flight plan.
         * @param plan is the flight plan to find the last enroute segment in.
         * @returns a segment index.
         */
        findLastEnrouteSegmentIndex(plan) {
            let enrouteSegmentFound = 0;
            for (let i = 1; i < plan.segmentCount; i++) {
                const segment = plan.getSegment(i);
                if (segment.segmentType === msfsSdk.FlightPlanSegmentType.Enroute) {
                    enrouteSegmentFound = i;
                }
            }
            return enrouteSegmentFound;
        }
        /**
         * Method manage the destination leg in the last enroute segment.
         * @param plan is the flight plan.
         * @param currentDestination is the currently set destination airport icao.
         */
        manageAirportLeg(plan, currentDestination) {
            if (plan.procedureDetails.arrivalIndex > -1 || !currentDestination || Simplane.getIsGrounded()) {
                //if we don't have a destination set, or an arrival is selected, don't add the airport to enroute
                return;
            }
            const lastEnrouteSegmentIndex = this.findLastEnrouteSegmentIndex(plan);
            const segment = plan.getSegment(lastEnrouteSegmentIndex);
            const lastLegIndex = segment.legs.length - 1;
            if (currentDestination && (lastLegIndex < 0 || segment.legs[lastLegIndex].leg.fixIcao !== currentDestination)) {
                //if a destination is set, AND either (a) the last enroute segment is empty OR (b) the last enroute segment isn't empty and
                //the last leg of the last enroute segment is not already the current destination
                this.planAddLeg(lastEnrouteSegmentIndex, msfsSdk.FlightPlan.createLeg({ fixIcao: currentDestination, type: msfsSdk.LegType.TF }));
            }
        }
        /**
         * Method to check whether an approach can load, or only activate.
         * @returns true if the approach can be loaded and not activated, otherwise the approach can only be immediatly activated.
         */
        canApproachLoad() {
            const plan = this.getFlightPlan();
            if (plan.length > 0) {
                const activeSegment = plan.getSegment(plan.getSegmentIndex(plan.activeLateralLeg));
                if (activeSegment.segmentType !== msfsSdk.FlightPlanSegmentType.Approach && plan.length > 1) {
                    return true;
                }
            }
            return false;
        }
        /**
         * Method to add or replace an approach procedure in the flight plan.
         * @param facility is the facility that contains the procedure to add.
         * @param approachIndex is the index of the approach procedure.
         * @param approachTransitionIndex is the index of the approach transition.
         * @param visualRunwayNumber is the visual runway number, if any.
         * @param visualRunwayDesignator is the visual runway designator, if any.
         * @param skipCourseReversal Whether to skip the course reversal. False by default.
         * @param activate Whether to activate the approach once it is loaded into the flight plan. Defaults to `false`.
         * @returns A Promise which is fulfilled with whether the approach was inserted.
         */
        async insertApproach(facility, approachIndex, approachTransitionIndex, visualRunwayNumber, visualRunwayDesignator, skipCourseReversal = false, activate = false) {
            var _a;
            const plan = this.getFlightPlan();
            let visualRunway;
            if (visualRunwayNumber !== undefined && visualRunwayDesignator !== undefined) {
                visualRunway = msfsSdk.RunwayUtils.matchOneWayRunway(facility, visualRunwayNumber, visualRunwayDesignator);
                if (!visualRunway) {
                    return false;
                }
            }
            const opId = ++this.insertApproachOpId;
            const insertProcedureObject = await this.buildApproachLegs(facility, approachIndex, approachTransitionIndex, visualRunway, skipCourseReversal);
            if (opId !== this.insertApproachOpId) {
                return false;
            }
            const originalPlanLength = plan.length;
            // Loading a new approach will always kick us out of any existing missed approach, so deactivate the missed approach.
            this.publisher.pub(this.lnavControlTopicMap['activate_missed_approach'], false, true, true);
            let skipDestinationLegCheck = false;
            const approachRunway = insertProcedureObject.runway;
            const approachRunwayIcao = approachRunway ? msfsSdk.RunwayUtils.getRunwayFacilityIcao(facility, approachRunway) : undefined;
            const isDtoExistingToRunwayActive = approachRunway
                && this.getDirectToState() === exports.DirectToState.TOEXISTING
                && plan.getLeg(plan.activeLateralLeg).leg.fixIcao[0] === 'R';
            const isDtoExistingToApproachRunway = isDtoExistingToRunwayActive && approachRunway && plan.getLeg(plan.activeLateralLeg).leg.fixIcao === approachRunwayIcao;
            let dtoExistingToRunwayIcao = '';
            let dtoExistingToRunwayCourse = undefined;
            if (isDtoExistingToRunwayActive) {
                const dtoLeg = plan.getLeg(plan.activeLateralLeg);
                dtoExistingToRunwayIcao = dtoLeg.leg.fixIcao;
                dtoExistingToRunwayCourse = dtoLeg.leg.type === msfsSdk.LegType.DF ? undefined : dtoLeg.leg.course;
                // Do not remove the destination runway leg if it is part of an arrival and the target of a direct to existing
                skipDestinationLegCheck = plan.getSegment(plan.directToData.segmentIndex).segmentType === msfsSdk.FlightPlanSegmentType.Arrival;
            }
            plan.deleteUserData(exports.FmsFplUserDataKey.VfrApproach);
            if (visualRunway) {
                plan.setUserData(exports.FmsFplUserDataKey.VisualApproach, {
                    runwayDesignation: visualRunway.designation,
                    isVtf: approachTransitionIndex < 0
                });
                plan.setUserData(exports.FmsFplUserDataKey.VisualApproachRunway, visualRunway.designation);
            }
            else {
                plan.deleteUserData(exports.FmsFplUserDataKey.VisualApproach);
                plan.deleteUserData(exports.FmsFplUserDataKey.VisualApproachRunway);
            }
            plan.setUserData(exports.FmsFplUserDataKey.ApproachSkipCourseReversal, skipCourseReversal);
            plan.setApproach(facility.icao, approachIndex, approachTransitionIndex);
            if (plan.procedureDetails.arrivalIndex < 0) {
                if (!this.moveDirectToDestinationLeg(plan, msfsSdk.FlightPlanSegmentType.Enroute)) {
                    this.manageAirportLeg(plan, plan.destinationAirport);
                }
                else {
                    skipDestinationLegCheck = true;
                }
            }
            plan.setDestinationAirport(facility.icao);
            if (!skipDestinationLegCheck) {
                this.removeDestLegFromSegments();
            }
            let approachSegment = plan.getSegment(this.ensureOnlyOneSegmentOfType(msfsSdk.FlightPlanSegmentType.Approach));
            if (approachSegment.legs.length > 0) {
                this.planClearSegment(approachSegment.segmentIndex, msfsSdk.FlightPlanSegmentType.Approach);
                // planClearSegment() actually removes and replaces the segment, so we need to get a reference to the new segment.
                approachSegment = FmsUtils.getApproachSegment(plan);
            }
            if (insertProcedureObject.runway) {
                plan.setDestinationRunway(insertProcedureObject.runway);
            }
            let didAddVtfFafLeg = false;
            let haveAddedMap = false;
            insertProcedureObject.procedureLegs.forEach((leg, index, array) => {
                var _a;
                let isMissedLeg = false;
                if (visualRunway !== undefined) {
                    // If the leg's fix is a visual approach fix, we need to add it to the facility repository so that others can
                    // look it up properly.
                    if (leg.type !== msfsSdk.LegType.Discontinuity && leg.type !== msfsSdk.LegType.ThruDiscontinuity) {
                        this.addVisualFacilityFromLeg(leg, visualRunway.designation);
                    }
                    if (haveAddedMap) {
                        isMissedLeg = true;
                    }
                    if (leg.fixTypeFlags & msfsSdk.FixTypeFlags.MAP) {
                        haveAddedMap = true;
                    }
                }
                let flags = (_a = leg.legDefinitionFlags) !== null && _a !== void 0 ? _a : msfsSdk.LegDefinitionFlags.None;
                // If we are inserting a VTF leg, we need to save the terminator ICAO of the prior leg in the published procedure
                // to the plan user data.
                if (msfsSdk.BitFlags.isAll(flags, msfsSdk.LegDefinitionFlags.VectorsToFinalFaf)) {
                    const prevLeg = array[index - 1];
                    const fixIcao = prevLeg === undefined ? undefined : msfsSdk.FlightPlanUtils.getTerminatorIcao(prevLeg);
                    plan.setUserData(Fms.VTF_FAF_DATA_KEY, fixIcao !== null && fixIcao !== void 0 ? fixIcao : '');
                    didAddVtfFafLeg = true;
                }
                if (isMissedLeg) {
                    flags |= msfsSdk.LegDefinitionFlags.MissedApproach;
                }
                this.planAddLeg(approachSegment.segmentIndex, leg, undefined, flags);
            });
            if (!didAddVtfFafLeg) {
                plan.deleteUserData(Fms.VTF_FAF_DATA_KEY);
            }
            const prevLeg = plan.getPrevLeg(approachSegment.segmentIndex, 0);
            const firstAppLeg = approachSegment.legs[0];
            if (prevLeg && firstAppLeg && this.isDuplicateLeg(prevLeg.leg, firstAppLeg.leg)) {
                this.planRemoveDuplicateLeg(prevLeg, firstAppLeg);
            }
            // Adds missed approach legs
            if (!visualRunway && insertProcedureObject.procedureLegs.length > 0) {
                const missedLegs = facility.approaches[approachIndex].missedLegs;
                if (missedLegs.length > 0) {
                    let maphIndex = -1;
                    for (let m = missedLegs.length - 1; m >= 0; m--) {
                        switch (missedLegs[m].type) {
                            case msfsSdk.LegType.HA:
                            case msfsSdk.LegType.HF:
                            case msfsSdk.LegType.HM:
                                maphIndex = m - 1;
                                break;
                        }
                    }
                    for (let n = 0; n < missedLegs.length; n++) {
                        const validLeg = this.procedureLegMapFunc(missedLegs[n]);
                        if (validLeg) {
                            const newLeg = msfsSdk.FlightPlan.createLeg(validLeg);
                            if (maphIndex >= 0 && n === maphIndex) {
                                newLeg.fixTypeFlags |= msfsSdk.FixTypeFlags.MAHP;
                                this.planAddLeg(approachSegment.segmentIndex, newLeg, undefined, msfsSdk.LegDefinitionFlags.MissedApproach);
                            }
                            else {
                                this.planAddLeg(approachSegment.segmentIndex, newLeg, undefined, msfsSdk.LegDefinitionFlags.MissedApproach);
                            }
                        }
                    }
                }
            }
            const approachType = visualRunway ? msfsSdk.AdditionalApproachType.APPROACH_TYPE_VISUAL : facility.approaches[approachIndex].approachType;
            const bestRnavType = visualRunway ? msfsSdk.RnavTypeFlags.None : FmsUtils.getBestRnavType(facility.approaches[approachIndex].rnavTypeFlags);
            const rnavTypeFlags = visualRunway ? msfsSdk.RnavTypeFlags.None : facility.approaches[approachIndex].rnavTypeFlags;
            const approachIsCircling = !visualRunway && !facility.approaches[approachIndex].runway ? true : false;
            const isVtf = approachTransitionIndex < 0;
            let referenceFacility = null;
            if (!visualRunway && FmsUtils.approachHasNavFrequency(facility.approaches[approachIndex])) {
                referenceFacility = (_a = await msfsSdk.ApproachUtils.getReferenceFacility(facility.approaches[approachIndex], this.facLoader)) !== null && _a !== void 0 ? _a : null;
            }
            ++this.updateApproachDetailsOpId;
            this.setApproachDetails(true, true, approachType, bestRnavType, rnavTypeFlags, approachIsCircling, isVtf, referenceFacility, approachRunway);
            this.autoDesignateProcedureConstraints(plan, approachSegment.segmentIndex);
            await plan.calculate();
            if (opId !== this.insertApproachOpId) {
                return false;
            }
            if (!activate && isDtoExistingToRunwayActive && this.getDirectToState() !== exports.DirectToState.TOEXISTING) {
                // Direct To Existing to the destination runway was canceled as a result of adding the approach
                if (isDtoExistingToApproachRunway) {
                    // DTO target runway matches the runway of the loaded approach -> need to reactivate DTO to the new runway leg
                    // in the approach
                    const runwayLegIndex = approachSegment.legs.findIndex(leg => leg.leg.fixIcao === approachRunwayIcao);
                    if (runwayLegIndex >= 0) {
                        this.createDirectToExisting(approachSegment.segmentIndex, runwayLegIndex, dtoExistingToRunwayCourse);
                    }
                }
                else {
                    // DTO target runway does not match the runway of the loaded approach (or the approach is circling only) ->
                    // activate DTO random to the old runway
                    this.createDirectToRandom(dtoExistingToRunwayIcao, dtoExistingToRunwayCourse);
                }
            }
            else {
                // If there were fewer than 2 legs in the flight plan before the approach was loaded, then we are forced to
                // activate the approach.
                activate || (activate = originalPlanLength < 2);
            }
            if (activate) {
                if (approachTransitionIndex < 0) {
                    this.activateVtf();
                }
                else {
                    this.activateApproach();
                }
            }
            else {
                // Only auto-tune approach frequency if not activating the approach, because activating the approach will also
                // trigger auto-tune.
                for (const index of this.navRadioIndexes) {
                    this.setLocFrequency(index);
                }
            }
            return true;
        }
        /**
         * Method to insert the approach legs.
         * @param facility The facility to build legs from.
         * @param approachIndex The approach procedure index to build legs from.
         * @param approachTransitionIndex The transition index to build legs from.
         * @param visualRunway If this is a visual approach, the visual approach one way runway object.
         * @param skipCourseReversal Whether to skip the course reversal.
         * @returns A Promise which is fulfilled with an `InsertProcedureObject` containing the flight plan legs to insert
         * into the flight plan.
         */
        async buildApproachLegs(facility, approachIndex, approachTransitionIndex, visualRunway, skipCourseReversal) {
            const isVisual = !!visualRunway;
            const approach = isVisual
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                ? FmsUtils.buildVisualApproach(facility, visualRunway, this.visualApproachOptions.finalFixDistance, this.visualApproachOptions.strghtFixDistance)
                : facility.approaches[approachIndex];
            const transition = approach.transitions[approachTransitionIndex];
            const isVtf = approachTransitionIndex < 0;
            const insertProcedureObject = { procedureLegs: [] };
            if (transition !== undefined && transition.legs.length > 0) {
                for (let t = 0; t < transition.legs.length; t++) {
                    const insertLeg = this.procedureLegMapFunc(transition.legs[t]);
                    insertLeg && insertProcedureObject.procedureLegs.push(msfsSdk.FlightPlan.createLeg(insertLeg));
                }
            }
            const lastTransitionLeg = insertProcedureObject.procedureLegs[insertProcedureObject.procedureLegs.length - 1];
            if (isVtf) {
                insertProcedureObject.procedureLegs.push(msfsSdk.FlightPlan.createLeg({ type: msfsSdk.LegType.ThruDiscontinuity }));
            }
            const finalLegs = approach.finalLegs;
            for (let i = 0; i < finalLegs.length; i++) {
                const leg = msfsSdk.FlightPlan.createLeg(finalLegs[i]);
                if (i === 0 && lastTransitionLeg && this.isDuplicateIFLeg(lastTransitionLeg, leg)) {
                    insertProcedureObject.procedureLegs[insertProcedureObject.procedureLegs.length - 1] = this.mergeDuplicateLegData(lastTransitionLeg, leg);
                    continue;
                }
                if (!isVisual && leg.fixIcao[0] === 'R') {
                    const approachRunway = msfsSdk.RunwayUtils.matchOneWayRunway(facility, approach.runwayNumber, approach.runwayDesignator);
                    if (approachRunway) {
                        insertProcedureObject.runway = approachRunway;
                        const runwayLeg = FmsUtils.buildRunwayLeg(facility, approachRunway, false);
                        runwayLeg.altitude1 += Fms.RUNWAY_LEG_ALTITUDE_OFFSET; // Add offset to raise runway leg altitude above the runway elevation.
                        insertProcedureObject.procedureLegs.push(runwayLeg);
                    }
                }
                else if (isVisual && i === finalLegs.length - 1) {
                    insertProcedureObject.runway = visualRunway;
                    insertProcedureObject.procedureLegs.push(leg);
                    if (approach.missedLegs.length > 0) {
                        insertProcedureObject.procedureLegs.push(approach.missedLegs[0]);
                    }
                }
                else {
                    if (isVtf && msfsSdk.BitFlags.isAll(leg.fixTypeFlags, msfsSdk.FixTypeFlags.FAF)) {
                        // If this is a VTF approach, replace the faf leg with a VTF leg
                        await this.insertVtfLeg(insertProcedureObject, leg, finalLegs[i - 1], finalLegs[i + 1]);
                    }
                    else {
                        const insertLeg = this.procedureLegMapFunc(leg);
                        insertLeg && insertProcedureObject.procedureLegs.push(msfsSdk.FlightPlan.createLeg(insertLeg));
                    }
                }
            }
            if (!isVisual) {
                this.tryInsertIFLeg(insertProcedureObject);
                this.tryReconcileIAFLeg(insertProcedureObject);
                this.manageFafAltitudeRestriction(insertProcedureObject);
                this.tryCleanupHold(insertProcedureObject);
                if (skipCourseReversal) {
                    this.tryRemoveCourseReversal(insertProcedureObject);
                }
                this.tryInsertMap(insertProcedureObject);
                if (!insertProcedureObject.runway && approach.runway) {
                    insertProcedureObject.runway = msfsSdk.RunwayUtils.matchOneWayRunway(facility, approach.runwayNumber, approach.runwayDesignator);
                }
                if (isVtf) {
                    // If the first leg (not counting the discontinuity leg) of the VTF approach is not already an IF leg, replace
                    // it with one unless it is also the faf and a CF leg.
                    // Note that we can skip checking for certain leg types because they are handled by tryInsertIfLeg().
                    const firstLeg = insertProcedureObject.procedureLegs[1];
                    switch (firstLeg === null || firstLeg === void 0 ? void 0 : firstLeg.type) {
                        case msfsSdk.LegType.TF:
                        case msfsSdk.LegType.DF:
                        case msfsSdk.LegType.CF:
                            if (msfsSdk.BitFlags.isAll(firstLeg.fixTypeFlags, msfsSdk.FixTypeFlags.FAF)) {
                                break;
                            }
                        // eslint-disable-next-line no-fallthrough
                        case msfsSdk.LegType.AF:
                        case msfsSdk.LegType.RF:
                            insertProcedureObject.procedureLegs[1] = msfsSdk.FlightPlan.createLeg({
                                type: msfsSdk.LegType.IF,
                                fixIcao: firstLeg.fixIcao,
                                fixTypeFlags: firstLeg.fixTypeFlags
                            });
                            break;
                        default:
                            // If we are not replacing the leg, we need to remove altitude restrictions from the leg unless it is the faf
                            if (!msfsSdk.BitFlags.isAll(firstLeg.fixTypeFlags, msfsSdk.FixTypeFlags.FAF)) {
                                firstLeg.altDesc = msfsSdk.AltitudeRestrictionType.Unused;
                                firstLeg.altitude1 = 0;
                                firstLeg.altitude2 = 0;
                            }
                    }
                }
            }
            return insertProcedureObject;
        }
        /**
         * Inserts a VFR approach into the primary flight plan, replacing any approach that is already loaded.
         *
         * VFR approaches are distinct from both _visual instrument approaches_, which are a type of published IFR approach,
         * and _Garmin visual approaches_, which are auto-generated approaches not based on any published approach. A VFR
         * approach is based on a published IFR approach, but only includes the flight plan legs between and including those
         * ending at the final approach fix (faf) and missed approach point (map). Flight plan legs in the missed approach
         * procedure are not included.
         * @param facility The airport facility containing the published approach on which the VFR approach to insert is
         * based.
         * @param approachIndex The index of the published approach on which the VFR approach to insert is based.
         * @param isVtf Whether to insert the approach as a vectors-to-final (VTF) approach.
         * @param activate Whether to activate the approach once it is loaded into the flight plan. Defaults to `false`.
         * @returns A Promise which is fulfilled with whether the approach was inserted.
         */
        async insertVfrApproach(facility, approachIndex, isVtf, activate = false) {
            var _a;
            const plan = this.getFlightPlan();
            const opId = ++this.insertApproachOpId;
            const insertProcedureObject = await this.buildVfrApproachLegs(facility, approachIndex, isVtf);
            if (opId !== this.insertApproachOpId || !insertProcedureObject) {
                return false;
            }
            const originalPlanLength = plan.length;
            // Loading a new approach will always kick us out of any existing missed approach, so deactivate the missed approach.
            this.publisher.pub(this.lnavControlTopicMap['activate_missed_approach'], false, true, true);
            let skipDestinationLegCheck = false;
            const approachRunway = insertProcedureObject.runway;
            const approachRunwayIcao = approachRunway ? msfsSdk.RunwayUtils.getRunwayFacilityIcao(facility, approachRunway) : undefined;
            const isDtoExistingToRunwayActive = approachRunway
                && this.getDirectToState() === exports.DirectToState.TOEXISTING
                && plan.getLeg(plan.activeLateralLeg).leg.fixIcao[0] === 'R';
            const isDtoExistingToApproachRunway = isDtoExistingToRunwayActive && approachRunway && plan.getLeg(plan.activeLateralLeg).leg.fixIcao === approachRunwayIcao;
            let dtoExistingToRunwayIcao = '';
            let dtoExistingToRunwayCourse = undefined;
            if (isDtoExistingToRunwayActive) {
                const dtoLeg = plan.getLeg(plan.activeLateralLeg);
                dtoExistingToRunwayIcao = dtoLeg.leg.fixIcao;
                dtoExistingToRunwayCourse = dtoLeg.leg.type === msfsSdk.LegType.DF ? undefined : dtoLeg.leg.course;
                // Do not remove the destination runway leg if it is part of an arrival and the target of a direct to existing
                skipDestinationLegCheck = plan.getSegment(plan.directToData.segmentIndex).segmentType === msfsSdk.FlightPlanSegmentType.Arrival;
            }
            plan.deleteUserData(exports.FmsFplUserDataKey.VisualApproach);
            plan.deleteUserData(exports.FmsFplUserDataKey.VisualApproachRunway);
            plan.setUserData(exports.FmsFplUserDataKey.VfrApproach, {
                approachIndex,
                isVtf
            });
            plan.setUserData(exports.FmsFplUserDataKey.ApproachSkipCourseReversal, false);
            plan.setApproach(facility.icao, -1, isVtf ? -1 : 0);
            if (plan.procedureDetails.arrivalIndex < 0) {
                if (!this.moveDirectToDestinationLeg(plan, msfsSdk.FlightPlanSegmentType.Enroute)) {
                    this.manageAirportLeg(plan, plan.destinationAirport);
                }
                else {
                    skipDestinationLegCheck = true;
                }
            }
            plan.setDestinationAirport(facility.icao);
            if (!skipDestinationLegCheck) {
                this.removeDestLegFromSegments();
            }
            let approachSegment = plan.getSegment(this.ensureOnlyOneSegmentOfType(msfsSdk.FlightPlanSegmentType.Approach));
            if (approachSegment.legs.length > 0) {
                this.planClearSegment(approachSegment.segmentIndex, msfsSdk.FlightPlanSegmentType.Approach);
                // planClearSegment() actually removes and replaces the segment, so we need to get a reference to the new segment.
                approachSegment = FmsUtils.getApproachSegment(plan);
            }
            if (insertProcedureObject.runway) {
                plan.setDestinationRunway(insertProcedureObject.runway);
            }
            let didAddVtfFafLeg = false;
            insertProcedureObject.procedureLegs.forEach((leg, index, array) => {
                var _a;
                const flags = (_a = leg.legDefinitionFlags) !== null && _a !== void 0 ? _a : msfsSdk.LegDefinitionFlags.None;
                // If we are inserting a VTF leg, we need to save the terminator ICAO of the prior leg in the published procedure
                // to the plan user data.
                if (msfsSdk.BitFlags.isAll(flags, msfsSdk.LegDefinitionFlags.VectorsToFinalFaf)) {
                    const prevLeg = array[index - 1];
                    const fixIcao = prevLeg === undefined ? undefined : msfsSdk.FlightPlanUtils.getTerminatorIcao(prevLeg);
                    plan.setUserData(Fms.VTF_FAF_DATA_KEY, fixIcao !== null && fixIcao !== void 0 ? fixIcao : '');
                    didAddVtfFafLeg = true;
                }
                this.planAddLeg(approachSegment.segmentIndex, leg, undefined, flags);
            });
            if (!didAddVtfFafLeg) {
                plan.deleteUserData(Fms.VTF_FAF_DATA_KEY);
            }
            const prevLeg = plan.getPrevLeg(approachSegment.segmentIndex, 0);
            const firstAppLeg = approachSegment.legs[0];
            if (prevLeg && firstAppLeg && this.isDuplicateLeg(prevLeg.leg, firstAppLeg.leg)) {
                this.planRemoveDuplicateLeg(prevLeg, firstAppLeg);
            }
            const approachType = exports.GarminAdditionalApproachType.APPROACH_TYPE_VFR;
            const bestRnavType = msfsSdk.RnavTypeFlags.None;
            const rnavTypeFlags = msfsSdk.RnavTypeFlags.None;
            const approachIsCircling = !facility.approaches[approachIndex].runway ? true : false;
            let referenceFacility = null;
            if (FmsUtils.approachHasNavFrequency(facility.approaches[approachIndex])) {
                referenceFacility = (_a = await msfsSdk.ApproachUtils.getReferenceFacility(facility.approaches[approachIndex], this.facLoader)) !== null && _a !== void 0 ? _a : null;
            }
            ++this.updateApproachDetailsOpId;
            this.setApproachDetails(true, true, approachType, bestRnavType, rnavTypeFlags, approachIsCircling, isVtf, referenceFacility, approachRunway);
            await plan.calculate();
            if (opId !== this.insertApproachOpId) {
                return false;
            }
            if (!activate && isDtoExistingToRunwayActive && this.getDirectToState() !== exports.DirectToState.TOEXISTING) {
                // Direct To Existing to the destination runway was canceled as a result of adding the approach
                if (isDtoExistingToApproachRunway) {
                    // DTO target runway matches the runway of the loaded approach -> need to reactivate DTO to the new runway leg
                    // in the approach
                    const runwayLegIndex = approachSegment.legs.findIndex(leg => leg.leg.fixIcao === approachRunwayIcao);
                    if (runwayLegIndex >= 0) {
                        this.createDirectToExisting(approachSegment.segmentIndex, runwayLegIndex, dtoExistingToRunwayCourse);
                    }
                }
                else {
                    // DTO target runway does not match the runway of the loaded approach (or the approach is circling only) ->
                    // activate DTO random to the old runway
                    this.createDirectToRandom(dtoExistingToRunwayIcao, dtoExistingToRunwayCourse);
                }
            }
            else {
                // If there were fewer than 2 legs in the flight plan before the approach was loaded, then we are forced to
                // activate the approach.
                activate || (activate = originalPlanLength < 2);
            }
            if (activate) {
                if (isVtf) {
                    this.activateVtf();
                }
                else {
                    this.activateApproach();
                }
            }
            else {
                // Only auto-tune approach frequency if not activating the approach, because activating the approach will also
                // trigger auto-tune.
                for (const index of this.navRadioIndexes) {
                    this.setLocFrequency(index);
                }
            }
            return true;
        }
        /**
         * Builds a set of VFR approach flight plan legs.
         * @param facility The airport facility containing the approach procedure for which build the legs.
         * @param approachIndex The index of the approach procedure for which to build the legs.
         * @param isVtf Whether to build a set of legs for a vectors-to-final (VTF) approach.
         * @returns A Promise which will be fulfilled with an `InsertProcedureObject` containing the flight plan legs to
         * insert into the flight plan, or with `undefined` if a set of legs could not be built for the specified procedure.
         */
        async buildVfrApproachLegs(facility, approachIndex, isVtf) {
            const approach = FmsUtils.buildVfrApproach(facility, approachIndex);
            if (!approach) {
                return undefined;
            }
            const insertProcedureObject = { procedureLegs: [] };
            const finalLegs = approach.finalLegs;
            for (let i = 0; i < finalLegs.length; i++) {
                const leg = finalLegs[i];
                if (leg.fixIcao[0] === 'R') {
                    const approachRunway = msfsSdk.RunwayUtils.matchOneWayRunway(facility, approach.runwayNumber, approach.runwayDesignator);
                    if (approachRunway) {
                        insertProcedureObject.runway = approachRunway;
                        const runwayLeg = FmsUtils.buildRunwayLeg(facility, approachRunway, false);
                        runwayLeg.altitude1 += Fms.RUNWAY_LEG_ALTITUDE_OFFSET; // Add offset to raise runway leg altitude above the runway elevation.
                        runwayLeg.fixTypeFlags = leg.fixTypeFlags;
                        insertProcedureObject.procedureLegs.push(runwayLeg);
                    }
                }
                else {
                    if (msfsSdk.BitFlags.isAll(leg.fixTypeFlags, msfsSdk.FixTypeFlags.FAF) && isVtf) {
                        // If this is a VTF approach, attempt to replace the faf leg with a VTF leg
                        await this.insertVtfLeg(insertProcedureObject, msfsSdk.FlightPlan.createLeg(leg), finalLegs[i - 1], finalLegs[i + 1]);
                    }
                    else {
                        const insertLeg = this.procedureLegMapFunc(leg);
                        insertLeg && insertProcedureObject.procedureLegs.push(msfsSdk.FlightPlan.createLeg(insertLeg));
                    }
                }
            }
            // Ensure VTF approaches begin with a discontinuity.
            if (isVtf) {
                insertProcedureObject.procedureLegs.unshift(msfsSdk.FlightPlan.createLeg({ type: msfsSdk.LegType.ThruDiscontinuity }));
            }
            this.manageFafAltitudeRestriction(insertProcedureObject);
            if (!insertProcedureObject.runway && approach.runway) {
                insertProcedureObject.runway = msfsSdk.RunwayUtils.matchOneWayRunway(facility, approach.runwayNumber, approach.runwayDesignator);
            }
            return insertProcedureObject;
        }
        /**
         * Inserts a vector-to-final (VTF) leg into a procedure insertion object. A VTF leg is a CF leg to the final approach
         * fix with the {@link LegDefinitionFlags.VectorsToFinalFaf} flag applied to it.
         *
         * The course of the VTF leg is defined as follows:
         * * If the leg to the faf is a CF leg, the VTF course is equal to the CF leg course.
         * * If the leg to the faf is not an IF leg, the VTF course is defined by the great-circle path from the fix
         * immediately prior to the faf to the faf.
         * * If the leg to the faf is an IF leg, the VTF course is defined by the great-circle path from the faf to the fix
         * immediately following it.
         *
         * If a VTF course cannot be defined, then the normal faf leg is inserted instead of the VTF leg.
         * @param insertProcedureObject The procedure insertion object into which to insert the leg.
         * @param fafLeg The leg to the final approach fix.
         * @param prevLeg The leg before the faf leg.
         * @param nextLeg The leg after the faf leg.
         */
        async insertVtfLeg(insertProcedureObject, fafLeg, prevLeg, nextLeg) {
            var _a, _b;
            switch (fafLeg.type) {
                case msfsSdk.LegType.CF:
                case msfsSdk.LegType.TF:
                case msfsSdk.LegType.IF:
                case msfsSdk.LegType.DF:
                    break;
                default:
                    insertProcedureObject.procedureLegs.push(fafLeg);
                    return;
            }
            try {
                const fafFacility = msfsSdk.ICAO.isFacility(fafLeg.fixIcao, msfsSdk.FacilityType.VIS)
                    // If the faf fix is a visual facility, we can't retrieve it from facility loader because it is not guaranteed
                    // to exist yet. However, because we are dealing with a visual approach, the leg should define the fix's
                    // lat/lon coordinates.
                    ? { lat: (_a = fafLeg.lat) !== null && _a !== void 0 ? _a : 0, lon: (_b = fafLeg.lon) !== null && _b !== void 0 ? _b : 0 }
                    : await this.facLoader.getFacility(msfsSdk.ICAO.getFacilityType(fafLeg.fixIcao), fafLeg.fixIcao);
                let course;
                let trueDegrees;
                if (fafLeg.type === msfsSdk.LegType.CF) {
                    course = fafLeg.course;
                    trueDegrees = fafLeg.trueDegrees;
                }
                else {
                    const fafPoint = Fms.geoPointCache[0].set(fafFacility);
                    if (fafLeg.type === msfsSdk.LegType.IF) {
                        // faf leg is an IF, meaning it is the first leg in the approach -> get the course from the next leg.
                        let nextLegFixIcao = '';
                        switch (nextLeg === null || nextLeg === void 0 ? void 0 : nextLeg.type) {
                            case msfsSdk.LegType.IF:
                            case msfsSdk.LegType.TF:
                            case msfsSdk.LegType.DF:
                            case msfsSdk.LegType.CF:
                            case msfsSdk.LegType.AF:
                            case msfsSdk.LegType.RF:
                            case msfsSdk.LegType.HF:
                            case msfsSdk.LegType.HM:
                            case msfsSdk.LegType.HA:
                                nextLegFixIcao = nextLeg.fixIcao;
                        }
                        if (msfsSdk.ICAO.isFacility(nextLegFixIcao)) {
                            const nextLegFacility = await this.facLoader.getFacility(msfsSdk.ICAO.getFacilityType(nextLegFixIcao), nextLegFixIcao);
                            course = fafPoint.bearingTo(nextLegFacility);
                        }
                    }
                    else {
                        // faf leg is not the first leg in the approach -> get the course from the previous leg.
                        let prevLegFixIcao = '';
                        switch (prevLeg === null || prevLeg === void 0 ? void 0 : prevLeg.type) {
                            case msfsSdk.LegType.IF:
                            case msfsSdk.LegType.TF:
                            case msfsSdk.LegType.DF:
                            case msfsSdk.LegType.CF:
                            case msfsSdk.LegType.AF:
                            case msfsSdk.LegType.RF:
                            case msfsSdk.LegType.HF:
                            case msfsSdk.LegType.HM:
                            case msfsSdk.LegType.HA:
                                prevLegFixIcao = prevLeg.fixIcao;
                        }
                        if (msfsSdk.ICAO.isFacility(prevLegFixIcao)) {
                            const prevLegFacility = await this.facLoader.getFacility(msfsSdk.ICAO.getFacilityType(prevLegFixIcao), prevLegFixIcao);
                            course = fafPoint.bearingFrom(prevLegFacility);
                        }
                    }
                    trueDegrees = true;
                }
                if (course === undefined) {
                    insertProcedureObject.procedureLegs.push(fafLeg);
                    return;
                }
                const vtfFafLeg = msfsSdk.FlightPlan.createLeg({
                    type: msfsSdk.LegType.CF,
                    lat: fafFacility.lat,
                    lon: fafFacility.lon,
                    fixIcao: fafLeg.fixIcao,
                    course,
                    trueDegrees,
                    originIcao: fafLeg.originIcao,
                    fixTypeFlags: fafLeg.fixTypeFlags,
                    altDesc: fafLeg.altDesc,
                    altitude1: fafLeg.altitude1,
                    altitude2: fafLeg.altitude2,
                    speedRestriction: fafLeg.speedRestriction
                });
                vtfFafLeg.legDefinitionFlags = msfsSdk.LegDefinitionFlags.VectorsToFinalFaf;
                insertProcedureObject.procedureLegs.push(vtfFafLeg);
            }
            catch (e) {
                console.warn(`Fms: could not insert VTF legs due to error... ${e}`);
                if (e instanceof Error) {
                    console.error(e.stack);
                }
                insertProcedureObject.procedureLegs.push(fafLeg);
            }
        }
        /**
         * Manages the altitude constraints when adding a procedure by creating a VerticalData object for each leg.
         * @param plan The Flight Plan.
         * @param segmentIndex The segment index for the inserted procedure.
         */
        autoDesignateProcedureConstraints(plan, segmentIndex) {
            const segment = plan.getSegment(segmentIndex);
            const isDeparture = segment.segmentType === msfsSdk.FlightPlanSegmentType.Origin || segment.segmentType === msfsSdk.FlightPlanSegmentType.Departure;
            const isApproach = segment.segmentType === msfsSdk.FlightPlanSegmentType.Approach;
            for (let l = 0; l < segment.legs.length; l++) {
                const leg = segment.legs[l];
                const isMapr = isApproach && msfsSdk.BitFlags.isAny(leg.flags, msfsSdk.LegDefinitionFlags.MissedApproach);
                const isAltitudeDesignable = this.isAltitudeAutoDesignable(segment, leg);
                let phase = msfsSdk.VerticalFlightPhase.Descent;
                let altDesc = msfsSdk.AltitudeRestrictionType.Unused;
                let altitude1 = 0;
                let altitude2 = 0;
                if (isAltitudeDesignable) {
                    // For simple VNAV, convert all constraints to AT constraints (for BETWEEN constraints, use the lower altitude).
                    if (!this.isAdvancedVnav) {
                        phase = msfsSdk.VerticalFlightPhase.Descent;
                        altDesc = leg.leg.altDesc === msfsSdk.AltitudeRestrictionType.Unused ? msfsSdk.AltitudeRestrictionType.Unused : msfsSdk.AltitudeRestrictionType.At;
                        altitude1 = leg.leg.altDesc === msfsSdk.AltitudeRestrictionType.Between ? leg.leg.altitude2 : leg.leg.altitude1;
                        altitude2 = 0;
                    }
                    else {
                        phase = isDeparture || isMapr ? msfsSdk.VerticalFlightPhase.Climb : msfsSdk.VerticalFlightPhase.Descent;
                        altDesc = leg.leg.altDesc;
                        altitude1 = leg.leg.altitude1;
                        altitude2 = leg.leg.altitude2;
                    }
                }
                const verticalData = {
                    phase,
                    altDesc,
                    altitude1,
                    altitude2,
                    displayAltitude1AsFlightLevel: FmsUtils.displayAltitudeAsFlightLevel(altitude1),
                    displayAltitude2AsFlightLevel: FmsUtils.displayAltitudeAsFlightLevel(altitude2),
                    isAltitude1TempCompensated: false,
                    isAltitude2TempCompensated: false,
                    speed: leg.leg.speedRestriction,
                    speedDesc: FmsUtils.getPublishedSpeedDescBasedOnSegment(leg.leg.speedRestriction, segment.segmentType),
                    speedUnit: msfsSdk.SpeedUnit.IAS,
                };
                plan.setLegVerticalData(segmentIndex, l, verticalData);
            }
        }
        /**
         * Checks whether an altitude constraint defined for a flight plan leg in a procedure can be auto-designated.
         * @param segment The procedure segment containing the flight plan leg to evaluate.
         * @param leg The flight plan leg to evaluate.
         * @returns Whether an altitude constraint defined for the specified orocedure flight plan leg can be
         * auto-designated.
         */
        isAltitudeAutoDesignable(segment, leg) {
            if (msfsSdk.BitFlags.isAny(leg.leg.fixTypeFlags, msfsSdk.FixTypeFlags.MAP)) {
                return false;
            }
            if (this.isAdvancedVnav) {
                switch (leg.leg.type) {
                    case msfsSdk.LegType.FM:
                    case msfsSdk.LegType.VM:
                    case msfsSdk.LegType.HM:
                    case msfsSdk.LegType.HA:
                        return false;
                }
            }
            else {
                // Departure altitudes are not designable in simple VNAV mode.
                if (segment.segmentType === msfsSdk.FlightPlanSegmentType.Origin || segment.segmentType === msfsSdk.FlightPlanSegmentType.Departure) {
                    return false;
                }
                // Missed approach altitudes are not designable in simple VNAV mode.
                if (msfsSdk.BitFlags.isAny(leg.flags, msfsSdk.LegDefinitionFlags.MissedApproach)) {
                    return false;
                }
                switch (leg.leg.type) {
                    case msfsSdk.LegType.FM:
                    case msfsSdk.LegType.VM:
                    case msfsSdk.LegType.HM:
                    case msfsSdk.LegType.CI:
                    case msfsSdk.LegType.VI:
                    case msfsSdk.LegType.CR:
                    case msfsSdk.LegType.VR:
                    case msfsSdk.LegType.CA:
                    case msfsSdk.LegType.FA:
                    case msfsSdk.LegType.VA:
                    case msfsSdk.LegType.HA:
                    case msfsSdk.LegType.HF:
                        return false;
                }
            }
            return true;
        }
        /**
         * Method to set a user altitude constraint.
         * @param segmentIndex The segment index to insert the constraint at.
         * @param segmentLegIndex The leg index to insert the constraint at.
         * @param altitudeFeet The altitude, in feet, to set the constraint at; if undefined, delete user constraint.
         * @param displayAsFlightLevel Whether the constraint should be displayed as a flight level. Defaults to false.
         * @throws Error if altitude is NaN.
         */
        setUserConstraint(segmentIndex, segmentLegIndex, altitudeFeet, displayAsFlightLevel = false) {
            if (altitudeFeet !== undefined && isNaN(altitudeFeet)) {
                throw new Error('altitudeFeet must not be NaN');
            }
            if (!this.hasPrimaryFlightPlan()) {
                return;
            }
            const plan = this.getPrimaryFlightPlan();
            const verticalData = {
                phase: msfsSdk.VerticalFlightPhase.Descent,
                altDesc: altitudeFeet !== undefined ? msfsSdk.AltitudeRestrictionType.At : msfsSdk.AltitudeRestrictionType.Unused,
                altitude1: altitudeFeet !== undefined ? msfsSdk.UnitType.FOOT.convertTo(altitudeFeet, msfsSdk.UnitType.METER) : 0,
                displayAltitude1AsFlightLevel: displayAsFlightLevel,
            };
            // When setting an altitude constraint other than "At", delete the FPA
            if (verticalData.altDesc !== msfsSdk.AltitudeRestrictionType.At) {
                verticalData.fpa = undefined;
            }
            this.setLegVerticalData(plan, segmentIndex, segmentLegIndex, verticalData);
        }
        /**
         * Method to set a user altitude constraint.
         * @param segmentIndex The segment index to insert the constraint at.
         * @param segmentLegIndex The leg index to insert the constraint at.
         * @param phase The vertical restriction type.
         * @param altDesc The altitude restriction type.
         * @param altitude1Meters The altitude 1 in meters.
         * @param displayAltitude1AsFlightLevel Whether to display altitude 1 as a flight level.
         * @param altitude2Meters The altitude 2 in meters. Optional.
         * @param displayAltitude2AsFlightLevel Whether to display altitude 2 as a flight level.
         * @throws Error if either altitude is NaN.
         */
        setUserConstraintAdvanced(segmentIndex, segmentLegIndex, phase, altDesc, altitude1Meters, displayAltitude1AsFlightLevel, altitude2Meters = 0, displayAltitude2AsFlightLevel = false) {
            if (isNaN(altitude1Meters)) {
                throw new Error('altitude1Meters must not be NaN');
            }
            if (isNaN(altitude2Meters)) {
                throw new Error('altitude2Meters must not be NaN');
            }
            if (!this.hasPrimaryFlightPlan()) {
                return;
            }
            const plan = this.getPrimaryFlightPlan();
            const verticalData = {
                phase,
                altDesc,
                altitude1: altitude1Meters,
                displayAltitude1AsFlightLevel,
                altitude2: altitude2Meters,
                displayAltitude2AsFlightLevel,
            };
            // When setting an altitude constraint other than "At", delete the FPA
            if (verticalData.altDesc !== msfsSdk.AltitudeRestrictionType.At) {
                verticalData.fpa = undefined;
            }
            this.setLegVerticalData(plan, segmentIndex, segmentLegIndex, verticalData);
        }
        /**
         * Reverts an altitude constraint to the published data.
         * @param segmentIndex The segment index to revert the constraint at.
         * @param segmentLegIndex The leg index to revert the constraint at.
         */
        revertAltitudeConstraint(segmentIndex, segmentLegIndex) {
            if (!this.hasPrimaryFlightPlan()) {
                return;
            }
            const plan = this.getPrimaryFlightPlan();
            const segment = plan.tryGetSegment(segmentIndex);
            const leg = segment === null || segment === void 0 ? void 0 : segment.legs[segmentLegIndex];
            if (segment === null || leg === undefined) {
                return;
            }
            let phase = msfsSdk.VerticalFlightPhase.Descent;
            let altDesc = msfsSdk.AltitudeRestrictionType.Unused;
            let altitude1 = 0;
            let altitude2 = 0;
            if (!this.isAdvancedVnav) {
                phase = msfsSdk.VerticalFlightPhase.Descent;
                altDesc = leg.leg.altDesc === msfsSdk.AltitudeRestrictionType.Unused ? msfsSdk.AltitudeRestrictionType.Unused : msfsSdk.AltitudeRestrictionType.At;
                altitude1 = leg.leg.altDesc === msfsSdk.AltitudeRestrictionType.Between ? leg.leg.altitude2 : leg.leg.altitude1;
                altitude2 = 0;
            }
            else {
                const isDeparture = segment.segmentType === msfsSdk.FlightPlanSegmentType.Departure;
                const isMapr = msfsSdk.BitFlags.isAny(leg.flags, msfsSdk.LegDefinitionFlags.MissedApproach);
                phase = isDeparture || isMapr ? msfsSdk.VerticalFlightPhase.Climb : msfsSdk.VerticalFlightPhase.Descent;
                altDesc = leg.leg.altDesc;
                altitude1 = leg.leg.altitude1;
                altitude2 = leg.leg.altitude2;
            }
            const verticalData = {
                phase,
                altDesc,
                altitude1,
                altitude2,
                displayAltitude1AsFlightLevel: FmsUtils.displayAltitudeAsFlightLevel(altitude1),
                displayAltitude2AsFlightLevel: FmsUtils.displayAltitudeAsFlightLevel(altitude2),
            };
            // When setting an altitude constraint other than "At", delete the FPA
            if (verticalData.altDesc !== msfsSdk.AltitudeRestrictionType.At) {
                verticalData.fpa = undefined;
            }
            this.setLegVerticalData(plan, segmentIndex, segmentLegIndex, verticalData);
        }
        /**
         * Method to set a user speed constraint.
         * @param planIndex The flight plan index to use.
         * @param segmentIndex The segment index to insert the constraint at.
         * @param segmentLegIndex The leg index to insert the constraint at.
         * @param speed The speed, in knots IAS or Mach (should match the given speedDesc),
         * to set the constraint at; if undefined, delete user constraint.
         * @param speedUnit The speed units.
         * @param speedDesc The speed restriction type to set.
         * @throws Error if speed is NaN.
         */
        setUserSpeedConstraint(planIndex, segmentIndex, segmentLegIndex, speed, speedUnit, speedDesc) {
            if (speed !== undefined && isNaN(speed)) {
                throw new Error('speed must not be NaN');
            }
            if (!this.hasFlightPlan(planIndex)) {
                return;
            }
            const plan = this.getFlightPlan(planIndex);
            let verticalData;
            const leg = plan.tryGetLeg(segmentIndex, segmentLegIndex);
            if (leg === null) {
                return;
            }
            if (speed !== undefined && speedUnit !== undefined && speedDesc !== undefined) {
                verticalData = {
                    speed,
                    speedUnit,
                    speedDesc,
                };
            }
            else {
                verticalData = {
                    speed: 0,
                    speedUnit: msfsSdk.SpeedUnit.IAS,
                    speedDesc: msfsSdk.SpeedRestrictionType.Unused,
                };
            }
            this.setLegVerticalData(plan, segmentIndex, segmentLegIndex, verticalData);
        }
        /**
         * Method to revert the speed constraint back to published value.
         * @param planIndex The flight plan index to use.
         * @param segmentIndex The segment index to revert the constraint at.
         * @param segmentLegIndex The leg index to revert the constraint at.
         */
        revertSpeedConstraint(planIndex, segmentIndex, segmentLegIndex) {
            if (!this.hasFlightPlan(planIndex)) {
                return;
            }
            const plan = this.getFlightPlan(planIndex);
            const leg = plan.tryGetLeg(segmentIndex, segmentLegIndex);
            if (leg === null) {
                return;
            }
            const segment = plan.getSegment(segmentIndex);
            const verticalData = {
                speed: leg.leg.speedRestriction,
                // There is no published speedDesc in the nav data, so they are all in knots IAS
                speedUnit: msfsSdk.SpeedUnit.IAS,
                // Nav data doesn't have a speed desc, so we pick one based on the segment
                speedDesc: FmsUtils.getPublishedSpeedDescBasedOnSegment(leg.leg.speedRestriction, segment.segmentType),
            };
            this.setLegVerticalData(plan, segmentIndex, segmentLegIndex, verticalData);
        }
        /**
         * Method to set a user flight path angle.
         * @param planIndex The flight plan index to use.
         * @param segmentIndex The segment index to insert the fpa at.
         * @param segmentLegIndex The leg index to insert the fpa at.
         * @param fpa The fpa, in degrees, to set the fpa to; if undefined, deletes the fpa.
         * @throws Error if fpa is NaN.
         */
        setUserFpa(planIndex, segmentIndex, segmentLegIndex, fpa) {
            if (fpa !== undefined && isNaN(fpa)) {
                throw new Error('fpa must not be NaN');
            }
            if (!this.hasFlightPlan(planIndex)) {
                return;
            }
            const plan = this.getFlightPlan(planIndex);
            const leg = plan.tryGetLeg(segmentIndex, segmentLegIndex);
            if (leg === null) {
                return;
            }
            const verticalData = { fpa };
            // Setting a user FPA auto converts the altitude constraint to an "At" constraint
            if (fpa !== undefined) {
                verticalData.altDesc = msfsSdk.AltitudeRestrictionType.At;
            }
            this.setLegVerticalData(plan, segmentIndex, segmentLegIndex, verticalData);
        }
        /**
         * Sets vertical data into the plan and the direct to if necessary, then calculates the plan.
         * @param plan The flight plan to use.
         * @param segmentIndex The segment index to set the vertical data for.
         * @param segmentLegIndex The leg index to set the vertical data for.
         * @param verticalData The vertical data to set, will be merged with existing data.
         */
        setLegVerticalData(plan, segmentIndex, segmentLegIndex, verticalData) {
            const segment = plan.tryGetSegment(segmentIndex);
            if (segment === null || segment.legs.length <= segmentLegIndex) {
                return;
            }
            // If we are editing an altitude constraint before a vertical direct-to, cancel the vertical direct-to.
            // Note that we don't need to make any corrections for lateral direct-to indexes. The VDTO leg index can only
            // point to the lateral direct-to leg, never the lateral direct-to target leg. Since the lateral direct-to leg
            // index is always greater than the target leg index, editing either leg will correctly cancel the VDTO if it
            // is targeting the direct-to leg.
            if (this.verticalPathCalculator && verticalData.altDesc !== undefined) {
                const verticalPlan = this.verticalPathCalculator.getVerticalFlightPlan(Fms.PRIMARY_PLAN_INDEX);
                if (verticalPlan.verticalDirectIndex !== undefined && verticalPlan.verticalDirectIndex >= segment.offset + segmentLegIndex) {
                    this.publishCancelVerticalDirectTo(FmsUtils.PRIMARY_PLAN_INDEX);
                }
            }
            plan.setLegVerticalData(segmentIndex, segmentLegIndex, verticalData);
            const directToData = plan.directToData;
            // If we are editing a direct-to leg or the direct-to target leg, we need to also edit the other leg in the pair
            if (plan === this.getPrimaryFlightPlan() && this.getDirectToState() === exports.DirectToState.TOEXISTING
                && segmentIndex === directToData.segmentIndex) {
                // TODO Change nxi to not deal with adding + 3 when calling setUserConstraint and other vertical data methods
                if (segmentLegIndex === directToData.segmentLegIndex + FmsUtils.DTO_LEG_OFFSET) {
                    plan.setLegVerticalData(segmentIndex, directToData.segmentLegIndex, verticalData);
                }
                else if (segmentLegIndex === directToData.segmentLegIndex) {
                    plan.setLegVerticalData(segmentIndex, directToData.segmentLegIndex + FmsUtils.DTO_LEG_OFFSET, verticalData);
                }
            }
            plan.calculate();
        }
        /**
         * Method to check if a leg has a user specified constraint.
         * @param segmentIndex The segment index to insert the constraint at.
         * @param segmentLegIndex The leg index to insert the constraint at.
         * @deprecated Use FmsUtils.isLegAltitudeEdited in preference to this.
         * @returns Whether the leg has a user constraint.
         */
        isConstraintUser(segmentIndex, segmentLegIndex) {
            if (this.hasPrimaryFlightPlan()) {
                const plan = this.getPrimaryFlightPlan();
                const leg = plan.tryGetLeg(segmentIndex, segmentLegIndex);
                if ((leg === null || leg === void 0 ? void 0 : leg.verticalData.altDesc) !== (leg === null || leg === void 0 ? void 0 : leg.leg.altDesc) || (leg === null || leg === void 0 ? void 0 : leg.verticalData.altitude1) !== (leg === null || leg === void 0 ? void 0 : leg.leg.altitude1) || (leg === null || leg === void 0 ? void 0 : leg.verticalData.altitude2) !== (leg === null || leg === void 0 ? void 0 : leg.leg.altitude2)) {
                    return true;
                }
            }
            return false;
        }
        /**
         * Method to check if a leg constraint can be reverted to the nav data constraint.
         * @param segmentIndex The segment index to insert the constraint at.
         * @param segmentLegIndex The leg index to insert the constraint at.
         * @returns Whether the leg has a nav data constraint to be reverted to.
         */
        hasConstraint(segmentIndex, segmentLegIndex) {
            if (this.hasPrimaryFlightPlan()) {
                const plan = this.getPrimaryFlightPlan();
                const leg = plan.tryGetLeg(segmentIndex, segmentLegIndex);
                if (leg !== null && leg.leg.altDesc !== msfsSdk.AltitudeRestrictionType.Unused) {
                    switch (leg.leg.altDesc) {
                        case msfsSdk.AltitudeRestrictionType.At:
                        case msfsSdk.AltitudeRestrictionType.AtOrAbove:
                        case msfsSdk.AltitudeRestrictionType.AtOrBelow:
                            return msfsSdk.UnitType.METER.convertTo(leg.leg.altitude1, msfsSdk.UnitType.FOOT);
                        case msfsSdk.AltitudeRestrictionType.Between:
                            return msfsSdk.UnitType.METER.convertTo(leg.leg.altitude2, msfsSdk.UnitType.FOOT);
                    }
                }
            }
            return undefined;
        }
        /**
         * Manages the altitude constraints for FAF legs where vertical angle info is also provided.
         * @param proc A procedure object.
         * @returns the procedure object, after it has been changed.
         */
        manageFafAltitudeRestriction(proc) {
            proc.procedureLegs.forEach(leg => {
                if (leg.fixTypeFlags === msfsSdk.FixTypeFlags.FAF && leg.altitude2 > 0) {
                    const alt = leg.altitude1 <= leg.altitude2 ? leg.altitude1 : leg.altitude2;
                    leg.altDesc = msfsSdk.AltitudeRestrictionType.AtOrAbove;
                    leg.altitude1 = alt;
                    leg.altitude2 = 0;
                }
            });
            return proc;
        }
        /**
         * Inserts an IF leg at the beginning of a procedure if it begins with a leg type which defines a fixed origin.
         * @param proc A procedure object.
         * @returns the procedure object, after it has been changed.
         */
        tryInsertIFLeg(proc) {
            const firstLeg = proc.procedureLegs[0];
            let icao;
            switch (firstLeg === null || firstLeg === void 0 ? void 0 : firstLeg.type) {
                case msfsSdk.LegType.HA:
                case msfsSdk.LegType.HF:
                case msfsSdk.LegType.HM:
                case msfsSdk.LegType.PI:
                case msfsSdk.LegType.FD:
                case msfsSdk.LegType.FC:
                    icao = firstLeg.fixIcao;
                    break;
                case msfsSdk.LegType.FM:
                case msfsSdk.LegType.VM:
                    icao = firstLeg.originIcao;
                    break;
            }
            if (icao && icao !== msfsSdk.ICAO.emptyIcao) {
                proc.procedureLegs.unshift(msfsSdk.FlightPlan.createLeg({
                    type: msfsSdk.LegType.IF,
                    fixIcao: icao,
                    fixTypeFlags: firstLeg.fixTypeFlags & (msfsSdk.FixTypeFlags.IF | msfsSdk.FixTypeFlags.IAF)
                }));
                if ((firstLeg === null || firstLeg === void 0 ? void 0 : firstLeg.type) === msfsSdk.LegType.HF || (firstLeg === null || firstLeg === void 0 ? void 0 : firstLeg.type) === msfsSdk.LegType.PI) {
                    proc.procedureLegs[0].altDesc = firstLeg.altDesc;
                    proc.procedureLegs[0].altitude1 = firstLeg.altitude1;
                    proc.procedureLegs[0].altitude2 = firstLeg.altitude2;
                }
                // need to remove IF/IAF flags from the original first leg (now the second leg)
                const replacementLeg = msfsSdk.FlightPlan.createLeg(proc.procedureLegs[1]);
                replacementLeg.fixTypeFlags = replacementLeg.fixTypeFlags & ~(msfsSdk.FixTypeFlags.IF | msfsSdk.FixTypeFlags.IAF);
                if ((firstLeg === null || firstLeg === void 0 ? void 0 : firstLeg.type) !== msfsSdk.LegType.PI) {
                    replacementLeg.altDesc = msfsSdk.AltitudeRestrictionType.Unused;
                    replacementLeg.altitude1 = 0;
                    replacementLeg.altitude2 = 0;
                }
                proc.procedureLegs[1] = replacementLeg;
            }
            return proc;
        }
        /**
         * Checks the approach legs for an IAF fix type flag, and if one exists, amend the approach to ensure that
         * the IAF is not on a hold/pt leg and that we do not add legs prior to the IAF except in cases where we needed to add
         * an IF leg type.
         * @param proc A procedure object.
         * @returns the procedure object, after it has been changed.
         */
        tryReconcileIAFLeg(proc) {
            let iafIndex = -1;
            for (let i = 0; i < proc.procedureLegs.length; i++) {
                const leg = proc.procedureLegs[i];
                if (leg.fixTypeFlags === msfsSdk.FixTypeFlags.IAF) {
                    iafIndex = i;
                    switch (leg.type) {
                        case msfsSdk.LegType.HA:
                        case msfsSdk.LegType.HF:
                        case msfsSdk.LegType.HM:
                        case msfsSdk.LegType.PI:
                        case msfsSdk.LegType.FD:
                        case msfsSdk.LegType.FC:
                            if (iafIndex > 0) {
                                leg.fixTypeFlags &= ~msfsSdk.FixTypeFlags.IAF;
                                proc.procedureLegs[iafIndex - 1].fixTypeFlags |= msfsSdk.FixTypeFlags.IAF;
                                iafIndex--;
                            }
                    }
                    break;
                }
            }
            return proc;
        }
        /**
         * Inserts a MAP fix type flag if none exists on the approach.
         * @param proc A procedure object.
         * @returns the procedure object, after it has been changed.
         */
        tryInsertMap(proc) {
            let addMap = true;
            let runwayIndex = -1;
            for (let i = 0; i < proc.procedureLegs.length; i++) {
                const leg = proc.procedureLegs[i];
                if (leg.fixTypeFlags === msfsSdk.FixTypeFlags.MAP) {
                    addMap = false;
                    break;
                }
                if (leg.fixIcao.search('R') === 0) {
                    runwayIndex = i;
                    break;
                }
            }
            if (addMap && runwayIndex > -1) {
                proc.procedureLegs[runwayIndex].fixTypeFlags = msfsSdk.FixTypeFlags.MAP;
            }
            return proc;
        }
        /**
         * Method to remove the duplicate leg after the hold leg.
         * @param proc A procedure object.
         * @returns the procedure object, after it has been changed.
         */
        tryCleanupHold(proc) {
            for (let i = 0; i < proc.procedureLegs.length; i++) {
                const leg = proc.procedureLegs[i];
                if (leg.type === msfsSdk.LegType.HF) {
                    const next = proc.procedureLegs[i + 1];
                    if (leg.fixIcao === next.fixIcao && next.type === msfsSdk.LegType.IF) {
                        proc.procedureLegs.splice(i + 1, 1);
                    }
                }
            }
            return proc;
        }
        /**
         * Method to remove a course reversal in an approach procedure.
         * @param proc A procedure object.
         * @returns the procedure object, after it has been changed.
         */
        tryRemoveCourseReversal(proc) {
            let canRemove = false;
            if (proc.procedureLegs.length > 2) {
                const leg = proc.procedureLegs[1];
                switch (leg.type) {
                    case msfsSdk.LegType.HA:
                    case msfsSdk.LegType.HF:
                    case msfsSdk.LegType.HM:
                    case msfsSdk.LegType.PI:
                        canRemove = true;
                }
            }
            if (canRemove) {
                proc.procedureLegs.splice(1, 1);
            }
            return proc;
        }
        /**
         * Method to remove the departure from the flight plan.
         */
        async removeDeparture() {
            var _a, _b, _c, _d;
            const plan = this.getFlightPlan();
            const segmentIndex = this.ensureOnlyOneSegmentOfType(msfsSdk.FlightPlanSegmentType.Departure);
            const wasActiveLegInApproach = this.getDirectToState() === exports.DirectToState.NONE && plan.activeLateralLeg >= ((_b = (_a = FmsUtils.getApproachSegment(plan)) === null || _a === void 0 ? void 0 : _a.offset) !== null && _b !== void 0 ? _b : Infinity);
            plan.setDeparture();
            this.planClearSegment(segmentIndex, msfsSdk.FlightPlanSegmentType.Departure);
            if (plan.originAirport) {
                const updatedSegmentIndex = this.ensureOnlyOneSegmentOfType(msfsSdk.FlightPlanSegmentType.Departure);
                this.planAddOriginDestinationLeg(true, updatedSegmentIndex, plan.originAirport, plan.procedureDetails.originRunway);
                const prevLeg = plan.getPrevLeg(updatedSegmentIndex, 1);
                const nextLeg = plan.getNextLeg(updatedSegmentIndex, 0);
                if (prevLeg && nextLeg && this.isDuplicateLeg(prevLeg.leg, nextLeg.leg)) {
                    this.planRemoveDuplicateLeg(prevLeg, nextLeg);
                }
            }
            plan.calculate(0);
            // If removing the segment caused the active leg to move from before the approach into the approach, activate the
            // approach instead.
            if (!wasActiveLegInApproach) {
                const isActiveLegInApproach = this.getDirectToState() === exports.DirectToState.NONE && plan.activeLateralLeg >= ((_d = (_c = FmsUtils.getApproachSegment(plan)) === null || _c === void 0 ? void 0 : _c.offset) !== null && _d !== void 0 ? _d : Infinity);
                if (isActiveLegInApproach) {
                    if (this.isApproachVtf()) {
                        this.activateVtf();
                    }
                    else {
                        this.activateApproach();
                    }
                }
            }
        }
        /**
         * Method to remove the arrival from the flight plan.
         */
        async removeArrival() {
            var _a, _b, _c, _d;
            const plan = this.getFlightPlan();
            const segmentIndex = this.ensureOnlyOneSegmentOfType(msfsSdk.FlightPlanSegmentType.Arrival);
            const wasActiveLegInApproach = this.getDirectToState() === exports.DirectToState.NONE && plan.activeLateralLeg >= ((_b = (_a = FmsUtils.getApproachSegment(plan)) === null || _a === void 0 ? void 0 : _a.offset) !== null && _b !== void 0 ? _b : Infinity);
            plan.setArrival();
            this.planRemoveSegment(segmentIndex);
            if (plan.procedureDetails.approachIndex < 0 && plan.destinationAirport) {
                const destSegmentIndex = this.ensureOnlyOneSegmentOfType(msfsSdk.FlightPlanSegmentType.Destination);
                this.planAddOriginDestinationLeg(false, destSegmentIndex, plan.destinationAirport, plan.procedureDetails.destinationRunway);
            }
            const prevLeg = plan.getPrevLeg(segmentIndex, 0);
            const nextLeg = plan.getNextLeg(segmentIndex, -1);
            if (prevLeg && nextLeg && this.isDuplicateLeg(prevLeg.leg, nextLeg.leg)) {
                this.planRemoveDuplicateLeg(prevLeg, nextLeg);
            }
            plan.calculate(0);
            // If removing the segment caused the active leg to move from before the approach into the approach, activate the
            // approach instead.
            if (!wasActiveLegInApproach) {
                const isActiveLegInApproach = this.getDirectToState() === exports.DirectToState.NONE && plan.activeLateralLeg >= ((_d = (_c = FmsUtils.getApproachSegment(plan)) === null || _c === void 0 ? void 0 : _c.offset) !== null && _d !== void 0 ? _d : Infinity);
                if (isActiveLegInApproach) {
                    if (this.isApproachVtf()) {
                        this.activateVtf();
                    }
                    else {
                        this.activateApproach();
                    }
                }
            }
        }
        /**
         * Method to remove the approach from the flight plan.
         */
        async removeApproach() {
            const plan = this.getFlightPlan();
            ++this.updateApproachDetailsOpId;
            this.setApproachDetails(true, false, ApproachType.APPROACH_TYPE_UNKNOWN, msfsSdk.RnavTypeFlags.None, msfsSdk.RnavTypeFlags.None, false, false, null, null);
            plan.deleteUserData(Fms.VTF_FAF_DATA_KEY);
            const hasArrival = plan.procedureDetails.arrivalIndex >= 0;
            const segmentIndex = this.ensureOnlyOneSegmentOfType(msfsSdk.FlightPlanSegmentType.Approach);
            if (hasArrival) {
                const lastEnrouteSegmentIndex = this.findLastEnrouteSegmentIndex(plan);
                const segment = plan.getSegment(lastEnrouteSegmentIndex);
                const lastLegIndex = segment && segment.legs.length > 0 ? segment.legs.length - 1 : 0;
                if (plan.destinationAirport && segment.legs[lastLegIndex] && segment.legs[lastLegIndex].leg.fixIcao === plan.destinationAirport) {
                    this.planRemoveLeg(lastEnrouteSegmentIndex, lastLegIndex);
                }
                plan.setDestinationRunway();
                if (plan.procedureDetails.arrivalFacilityIcao && plan.procedureDetails.arrivalFacilityIcao !== plan.destinationAirport) {
                    const arrivalFacility = await this.facLoader.getFacility(msfsSdk.FacilityType.Airport, plan.procedureDetails.arrivalFacilityIcao);
                    this.setDestination(arrivalFacility);
                }
            }
            plan.setApproach();
            this.planRemoveSegment(segmentIndex);
            if (plan.destinationAirport) {
                const destLegSegmentIndex = hasArrival
                    ? this.ensureOnlyOneSegmentOfType(msfsSdk.FlightPlanSegmentType.Arrival)
                    : this.ensureOnlyOneSegmentOfType(msfsSdk.FlightPlanSegmentType.Destination);
                this.planAddOriginDestinationLeg(false, destLegSegmentIndex, plan.destinationAirport, plan.procedureDetails.destinationRunway);
            }
            const prevLeg = plan.getPrevLeg(segmentIndex, 0);
            const nextLeg = plan.getNextLeg(segmentIndex, -1);
            if (prevLeg && nextLeg && this.isDuplicateLeg(prevLeg.leg, nextLeg.leg)) {
                this.planRemoveDuplicateLeg(prevLeg, nextLeg);
            }
            plan.deleteUserData(exports.FmsFplUserDataKey.VisualApproach);
            plan.deleteUserData(exports.FmsFplUserDataKey.VisualApproachRunway);
            plan.deleteUserData(exports.FmsFplUserDataKey.VfrApproach);
            plan.deleteUserData(exports.FmsFplUserDataKey.ApproachSkipCourseReversal);
            // Without an approach, we can't be in a missed approach, so deactivate the missed approach.
            this.publisher.pub(this.lnavControlTopicMap['activate_missed_approach'], false, true, true);
            plan.calculate(0);
        }
        /**
         * Activates a flight plan leg.
         * @param segmentIndex The index of the flight plan segment containing the leg to activate.
         * @param segmentLegIndex The index of the leg to activate in its containing segment.
         * @param planIndex The index of the flight plan containing the leg to activate. Defaults to the index of the primary
         * flight plan.
         * @param inhibitImmediateSequence Whether to inhibit immediate automatic sequencing past the activated leg. Defaults
         * to `false`.
         */
        activateLeg(segmentIndex, segmentLegIndex, planIndex = Fms.PRIMARY_PLAN_INDEX, inhibitImmediateSequence = false) {
            const plan = this.getFlightPlan(planIndex);
            const dtoState = this.getDirectToState();
            const activeFplIndex = dtoState === exports.DirectToState.TORANDOM ? Fms.DTO_RANDOM_PLAN_INDEX : Fms.PRIMARY_PLAN_INDEX;
            let needConvertObsToDto = false;
            if (planIndex === activeFplIndex) {
                if (dtoState === exports.DirectToState.TOEXISTING) {
                    needConvertObsToDto = plan.directToData.segmentIndex < segmentIndex
                        || (plan.directToData.segmentIndex === segmentIndex && plan.directToData.segmentLegIndex < segmentLegIndex);
                }
                else {
                    const segment = plan.getSegment(segmentIndex);
                    const globalLegIndex = segment.offset + segmentLegIndex;
                    needConvertObsToDto = globalLegIndex > plan.activeLateralLeg;
                }
            }
            const oldActiveSegmentIndex = plan.getSegmentIndex(plan.activeLateralLeg);
            const legOffset = this.cancelObs(needConvertObsToDto);
            this._activateLeg(planIndex, segmentIndex, segmentLegIndex + (oldActiveSegmentIndex === segmentIndex ? legOffset : 0), inhibitImmediateSequence);
        }
        /**
         * Activates a flight plan leg.
         * @param planIndex The index of the flight plan containing the leg to activate.
         * @param segmentIndex The index of the flight plan segment containing the leg to activate.
         * @param segmentLegIndex The index of the leg to activate in its containing segment.
         * @param inhibitImmediateSequence Whether to inhibit immediate automatic sequencing past the activated leg.
         */
        _activateLeg(planIndex, segmentIndex, segmentLegIndex, inhibitImmediateSequence) {
            var _a, _b, _c, _d;
            const plan = this.getFlightPlan(planIndex);
            const segment = plan.getSegment(segmentIndex);
            const globalLegIndex = segment.offset + segmentLegIndex;
            const oldDtoState = this.getDirectToState();
            if (planIndex === Fms.PRIMARY_PLAN_INDEX && this.flightPlanner.activePlanIndex != Fms.PRIMARY_PLAN_INDEX) {
                this.flightPlanner.setActivePlanIndex(Fms.PRIMARY_PLAN_INDEX);
                this.flightPlanner.deleteFlightPlan(Fms.DTO_RANDOM_PLAN_INDEX);
            }
            if (oldDtoState === exports.DirectToState.TORANDOM
                || (oldDtoState === exports.DirectToState.TOEXISTING && (segmentIndex !== plan.directToData.segmentIndex || segmentLegIndex !== plan.directToData.segmentLegIndex))) {
                // Removing a lateral direct-to also cancels any existing vertical direct-to
                this.publishCancelVerticalDirectTo(FmsUtils.PRIMARY_PLAN_INDEX);
            }
            // Activate or deactivate missed approach state depending on if we are activating a leg in the missed approach.
            if (segment.legs[segmentLegIndex]) {
                this.publisher.pub(this.lnavControlTopicMap['activate_missed_approach'], msfsSdk.BitFlags.isAll(segment.legs[segmentLegIndex].flags, msfsSdk.LegDefinitionFlags.MissedApproach), true, true);
            }
            // If we are activating a leg before a direct to existing sequence, we need to remove the sequence.
            if (planIndex === Fms.PRIMARY_PLAN_INDEX
                && (segmentIndex < plan.directToData.segmentIndex || (segmentIndex === plan.directToData.segmentIndex && segmentLegIndex <= plan.directToData.segmentLegIndex))) {
                this.removeDirectToExisting(globalLegIndex);
            }
            else {
                plan.setCalculatingLeg(globalLegIndex);
                plan.calculate(Math.max(0, globalLegIndex - 1));
                plan.setLateralLeg(globalLegIndex);
            }
            // If we are activating a leg before a VTF leg, we need to check to see if we need to remove the discontinuity leg
            // preceding the VTF leg.
            if (FmsUtils.isVtfApproachLoaded(plan)) {
                const approachSegment = FmsUtils.getApproachSegment(plan);
                const vtfFafLeg = FmsUtils.getApproachVtfLeg(plan);
                if (approachSegment !== undefined && vtfFafLeg !== undefined) {
                    const vtfLegSegmentLegIndex = approachSegment.legs.indexOf(vtfFafLeg);
                    const discoLegExists = msfsSdk.BitFlags.isAll((_b = (_a = approachSegment.legs[vtfLegSegmentLegIndex - 1]) === null || _a === void 0 ? void 0 : _a.flags) !== null && _b !== void 0 ? _b : 0, msfsSdk.LegDefinitionFlags.VectorsToFinal);
                    if (discoLegExists && plan.activeLateralLeg < approachSegment.offset + vtfLegSegmentLegIndex) {
                        const prevLeg = approachSegment.legs[vtfLegSegmentLegIndex - 2];
                        const publishedLegIcao = (_c = plan.getUserData(Fms.VTF_FAF_DATA_KEY)) !== null && _c !== void 0 ? _c : '';
                        const legTerminatorIcao = prevLeg === undefined ? '' : (_d = msfsSdk.FlightPlanUtils.getTerminatorIcao(prevLeg.leg)) !== null && _d !== void 0 ? _d : '';
                        if (msfsSdk.ICAO.isFacility(publishedLegIcao) && msfsSdk.ICAO.isFacility(legTerminatorIcao) && publishedLegIcao === legTerminatorIcao) {
                            plan.removeLeg(approachSegment.segmentIndex, vtfLegSegmentLegIndex - 1);
                        }
                    }
                }
            }
            this.publisher.pub(this.lnavControlTopicMap['suspend_sequencing'], false, true, false);
            if (inhibitImmediateSequence) {
                this.publisher.pub(this.lnavControlTopicMap['lnav_inhibit_next_sequence'], true, true, false);
            }
        }
        /**
         * Gets the required flight path angle, in degrees, for a vertical direct-to if it were to be activated immediately.
         * Positive angles represent descending paths.
         * @param constraintGlobalLegIndex The global index of the target flight plan leg of the vertical direct-to.
         * @param altitudeMeters The target altitude, in meters, of the vertical direct-to.
         * @returns The required flight path angle, in degrees, for the specified vertical direct-to if it were to be
         * activated immediately, or `undefined` if an angle cannot be calculated. Positive angles represent descending
         * paths.
         */
        async getVerticalDirectRequiredFpa(constraintGlobalLegIndex, altitudeMeters) {
            const activePlanIndex = this.flightPlanner.activePlanIndex;
            // TODO: support off-route direct to
            if (activePlanIndex !== Fms.PRIMARY_PLAN_INDEX) {
                return undefined;
            }
            if (!this.flightPlanner.hasFlightPlan(activePlanIndex) || !this.verticalPathCalculator) {
                return undefined;
            }
            const lateralPlan = this.flightPlanner.getFlightPlan(activePlanIndex);
            if (constraintGlobalLegIndex >= lateralPlan.length) {
                return undefined;
            }
            let verticalDirectLegIndex = constraintGlobalLegIndex;
            // Check if we are trying to activate VDTO on a direct-to target leg
            const segmentIndex = lateralPlan.getSegmentIndex(constraintGlobalLegIndex);
            const segmentLegIndex = lateralPlan.getSegmentLegIndex(constraintGlobalLegIndex);
            if (lateralPlan.directToData.segmentIndex === segmentIndex && lateralPlan.directToData.segmentLegIndex === segmentLegIndex) {
                verticalDirectLegIndex += FmsUtils.DTO_LEG_OFFSET;
            }
            else if (lateralPlan.directToData.segmentIndex === segmentIndex && lateralPlan.directToData.segmentLegIndex === segmentLegIndex - FmsUtils.DTO_LEG_OFFSET) {
                constraintGlobalLegIndex -= FmsUtils.DTO_LEG_OFFSET;
            }
            let activeLegIndex = this.lnavTrackedLegIndex.get();
            // Cannot activate VDTO to a constraint that is located before the active leg.
            if (activeLegIndex > verticalDirectLegIndex) {
                return undefined;
            }
            // We need to make sure all legs from the active leg to the VDTO target leg are calculated, so we force a calculate.
            try {
                await lateralPlan.calculate();
            }
            catch (_a) {
                // Abort the operation if the calculate failed.
                return undefined;
            }
            activeLegIndex = this.lnavTrackedLegIndex.get();
            // Check active plan index, plan length, and active leg index again in case they changed.
            if (activePlanIndex !== this.flightPlanner.activePlanIndex || verticalDirectLegIndex >= lateralPlan.length || activeLegIndex > verticalDirectLegIndex) {
                return undefined;
            }
            return this.getVerticalDirectFpa(lateralPlan, verticalDirectLegIndex, altitudeMeters, activeLegIndex, msfsSdk.UnitType.NMILE.convertTo(this.lnavLegDistanceRemaining.get(), msfsSdk.UnitType.METER), msfsSdk.UnitType.FOOT.convertTo(this.indicatedAlt.get(), msfsSdk.UnitType.METER));
        }
        /**
         * Activates a vertical direct to a selected constraint.
         * @param constraintGlobalLegIndex The global index of the target flight plan leg of the vertical direct-to.
         * @param altitudeMeters The target altitude, in meters, of the vertical direct-to.
         * @param displayAsFlightLevel Whether the target altitude should be displayed as a flight level. Defaults to false.
         * @param fpa The flight path angle, in degrees, for the vertical direct-to. Positive values indicate descending
         * paths. The FPA will be clamped to between 0 and 6 degrees. If not defined, then the flight path angle will be set
         * to the default VNAV descent FPA.
         * @returns Whether the vertical direct was activated or not.
         */
        activateVerticalDirect(constraintGlobalLegIndex, altitudeMeters, displayAsFlightLevel = false, fpa) {
            if (!this.verticalPathCalculator) {
                return false;
            }
            const activePlanIndex = this.flightPlanner.activePlanIndex;
            // TODO: support off-route direct to
            if (activePlanIndex !== FmsUtils.PRIMARY_PLAN_INDEX) {
                return false;
            }
            if (!this.flightPlanner.hasFlightPlan(activePlanIndex)) {
                return false;
            }
            const lateralPlan = this.flightPlanner.getFlightPlan(activePlanIndex);
            if (constraintGlobalLegIndex >= lateralPlan.length) {
                return false;
            }
            let verticalDirectLegIndex = constraintGlobalLegIndex;
            // Check if we are trying to activate VDTO on a direct-to target leg
            const segmentIndex = lateralPlan.getSegmentIndex(constraintGlobalLegIndex);
            const segmentLegIndex = lateralPlan.getSegmentLegIndex(constraintGlobalLegIndex);
            if (lateralPlan.directToData.segmentIndex === segmentIndex && lateralPlan.directToData.segmentLegIndex === segmentLegIndex) {
                verticalDirectLegIndex += FmsUtils.DTO_LEG_OFFSET;
            }
            else if (lateralPlan.directToData.segmentIndex === segmentIndex && lateralPlan.directToData.segmentLegIndex === segmentLegIndex - FmsUtils.DTO_LEG_OFFSET) {
                constraintGlobalLegIndex -= FmsUtils.DTO_LEG_OFFSET;
            }
            // Cannot activate VDTO to a constraint that is located before the active leg.
            if (this.lnavTrackedLegIndex.get() > verticalDirectLegIndex) {
                return false;
            }
            // Does delete the user FPA
            const verticalData = {
                altDesc: msfsSdk.AltitudeRestrictionType.Unused,
                altitude1: 0,
                altitude2: 0,
                displayAltitude1AsFlightLevel: false,
                displayAltitude2AsFlightLevel: false,
                fpa: undefined,
            };
            // Delete all constraints before the vnav direct to leg
            for (let i = 0; i < constraintGlobalLegIndex; i++) {
                lateralPlan.setLegVerticalData(i, verticalData);
            }
            // Create an "At" constraint at the vnav direct to leg, and delete the fpa
            this.setLegVerticalData(lateralPlan, segmentIndex, segmentLegIndex, {
                phase: msfsSdk.VerticalFlightPhase.Descent,
                altDesc: msfsSdk.AltitudeRestrictionType.At,
                altitude1: altitudeMeters,
                altitude2: 0,
                displayAltitude1AsFlightLevel: displayAsFlightLevel,
                displayAltitude2AsFlightLevel: false,
                fpa: undefined,
            });
            this.publisher.pub(this.vnavControlTopicMap['vnav_set_vnav_direct_to'], {
                planIndex: activePlanIndex,
                globalLegIndex: verticalDirectLegIndex,
                fpa: fpa === undefined ? undefined : msfsSdk.MathUtils.clamp(fpa, 0, 6),
            }, true, false);
            return true;
        }
        /**
         * Cancels the currently active vertical direct-to.
         */
        cancelVerticalDirectTo() {
            if (!this.verticalPathCalculator) {
                return;
            }
            const activePlanIndex = this.flightPlanner.activePlanIndex;
            // TODO: support off-route direct to
            if (activePlanIndex !== Fms.PRIMARY_PLAN_INDEX) {
                return;
            }
            this.publishCancelVerticalDirectTo(activePlanIndex);
        }
        /**
         * Publishes a command to cancel the current vertical direct-to for a given flight plan.
         * @param planIndex The index of the flight plan for which to cancel the vertical direct-to.
         */
        publishCancelVerticalDirectTo(planIndex) {
            if (!this.vnavControlTopicMap) {
                return;
            }
            this.publisher.pub(this.vnavControlTopicMap['vnav_set_vnav_direct_to'], {
                planIndex,
                globalLegIndex: -1
            }, true, false);
        }
        /**
         * Computes the desired flight path angle, in degrees, for a vertical direct-to. Positive angles represent descending
         * paths. The FPA is computed such that the vertical path for the direct-to is placed 200 feet above the airplane,
         * with the TOD some distance in front of the airplane.
         *
         * If there are any uncalculated or VNAV-ineligible legs between the active leg and the vertical direct-to target leg
         * (inclusive), the FPA cannot be computed and will be `undefined`. If the airplane's indicated altitude is at or
         * below the vertical direct-to target altitude, the computed FPA will be zero.
         * @param plan The lateral flight plan for which to
         * @param directLegIndex The global index of the vertical direct-to target flight plan leg.
         * @param directAltitude The target altitude of the vertical direct-to, in meters.
         * @param activeLegIndex The global index of the active flight plan leg.
         * @param legDistanceRemaining The along-track distance from the airplane's present position to the end of the active
         * flight plan leg, in meters.
         * @param indicatedAlt The indicated altitude of the airplane, in meters.
         * @returns The desired flight path angle, in degrees, for the specified vertical direct-to, or `undefined` if the
         * FPA could not be computed. Positive angles represent descending paths.
         */
        getVerticalDirectFpa(plan, directLegIndex, directAltitude, activeLegIndex, legDistanceRemaining, indicatedAlt) {
            let distance = Math.max(0, legDistanceRemaining);
            for (const leg of plan.legs(false, activeLegIndex + 1, directLegIndex + 1)) {
                if (leg.calculated === undefined) {
                    return undefined;
                }
                if (!GarminVNavUtils.isLegVNavEligible(leg)) {
                    return undefined;
                }
                distance += leg.calculated.distanceWithTransitions;
            }
            const altitudeDelta = indicatedAlt - directAltitude;
            if (altitudeDelta < 30) {
                return 0;
            }
            return msfsSdk.VNavUtils.getFpa(distance, indicatedAlt - directAltitude + 60);
        }
        /**
         * Checks whether an approach can be activated. An approach can be activated if and only if the primary flight plan
         * has a non-vectors-to-final approach loaded.
         * @returns Whether an approach can be activated.
         */
        canActivateApproach() {
            const plan = this.hasPrimaryFlightPlan() && this.getPrimaryFlightPlan();
            if (!plan) {
                return false;
            }
            return FmsUtils.isApproachLoaded(plan) && !FmsUtils.isVtfApproachLoaded(plan);
        }
        /**
         * Activates an approach. Activating an approach activates a Direct To to the first approach waypoint of the primary
         * flight plan, and attempts to load the primary approach frequency (if one exists) to the nav radios. If the primary
         * flight plan does not have an approach loaded, this method has no effect.
         */
        activateApproach() {
            if (!this.canActivateApproach()) {
                return;
            }
            const approachSegmentIndex = this.ensureOnlyOneSegmentOfType(msfsSdk.FlightPlanSegmentType.Approach, false);
            this.createDirectToExisting(approachSegmentIndex, 0);
            for (const index of this.navRadioIndexes) {
                this.setLocFrequency(index);
            }
            this.publisher.pub(this.fmsTopicMap['fms_approach_activate'], undefined, true, false);
        }
        /**
         * Checks whether vectors-to-final can be activated. VTF can be activated if and only if the primary flight plan has
         * an approach loaded.
         * @returns Whether vectors-to-final can be activated.
         */
        canActivateVtf() {
            const plan = this.hasPrimaryFlightPlan() && this.getPrimaryFlightPlan();
            if (!plan) {
                return false;
            }
            return FmsUtils.isApproachLoaded(plan);
        }
        /**
         * Activates vectors-to-final. Activating vectors-to-final activates the primary flight plan's vectors-to-final leg,
         * and attempts to load the primary approach frequency (if one exists) to the nav radios. If the primary flight plan
         * has a non-VTF approach loaded, it will be replaced by its VTF counterpart. If the primary flight plan has no
         * approach loaded, this method has no effect.
         */
        async activateVtf() {
            var _a, _b;
            if (!this.canActivateVtf()) {
                return;
            }
            this.cancelObs(false);
            const plan = this.getPrimaryFlightPlan();
            let approachType = ApproachType.APPROACH_TYPE_UNKNOWN;
            if (!FmsUtils.isVtfApproachLoaded(plan)) {
                // if a VTF approach is not loaded; replace the current approach with its VTF counterpart.
                try {
                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                    const airport = await this.facLoader.getFacility(msfsSdk.FacilityType.Airport, plan.procedureDetails.approachFacilityIcao);
                    if (plan.procedureDetails.approachIndex >= 0) {
                        await this.insertApproach(airport, plan.procedureDetails.approachIndex, -1);
                        approachType = airport.approaches[plan.procedureDetails.approachIndex].approachType;
                    }
                    else {
                        const visApproachData = plan.getUserData(exports.FmsFplUserDataKey.VisualApproach);
                        const vfrApproachData = plan.getUserData(exports.FmsFplUserDataKey.VfrApproach);
                        if (visApproachData) {
                            const runway = msfsSdk.RunwayUtils.matchOneWayRunwayFromDesignation(airport, visApproachData.runwayDesignation);
                            if (!runway) {
                                return;
                            }
                            approachType = msfsSdk.AdditionalApproachType.APPROACH_TYPE_VISUAL;
                            await this.insertApproach(airport, -1, -1, runway.direction, runway.runwayDesignator);
                        }
                        else if (vfrApproachData) {
                            await this.insertVfrApproach(airport, vfrApproachData.approachIndex, true);
                        }
                    }
                }
                catch (e) {
                    console.warn(`Fms: failed to activate VTF approach... ${e}`);
                    if (e instanceof Error) {
                        console.error(e.stack);
                    }
                    return;
                }
            }
            else {
                approachType = this.approachDetails.get().type;
            }
            const approachSegment = FmsUtils.getApproachSegment(plan);
            if (approachSegment === undefined) {
                // This should never happen.
                return;
            }
            // If a VTF leg was not inserted, activate the normal faf leg.
            const legToActivate = (_a = FmsUtils.getApproachVtfLeg(plan)) !== null && _a !== void 0 ? _a : FmsUtils.getApproachFafLeg(plan);
            if (legToActivate === undefined) {
                console.warn('Fms: failed to activate VTF approach');
                return;
            }
            let legToActivateSegmentLegIndex = approachSegment.legs.indexOf(legToActivate);
            // If we are activating a VTF leg, check to see if it is preceded by a discontinuity leg. If it is not, then
            // we need to add one.
            if (msfsSdk.BitFlags.isAll(legToActivate.flags, msfsSdk.LegDefinitionFlags.VectorsToFinalFaf)
                && !msfsSdk.BitFlags.isAll((_b = approachSegment.legs[legToActivateSegmentLegIndex - 1]) === null || _b === void 0 ? void 0 : _b.flags, msfsSdk.LegDefinitionFlags.VectorsToFinal)) {
                plan.addLeg(approachSegment.segmentIndex, msfsSdk.FlightPlan.createLeg({ type: msfsSdk.LegType.ThruDiscontinuity }), legToActivateSegmentLegIndex, msfsSdk.LegDefinitionFlags.VectorsToFinal);
                legToActivateSegmentLegIndex++;
            }
            this._activateLeg(Fms.PRIMARY_PLAN_INDEX, approachSegment.segmentIndex, legToActivateSegmentLegIndex, true);
            let firstNavRadioIndex = undefined;
            for (const index of this.navRadioIndexes) {
                firstNavRadioIndex !== null && firstNavRadioIndex !== void 0 ? firstNavRadioIndex : (firstNavRadioIndex = index);
                this.setLocFrequency(index);
            }
            this.publisher.pub(this.fmsTopicMap['fms_approach_activate'], undefined, true, false);
            if (firstNavRadioIndex !== undefined) {
                switch (approachType) {
                    case ApproachType.APPROACH_TYPE_ILS:
                    case ApproachType.APPROACH_TYPE_LDA:
                    case ApproachType.APPROACH_TYPE_LOCALIZER:
                    case ApproachType.APPROACH_TYPE_LOCALIZER_BACK_COURSE:
                    case ApproachType.APPROACH_TYPE_SDF:
                    case ApproachType.APPROACH_TYPE_VOR:
                    case ApproachType.APPROACH_TYPE_VORDME:
                        this.publisher.pub(this.cdiControlTopicMap['cdi_src_set'], { type: msfsSdk.NavSourceType.Nav, index: firstNavRadioIndex }, true, false);
                        break;
                }
            }
        }
        /**
         * Method to check if the approach is VTF.
         * @returns whether the approach is VTF.
         */
        isApproachVtf() {
            if (!this.hasPrimaryFlightPlan()) {
                return false;
            }
            const plan = this.getPrimaryFlightPlan();
            return FmsUtils.isVtfApproachLoaded(plan);
        }
        /**
         * Checks if the missed approach can be activated.
         * @returns whether the missed approach can be activated.
         */
        canMissedApproachActivate() {
            if (this.activateMaprState.get()) {
                return false;
            }
            if (this.getDirectToState() === exports.DirectToState.TORANDOM) {
                return false;
            }
            const plan = this.hasPrimaryFlightPlan() && this.getPrimaryFlightPlan();
            if (!plan) {
                return false;
            }
            if (this.cdiSource.type === msfsSdk.NavSourceType.Gps && plan.activeLateralLeg < plan.length - 1 && plan.segmentCount > 0) {
                const segmentIndex = plan.getSegmentIndex(plan.activeLateralLeg);
                if (segmentIndex > 0) {
                    const segment = plan.getSegment(segmentIndex);
                    if (segment.segmentType === msfsSdk.FlightPlanSegmentType.Approach
                        && msfsSdk.BitFlags.isAll(segment.legs[segment.legs.length - 1].flags, msfsSdk.LegDefinitionFlags.MissedApproach)) {
                        for (let i = 0; i < segment.legs.length; i++) {
                            const leg = segment.legs[i];
                            if (leg.leg.fixTypeFlags === msfsSdk.FixTypeFlags.FAF) {
                                if (plan.activeLateralLeg - segment.offset >= i) {
                                    return true;
                                }
                            }
                        }
                    }
                }
            }
            return false;
        }
        /**
         * Activates the missed approach.
         */
        activateMissedApproach() {
            if (this.canMissedApproachActivate()) {
                this.publisher.pub(this.lnavControlTopicMap['activate_missed_approach'], true, true, true);
            }
        }
        /**
         * Creates and activates a Direct-To targeting a waypoint not in the primary flight plan (off-route Direct-To).
         * @param target The Direct-To's target waypoint facility or its ICAO.
         * @param course The magnetic course for the Direct-To, in degrees. If not defined, then the Direct-To will be
         * initiated from the airplane's present position.
         */
        createDirectToRandom(target, course) {
            this.cancelObs(false);
            // Creating a lateral direct-to also cancels any existing vertical direct-to
            this.publishCancelVerticalDirectTo(FmsUtils.PRIMARY_PLAN_INDEX);
            // We can't be in an missed approach while on an off-route direct-to, so deactivate the missed approach.
            this.publisher.pub(this.lnavControlTopicMap['activate_missed_approach'], false, true, true);
            const icao = typeof target === 'string' ? target : target.icao;
            const plan = this.flightPlanner.createFlightPlan(1);
            plan.setCalculatingLeg(0);
            plan.setLateralLeg(0);
            // Delete all segments for the random dto plan
            for (let i = plan.segmentCount - 1; i >= 0; i--) {
                if (plan.getSegment(i) !== undefined) {
                    plan.removeSegment(i);
                }
            }
            plan.insertSegment(0, msfsSdk.FlightPlanSegmentType.RandomDirectTo, undefined, true);
            const segment = plan.getSegment(0);
            if (segment) {
                const discoLeg = msfsSdk.FlightPlan.createLeg({ type: msfsSdk.LegType.Discontinuity });
                // Dupe the disco leg if we have a defined course so that DTO sequences are always 3 legs long
                const dtoOriginLeg = course === undefined ? this.createDTOOriginLeg(this.ppos) : discoLeg;
                const dtoTargetLeg = this.createDTODirectLeg(icao, undefined, course);
                plan.addLeg(0, discoLeg, 0, msfsSdk.LegDefinitionFlags.DirectTo);
                plan.addLeg(0, dtoOriginLeg, 1, msfsSdk.LegDefinitionFlags.DirectTo);
                plan.addLeg(0, dtoTargetLeg, 2, msfsSdk.LegDefinitionFlags.DirectTo);
                plan.calculate(0);
                plan.setCalculatingLeg(2);
                plan.setLateralLeg(2);
                if (this.flightPlanner.activePlanIndex !== 1) {
                    this.flightPlanner.setActivePlanIndex(1);
                }
                this.publisher.pub(this.lnavControlTopicMap['suspend_sequencing'], false, true, false);
            }
        }
        /**
         * Creates and activates a Direct-To to an existing waypoint in the primary flight plan (on-route Direct-To).
         * @param segmentIndex The index of the segment containing the Direct-To's target flight plan leg.
         * @param segmentLegIndex The index of the Direct-To's target flight plan leg in its containing segment.
         * @param course The magnetic course for the Direct-To, in degrees. If not defined, then the Direct-To will be
         * initiated from the airplane's present position.
         * @param deletePriorConstraints Whether to delete all altitude constraints before the Direct-To target leg.
         * User-defined flight path angles will be preserved.
         */
        createDirectToExisting(segmentIndex, segmentLegIndex, course, deletePriorConstraints = false) {
            this.cancelObs(false);
            const plan = this.getPrimaryFlightPlan();
            const segment = plan.getSegment(segmentIndex);
            const leg = segment.legs[segmentLegIndex];
            let legIndexDelta = 0;
            if (plan.directToData.segmentIndex > -1 && plan.directToData.segmentLegIndex > -1) {
                legIndexDelta -= plan.directToData.segmentIndex === segmentIndex && segmentLegIndex > plan.directToData.segmentLegIndex ? 3 : 0;
                if (this.getDirectToState() === exports.DirectToState.TOEXISTING) {
                    this.removeDirectToExisting();
                }
                else {
                    plan.removeLeg(plan.directToData.segmentIndex, plan.directToData.segmentLegIndex + 1);
                    plan.removeLeg(plan.directToData.segmentIndex, plan.directToData.segmentLegIndex + 1);
                    plan.removeLeg(plan.directToData.segmentIndex, plan.directToData.segmentLegIndex + 1);
                }
            }
            // Creating a lateral direct-to also cancels any existing vertical direct-to
            this.publishCancelVerticalDirectTo(FmsUtils.PRIMARY_PLAN_INDEX);
            plan.setDirectToData(segmentIndex, segmentLegIndex + legIndexDelta);
            if (segment && leg) {
                const discoLeg = msfsSdk.FlightPlan.createLeg({ type: msfsSdk.LegType.Discontinuity });
                // Dup the disco leg if we have a defined course so that DTO sequences are always 3 legs long
                const dtoOriginLeg = course === undefined ? this.createDTOOriginLeg(this.ppos) : discoLeg;
                const dtoTargetLeg = this.createDTODirectLeg(leg.leg.fixIcao, leg.leg, course);
                const dtoLegFlags = (leg.flags & msfsSdk.LegDefinitionFlags.MissedApproach) | msfsSdk.LegDefinitionFlags.DirectTo;
                plan.addLeg(segmentIndex, discoLeg, segmentLegIndex + legIndexDelta + 1, dtoLegFlags);
                plan.addLeg(segmentIndex, dtoOriginLeg, segmentLegIndex + legIndexDelta + 2, dtoLegFlags);
                plan.addLeg(segmentIndex, dtoTargetLeg, segmentLegIndex + legIndexDelta + FmsUtils.DTO_LEG_OFFSET, dtoLegFlags);
                plan.setLegVerticalData(segmentIndex, segmentLegIndex + legIndexDelta + FmsUtils.DTO_LEG_OFFSET, leg.verticalData);
                this._activateLeg(Fms.PRIMARY_PLAN_INDEX, segmentIndex, segmentLegIndex + legIndexDelta + FmsUtils.DTO_LEG_OFFSET, false);
                if (deletePriorConstraints) {
                    // Does not delete the user FPA
                    const verticalData = {
                        altDesc: msfsSdk.AltitudeRestrictionType.Unused,
                        altitude1: 0,
                        altitude2: 0,
                        displayAltitude1AsFlightLevel: false,
                        displayAltitude2AsFlightLevel: false,
                    };
                    const globalLegIndex = segment.offset + segmentLegIndex + legIndexDelta;
                    // Delete all constraints before the new direct to leg
                    for (let i = 0; i < globalLegIndex; i++) {
                        plan.setLegVerticalData(i, verticalData);
                    }
                }
                // If advanced VNAV is not supported, activating a lateral direct-to will also activate a vertical direct-to with
                // minimum FPA of 3 degrees.
                if (!this.isAdvancedVnav) {
                    this.handleDirectToVerticalDirect(segmentIndex, segmentLegIndex + legIndexDelta);
                }
            }
        }
        /**
         * Creates a Direct-To origin IF leg.
         * @param ppos The current plane position.
         * @returns a Direct-To origin IF leg.
         */
        createDTOOriginLeg(ppos) {
            return msfsSdk.FlightPlan.createLeg({
                type: msfsSdk.LegType.IF,
                lat: ppos.lat,
                lon: ppos.lon
            });
        }
        /**
         * Creates a Direct-To target leg.
         * @param icao is the icao.
         * @param leg The FlightPlanLeg.
         * @param course The magnetic course for the Direct To.
         * @returns a Direct-To leg.
         */
        createDTODirectLeg(icao, leg, course) {
            let legType;
            if (course === undefined) {
                legType = msfsSdk.LegType.DF;
                const planeHeading = SimVar.GetSimVarValue('PLANE HEADING DEGREES MAGNETIC', 'degrees');
                course = planeHeading === 0 ? 360 : planeHeading;
            }
            else {
                legType = msfsSdk.LegType.CF;
            }
            const dtoLeg = msfsSdk.FlightPlan.createLeg(leg !== null && leg !== void 0 ? leg : {});
            dtoLeg.type = legType;
            dtoLeg.fixIcao = icao;
            dtoLeg.course = course;
            dtoLeg.trueDegrees = false;
            dtoLeg.turnDirection = msfsSdk.LegTurnDirection.None;
            return dtoLeg;
        }
        /**
         * Handles the automatic activation of a vertical direct-to after a lateral direct-to has been activated. This will
         * activate a vertical direct-to targeting the first constraint at or after the target leg of the lateral direct-to,
         * with a minimum flight path angle of 3 degrees. If the first constraint at or after the target leg of the lateral
         * direct-to is not a descent constraint, the vertical direct-to will not be activated.
         * @param segmentIndex The index of the segment containing the target flight plan leg of the new lateral direct-to.
         * @param segmentLegIndex The index of the target flight plan leg of the new lateral direct-to in its segment.
         */
        async handleDirectToVerticalDirect(segmentIndex, segmentLegIndex) {
            if (!this.verticalPathCalculator) {
                return;
            }
            const lateralPlan = this.getPrimaryFlightPlan();
            const segment = lateralPlan.getSegment(segmentIndex);
            const dtoLegSegmentLegIndex = segmentLegIndex + FmsUtils.DTO_LEG_OFFSET;
            const verticalPlan = this.verticalPathCalculator.getVerticalFlightPlan(Fms.PRIMARY_PLAN_INDEX);
            try {
                // Wait until the vertical plan has been rebuilt with the direct-to legs included. Abort after 5 seconds.
                await msfsSdk.Wait.awaitSubEvent(this.verticalPathCalculator.vnavCalculated, planIndex => {
                    if (planIndex !== Fms.PRIMARY_PLAN_INDEX) {
                        return false;
                    }
                    const verticalLeg = msfsSdk.VNavUtils.getVerticalLegFromPlan(verticalPlan, segment.offset + dtoLegSegmentLegIndex);
                    return verticalLeg.segmentIndex === segmentIndex && verticalLeg.legIndex === dtoLegSegmentLegIndex;
                }, 5000);
                // Wait until LNAV is tracking the newly created direct-to leg. Abort after 2 seconds.
                await msfsSdk.Wait.awaitSubscribable(this.lnavTrackedLegIndex, index => {
                    return index === segment.offset + dtoLegSegmentLegIndex;
                }, true, 2000);
                // If the lateral direct-to has changed or has been removed, abort.
                if (lateralPlan.directToData.segmentIndex !== segmentIndex || lateralPlan.directToData.segmentLegIndex !== segmentLegIndex) {
                    return;
                }
                const constraint = msfsSdk.VNavUtils.getConstraintFromLegIndex(verticalPlan, segment.offset + dtoLegSegmentLegIndex);
                if (constraint && constraint.type !== 'climb' && constraint.type !== 'missed') {
                    const lateralLeg = lateralPlan.tryGetLeg(constraint.index);
                    if (lateralLeg) {
                        let fpa = await this.getVerticalDirectRequiredFpa(constraint.index, lateralLeg.verticalData.altitude1);
                        if (lateralPlan.tryGetLeg(constraint.index) === lateralLeg) {
                            if (fpa !== undefined) {
                                fpa = msfsSdk.MathUtils.clamp(fpa, 3, 6);
                            }
                            this.activateVerticalDirect(constraint.index, lateralLeg.verticalData.altitude1, lateralLeg.verticalData.displayAltitude1AsFlightLevel, fpa);
                        }
                    }
                }
            }
            catch (_a) {
                // noop
            }
        }
        /**
         * Cancels the currently active on-route or off-route direct-to.
         * @returns Whether an active direct-to was cancelled.
         */
        cancelDirectTo() {
            const directToState = this.getDirectToState();
            if (directToState === exports.DirectToState.TOEXISTING) {
                this.cancelObs(true);
                const plan = this.getPrimaryFlightPlan();
                this._activateLeg(Fms.PRIMARY_PLAN_INDEX, plan.directToData.segmentIndex, plan.directToData.segmentLegIndex, false);
                return true;
            }
            else if (directToState === exports.DirectToState.TORANDOM) {
                this.cancelObs(false);
                if (this.activateNearestLeg()) {
                    return true;
                }
                // We failed to activate the nearest leg...
                if (this.hasPrimaryFlightPlan()) {
                    const plan = this.getPrimaryFlightPlan();
                    if (plan.length > 0) {
                        // If the plan is not empty, activate the first leg.
                        const segmentIndex = plan.getSegmentIndex(0);
                        this._activateLeg(Fms.PRIMARY_PLAN_INDEX, segmentIndex, 0, false);
                    }
                    else {
                        // If the primary plan is empty, then just reset the active flight plan to the primary plan.
                        this.flightPlanner.setActivePlanIndex(Fms.PRIMARY_PLAN_INDEX);
                        this.flightPlanner.deleteFlightPlan(Fms.DTO_RANDOM_PLAN_INDEX);
                    }
                    return true;
                }
            }
            return false;
        }
        /**
         * Empties the primary flight plan.
         */
        async emptyPrimaryFlightPlan() {
            if (!this.flightPlanner.hasFlightPlan(Fms.PRIMARY_PLAN_INDEX)) {
                return;
            }
            // Cancel any existing vertical direct-to.
            this.publishCancelVerticalDirectTo(FmsUtils.PRIMARY_PLAN_INDEX);
            // Deactivate the missed approach.
            this.publisher.pub(this.lnavControlTopicMap['activate_missed_approach'], false, true, true);
            const plan = this.flightPlanner.getFlightPlan(Fms.PRIMARY_PLAN_INDEX);
            for (let i = plan.segmentCount - 1; i >= 0; i--) {
                plan.removeSegment(i);
            }
            plan.addSegment(0, msfsSdk.FlightPlanSegmentType.Departure);
            plan.addSegment(1, msfsSdk.FlightPlanSegmentType.Enroute);
            plan.addSegment(2, msfsSdk.FlightPlanSegmentType.Destination);
            plan.removeOriginAirport();
            plan.removeDestinationAirport();
            plan.setDirectToData(-1);
            plan.setDeparture();
            plan.setArrival();
            plan.setApproach();
            plan.deleteUserData(exports.FmsFplUserDataKey.VisualApproach);
            plan.deleteUserData(exports.FmsFplUserDataKey.VisualApproachRunway);
            plan.deleteUserData(exports.FmsFplUserDataKey.VfrApproach);
            plan.deleteUserData(exports.FmsFplUserDataKey.ApproachSkipCourseReversal);
            ++this.updateApproachDetailsOpId;
            this.setApproachDetails(true, false, ApproachType.APPROACH_TYPE_UNKNOWN, msfsSdk.RnavTypeFlags.None, msfsSdk.RnavTypeFlags.None, false, false, null, null);
            plan.deleteUserData(Fms.VTF_FAF_DATA_KEY);
            plan.setCalculatingLeg(0);
            plan.setLateralLeg(0);
            plan.setVerticalLeg(0);
        }
        /**
         * Empties the primary flight plan and deletes its name.
         */
        async deletePrimaryFlightPlan() {
            if (!this.flightPlanner.hasFlightPlan(Fms.PRIMARY_PLAN_INDEX)) {
                return;
            }
            const plan = this.flightPlanner.getFlightPlan(Fms.PRIMARY_PLAN_INDEX);
            plan.deleteUserData(exports.FmsFplUserDataKey.Name);
            await this.emptyPrimaryFlightPlan();
        }
        /**
         * Resets all flight plans to their initial empty states, and cancels any active off-route Direct-To.
         */
        async resetAllFlightPlans() {
            await this.deletePrimaryFlightPlan();
            this.flightPlanner.setActivePlanIndex(Fms.PRIMARY_PLAN_INDEX);
            this.flightPlanner.deleteFlightPlan(Fms.DTO_RANDOM_PLAN_INDEX);
        }
        /**
         * Builds a flight plan to preview a procedure.
         * @param calculator The flight path calculator to assign to the preview plan.
         * @param facility The airport facility to load the approach from
         * @param procType The type of procedure to preview.
         * @param procIndex The procedure index selected.
         * @param transIndex The transition index selected.
         * @param oneWayRunway The one way runway to build the preview with, if any.
         * @param rwyTransIndex The runway transition index selected, if any.
         * @param visualRunwayNumber is the visual runway number, if any.
         * @param visualRunwayDesignator is the visual runway designator, if any.
         * @returns A Promise which is fulfilled with whether the preview plan was successfully built.
         */
        async buildProcedurePreviewPlan(calculator, facility, procType, procIndex, transIndex, oneWayRunway, rwyTransIndex, visualRunwayNumber, visualRunwayDesignator) {
            const plan = new msfsSdk.FlightPlan(0, calculator, msfsSdk.FlightPlanner.buildDefaultLegName);
            let procedureLegObject;
            switch (procType) {
                case exports.ProcedureType.APPROACH:
                    procedureLegObject = await this.buildApproachLegs(facility, procIndex, transIndex, undefined);
                    plan.addSegment(0, msfsSdk.FlightPlanSegmentType.Approach, undefined, false);
                    break;
                case exports.ProcedureType.ARRIVAL: {
                    const runwayIndex = rwyTransIndex !== null && rwyTransIndex !== void 0 ? rwyTransIndex : -1;
                    procedureLegObject = this.buildArrivalLegs(facility, procIndex, transIndex, runwayIndex, oneWayRunway);
                    plan.addSegment(0, msfsSdk.FlightPlanSegmentType.Arrival, undefined, false);
                    break;
                }
                case exports.ProcedureType.DEPARTURE: {
                    const runwayIndex = rwyTransIndex !== null && rwyTransIndex !== void 0 ? rwyTransIndex : -1;
                    procedureLegObject = this.buildDepartureLegs(facility, procIndex, transIndex, runwayIndex, oneWayRunway);
                    plan.addSegment(0, msfsSdk.FlightPlanSegmentType.Departure, undefined, false);
                    break;
                }
                case exports.ProcedureType.VISUALAPPROACH:
                    if (visualRunwayNumber !== undefined && visualRunwayDesignator !== undefined) {
                        const visualRunway = msfsSdk.RunwayUtils.matchOneWayRunway(facility, visualRunwayNumber, visualRunwayDesignator);
                        procedureLegObject = await this.buildApproachLegs(facility, -1, transIndex, visualRunway);
                        plan.addSegment(0, msfsSdk.FlightPlanSegmentType.Approach, undefined, false);
                    }
                    break;
            }
            if (procedureLegObject && procedureLegObject.procedureLegs.length > 0) {
                const visualRunway = visualRunwayNumber !== undefined && visualRunwayDesignator !== undefined
                    ? msfsSdk.RunwayUtils.matchOneWayRunway(facility, visualRunwayNumber, visualRunwayDesignator)
                    : undefined;
                procedureLegObject.procedureLegs.forEach((l) => {
                    var _a;
                    // If the leg's fix is a visual approach fix, we need to add it to the facility repository so that others can
                    // look it up properly.
                    if (visualRunway !== undefined && msfsSdk.ICAO.isFacility(l.fixIcao) && msfsSdk.ICAO.getFacilityType(l.fixIcao) === msfsSdk.FacilityType.VIS) {
                        this.addVisualFacilityFromLeg(l, visualRunway.designation);
                    }
                    plan.addLeg(0, l, undefined, (_a = l.legDefinitionFlags) !== null && _a !== void 0 ? _a : msfsSdk.LegDefinitionFlags.None, false);
                });
                if (procType === exports.ProcedureType.APPROACH) {
                    // Adds missed approach legs
                    if (visualRunwayNumber === undefined && visualRunwayDesignator === undefined && procedureLegObject.procedureLegs.length > 0) {
                        const missedLegs = facility.approaches[procIndex].missedLegs;
                        if (missedLegs && missedLegs.length > 0) {
                            let maphIndex = -1;
                            for (let m = missedLegs.length - 1; m >= 0; m--) {
                                switch (missedLegs[m].type) {
                                    case msfsSdk.LegType.HA:
                                    case msfsSdk.LegType.HF:
                                    case msfsSdk.LegType.HM:
                                        maphIndex = m - 1;
                                        break;
                                }
                            }
                            for (let n = 0; n < missedLegs.length; n++) {
                                const newLeg = msfsSdk.FlightPlan.createLeg(missedLegs[n]);
                                if (maphIndex > 0 && n === maphIndex) {
                                    newLeg.fixTypeFlags |= msfsSdk.FixTypeFlags.MAHP;
                                    plan.addLeg(0, newLeg, undefined, msfsSdk.LegDefinitionFlags.MissedApproach, false);
                                }
                                else {
                                    plan.addLeg(0, newLeg, undefined, msfsSdk.LegDefinitionFlags.MissedApproach, false);
                                }
                            }
                        }
                    }
                }
                await plan.calculate(0);
                return plan;
            }
            else {
                return plan;
            }
        }
        /**
         * Builds a flight plan to preview procedure transitions.
         * @param calculator The flight path calculator to assign to the preview plan.
         * @param facility The airport facility to which the procedure to preview belongs.
         * @param procType The type of procedure to preview.
         * @param procIndex The index of the procedure to preview.
         * @param excludeTransitionIndex The index of the transition to exclude in the preview.
         * @param rwyTransIndex The index of the procedure's runway transition.
         * @returns The index of the procedure transition preview plan.
         */
        async buildProcedureTransitionPreviewPlan(calculator, facility, procType, procIndex, excludeTransitionIndex, rwyTransIndex) {
            const plan = new msfsSdk.FlightPlan(0, calculator, msfsSdk.FlightPlanner.buildDefaultLegName);
            let legs;
            switch (procType) {
                case exports.ProcedureType.DEPARTURE:
                    if (facility.departures[procIndex] && rwyTransIndex !== undefined) {
                        legs = this.buildDepartureTransitionPreviewLegs(facility.departures[procIndex], excludeTransitionIndex, rwyTransIndex);
                    }
                    break;
                case exports.ProcedureType.ARRIVAL:
                    if (facility.arrivals[procIndex] && rwyTransIndex !== undefined) {
                        legs = this.buildArrivalTransitionPreviewLegs(facility.arrivals[procIndex], excludeTransitionIndex, rwyTransIndex);
                    }
                    break;
                case exports.ProcedureType.APPROACH:
                    if (facility.approaches[procIndex]) {
                        legs = this.buildApproachTransitionPreviewLegs(facility.approaches[procIndex], excludeTransitionIndex);
                    }
                    break;
            }
            if (legs && legs.length > 0) {
                plan.addSegment(0, msfsSdk.FlightPlanSegmentType.Enroute, undefined, false);
                legs.forEach((l) => {
                    plan.addLeg(0, l, undefined, 0, false);
                });
                await plan.calculate(0);
            }
            return plan;
        }
        /**
         * Builds a sequence of legs for a departure transition preview. The sequence consists of the legs of each departure
         * transition in order. Discontinuity legs separate legs of different transitions.
         * @param departure A departure.
         * @param excludeTransitionIndex The index of the transition to exclude in the preview.
         * @param rwyTransIndex The runway transition index of the departure.
         * @returns A sequence of legs for a departure transition preview.
         */
        buildDepartureTransitionPreviewLegs(departure, excludeTransitionIndex, rwyTransIndex) {
            var _a;
            const runwayTransition = departure.runwayTransitions[rwyTransIndex];
            if (!runwayTransition && departure.runwayTransitions.length > 0) {
                return [];
            }
            const insertProcObject = { procedureLegs: [] };
            const legs = [];
            const preTransitionLegs = [];
            const lastCommonLeg = departure.commonLegs[departure.commonLegs.length - 1];
            const lastPreTransitionLeg = lastCommonLeg !== null && lastCommonLeg !== void 0 ? lastCommonLeg : runwayTransition.legs[runwayTransition.legs.length - 1];
            const secondLastPreTransitionLeg = lastPreTransitionLeg
                ? lastCommonLeg
                    ? (_a = departure.commonLegs[departure.commonLegs.length - 2]) !== null && _a !== void 0 ? _a : runwayTransition.legs[runwayTransition.legs.length - 1]
                    : runwayTransition.legs[runwayTransition.legs.length - 2]
                : undefined;
            secondLastPreTransitionLeg && preTransitionLegs.push(secondLastPreTransitionLeg);
            lastPreTransitionLeg && preTransitionLegs.push(lastPreTransitionLeg);
            const transitions = departure.enRouteTransitions;
            for (let i = 0; i < transitions.length; i++) {
                if (i === excludeTransitionIndex) {
                    continue;
                }
                const transition = transitions[i];
                if (transition.legs.length > 0) {
                    insertProcObject.procedureLegs.push(...preTransitionLegs);
                    for (let j = 0; j < transition.legs.length; j++) {
                        const leg = transition.legs[j];
                        if (j === 0 && lastPreTransitionLeg && this.isDuplicateIFLeg(lastPreTransitionLeg, leg)) {
                            continue;
                        }
                        insertProcObject.procedureLegs.push(leg);
                    }
                    this.tryCleanupHold(insertProcObject);
                    legs.push(...insertProcObject.procedureLegs, msfsSdk.FlightPlan.createLeg({ type: msfsSdk.LegType.Discontinuity }));
                    insertProcObject.procedureLegs.length = 0;
                }
            }
            return legs;
        }
        /**
         * Builds a sequence of legs for an arrival transition preview. The sequence consists of the legs of each arrival
         * transition in order. Discontinuity legs separate legs of different transitions.
         * @param arrival An arrival.
         * @param excludeTransitionIndex The index of the transition to exclude in the preview.
         * @param rwyTransIndex The runway transition index of the arrival.
         * @returns A sequence of legs for an arrival transition preview.
         */
        buildArrivalTransitionPreviewLegs(arrival, excludeTransitionIndex, rwyTransIndex) {
            var _a;
            const runwayTransition = arrival.runwayTransitions[rwyTransIndex];
            if (!runwayTransition && arrival.runwayTransitions.length > 0) {
                return [];
            }
            const insertProcObject = { procedureLegs: [] };
            const legs = [];
            const firstCommonLeg = arrival.commonLegs[0];
            const firstPostTransitionLeg = firstCommonLeg !== null && firstCommonLeg !== void 0 ? firstCommonLeg : runwayTransition.legs[0];
            const secondPostTransitionLeg = firstPostTransitionLeg
                ? firstCommonLeg
                    ? (_a = arrival.commonLegs[1]) !== null && _a !== void 0 ? _a : runwayTransition.legs[0]
                    : runwayTransition.legs[1]
                : undefined;
            const transitions = arrival.enRouteTransitions;
            for (let i = 0; i < transitions.length; i++) {
                if (i === excludeTransitionIndex) {
                    continue;
                }
                const transition = transitions[i];
                if (transition.legs.length > 0) {
                    for (let j = 0; j < transition.legs.length; j++) {
                        insertProcObject.procedureLegs.push(transition.legs[j]);
                    }
                    const lastTransitionLeg = insertProcObject.procedureLegs[insertProcObject.procedureLegs.length - 1];
                    if (firstPostTransitionLeg && !this.isDuplicateIFLeg(lastTransitionLeg, firstPostTransitionLeg)) {
                        insertProcObject.procedureLegs.push(firstPostTransitionLeg);
                        // need to add the second post-transition leg if the last transition leg is a PI leg and first post-
                        // transition leg is an IF so that the calculator can get an inbound course for the PI leg.
                        if (lastTransitionLeg.type === msfsSdk.LegType.PI && firstPostTransitionLeg.type === msfsSdk.LegType.IF && secondPostTransitionLeg) {
                            insertProcObject.procedureLegs.push(secondPostTransitionLeg);
                        }
                    }
                    this.tryInsertIFLeg(insertProcObject);
                    this.tryCleanupHold(insertProcObject);
                    legs.push(...insertProcObject.procedureLegs, msfsSdk.FlightPlan.createLeg({ type: msfsSdk.LegType.Discontinuity }));
                    insertProcObject.procedureLegs.length = 0;
                }
            }
            return legs;
        }
        /**
         * Builds a sequence of legs for an approach transition preview. The sequence consists of the legs of each approach
         * transition in order, followed by the first leg of the final approach. Discontinuity legs separate legs of
         * different transitions.
         * @param approach An approach.
         * @param excludeTransitionIndex The index of the transition to exclude in the preview.
         * @returns A sequence of legs for an approach transition preview.
         */
        buildApproachTransitionPreviewLegs(approach, excludeTransitionIndex) {
            const insertProcObject = { procedureLegs: [] };
            const legs = [];
            const firstFinalLeg = approach.finalLegs[0];
            const secondFinalLeg = approach.finalLegs[1];
            const transitions = approach.transitions;
            for (let i = 0; i < transitions.length; i++) {
                if (i === excludeTransitionIndex) {
                    continue;
                }
                const transition = transitions[i];
                if (transition.legs.length > 0) {
                    for (let j = 0; j < transition.legs.length; j++) {
                        insertProcObject.procedureLegs.push(transition.legs[j]);
                    }
                    const lastTransitionLeg = insertProcObject.procedureLegs[insertProcObject.procedureLegs.length - 1];
                    if (firstFinalLeg && !this.isDuplicateIFLeg(lastTransitionLeg, firstFinalLeg)) {
                        insertProcObject.procedureLegs.push(firstFinalLeg);
                        // need to add the second final approach leg if the last transition leg is a PI leg and first final leg is
                        // an IF so that the calculator can get an inbound course for the PI leg.
                        if (lastTransitionLeg.type === msfsSdk.LegType.PI && firstFinalLeg.type === msfsSdk.LegType.IF && secondFinalLeg) {
                            insertProcObject.procedureLegs.push(secondFinalLeg);
                        }
                    }
                    this.tryInsertIFLeg(insertProcObject);
                    this.tryCleanupHold(insertProcObject);
                    legs.push(...insertProcObject.procedureLegs, msfsSdk.FlightPlan.createLeg({ type: msfsSdk.LegType.Discontinuity }));
                    insertProcObject.procedureLegs.length = 0;
                }
            }
            return legs;
        }
        /**
         * Builds a flight plan to preview a VFR approach procedure.
         * @param calculator The flight path calculator to assign to the preview plan.
         * @param facility The airport facility containing the published approach on which the VFR approach to preview is
         * based.
         * @param approachIndex The index of the published approach on which the VFR approach to preview is based.
         * @param isVtf Whether to preview the approach as a vectors-to-final (VTF) approach.
         * @returns A Promise which will be fulfilled with the preview plan after it has been built.
         */
        async buildVfrApproachPreviewPlan(calculator, facility, approachIndex, isVtf) {
            var _a;
            const plan = new msfsSdk.FlightPlan(0, calculator, msfsSdk.FlightPlanner.buildDefaultLegName);
            const procedureLegObject = await this.buildVfrApproachLegs(facility, approachIndex, isVtf);
            plan.addSegment(0, msfsSdk.FlightPlanSegmentType.Approach, undefined, false);
            if (procedureLegObject && procedureLegObject.procedureLegs.length > 0) {
                for (const leg of procedureLegObject.procedureLegs) {
                    plan.addLeg(0, leg, undefined, (_a = leg.legDefinitionFlags) !== null && _a !== void 0 ? _a : msfsSdk.LegDefinitionFlags.None, false);
                }
                await plan.calculate(0);
            }
            return plan;
        }
        /**
         * Builds a temporary flight plan to preview an airway entry.
         * @param airway The airway object.
         * @param entry The entry intersection facility.
         * @param exit The exit intersection facility.
         * @returns the index of the temporary flight plan.
         */
        buildAirwayPreviewSegment(airway, entry, exit) {
            this.flightPlanner.deleteFlightPlan(Fms.PROC_PREVIEW_PLAN_INDEX);
            const plan = this.flightPlanner.createFlightPlan(Fms.PROC_PREVIEW_PLAN_INDEX);
            const airwayLegObject = this.buildAirwayLegs(airway, entry, exit);
            plan.insertSegment(0, msfsSdk.FlightPlanSegmentType.Enroute, airway.name, false);
            if (airwayLegObject.procedureLegs.length > 0) {
                airwayLegObject.procedureLegs.forEach((l) => {
                    plan.addLeg(0, l, undefined, msfsSdk.LegDefinitionFlags.None, false);
                });
                plan.calculate(0, true);
            }
            return Fms.PROC_PREVIEW_PLAN_INDEX;
        }
        /**
         * Inserts an airway segment into the flight plan.
         * @param airway The airway object.
         * @param entry The entry intersection facility.
         * @param exit The exit intersection facility.
         * @param segmentIndex The index of the segment containing the airway entry leg.
         * @param segmentLegIndex The index of the airway entry leg in its containing leg.
         * @returns The index of the inserted airway segment.
         */
        insertAirwaySegment(airway, entry, exit, segmentIndex, segmentLegIndex) {
            const plan = this.getFlightPlan();
            // If the entry leg is the target of a direct-to, insert the airway segment after the direct-to leg sequence.
            if (plan.directToData.segmentIndex === segmentIndex && plan.directToData.segmentLegIndex === segmentLegIndex) {
                segmentLegIndex += FmsUtils.DTO_LEG_OFFSET;
            }
            const entrySegment = plan.tryGetSegment(segmentIndex);
            // Editing the plan prior to an existing vertical direct-to cancels the vertical direct-to
            if (entrySegment && this.verticalPathCalculator) {
                const entryLegGlobalIndex = entrySegment.offset + segmentLegIndex;
                const verticalPlan = this.verticalPathCalculator.getVerticalFlightPlan(Fms.PRIMARY_PLAN_INDEX);
                if (verticalPlan.verticalDirectIndex !== undefined && verticalPlan.verticalDirectIndex > entryLegGlobalIndex) {
                    this.publishCancelVerticalDirectTo(FmsUtils.PRIMARY_PLAN_INDEX);
                }
            }
            const airwaySegmentIndex = this.prepareAirwaySegment(`${airway.name}.${msfsSdk.ICAO.getIdent(exit.icao)}`, segmentIndex, segmentLegIndex);
            const airwayLegObject = this.buildAirwayLegs(airway, entry, exit);
            const airwayLegs = airwayLegObject.procedureLegs;
            for (let i = 1; i < airwayLegs.length; i++) {
                this.planAddLeg(airwaySegmentIndex, airwayLegs[i]);
            }
            // handle duplicates
            const airwaySegment = plan.getSegment(airwaySegmentIndex);
            const lastLeg = airwaySegment.legs[airwaySegment.legs.length - 1];
            const nextLeg = plan.getNextLeg(airwaySegmentIndex + 1, -1);
            if (lastLeg && nextLeg && this.isDuplicateLeg(lastLeg.leg, nextLeg.leg)) {
                const nextLegIndex = plan.getLegIndexFromLeg(nextLeg);
                const nextLegSegmentIndex = plan.getSegmentIndex(nextLegIndex);
                const nextLegSegment = plan.getSegment(nextLegSegmentIndex);
                if (this.getAirwayLegType(plan, nextLegSegmentIndex, nextLegIndex - nextLegSegment.offset) === exports.AirwayLegType.ENTRY) {
                    // the duplicated leg is an airway entry -> remove the segment containing it (the segment is guaranteed to
                    // contain just the one leg)
                    this.planRemoveSegment(nextLegSegmentIndex);
                }
                else {
                    this.planRemoveDuplicateLeg(lastLeg, nextLeg);
                }
            }
            plan.calculate(0, true);
            return airwaySegmentIndex;
        }
        /**
         * Prepares a new, empty airway segment in the primary flight plan which is ready to accept airway legs. Also
         * modifies the segment containing the entry leg, if necessary, either splitting it following the entry leg if it is
         * a non-airway enroute segment, or removing all legs following the entry leg if it is an airway segment. If the
         * entry leg is the last leg in its segment, a new non-airway enroute segment will be inserted after the entry leg
         * segment if the entry leg segment is the last segment in the flight plan or if the following segment is not an
         * enroute segment. If the entry leg is the entry for an existing airway segment, the existing airway segment will be
         * removed.
         * @param airwayName The name of the airway.
         * @param entrySegmentIndex The index of the segment containing the airway entry leg.
         * @param entrySegmentLegIndex The index of the airway entry leg in its segment.
         * @returns The index of the new airway segment.
         */
        prepareAirwaySegment(airwayName, entrySegmentIndex, entrySegmentLegIndex) {
            const plan = this.getPrimaryFlightPlan();
            if (entrySegmentIndex < plan.directToData.segmentIndex
                || (entrySegmentIndex === plan.directToData.segmentIndex && entrySegmentLegIndex < plan.directToData.segmentLegIndex)) {
                this.removeDirectToExisting();
            }
            const entrySegment = plan.getSegment(entrySegmentIndex);
            const nextSegment = entrySegmentIndex + 1 < plan.segmentCount ? plan.getSegment(entrySegmentIndex + 1) : undefined;
            let airwaySegmentIndex = entrySegmentIndex + 1;
            let removeLegsSegmentIndex = -1;
            let removeLegsFromIndex = -1;
            if (entrySegment.airway !== undefined) {
                // the entry leg is within an existing airway segment -> remove all legs in the same segment after the entry leg
                removeLegsSegmentIndex = entrySegmentIndex;
                removeLegsFromIndex = entrySegmentLegIndex + 1;
            }
            else if (entrySegmentLegIndex === entrySegment.legs.length - 1 && (nextSegment === null || nextSegment === void 0 ? void 0 : nextSegment.airway) !== undefined) {
                // the entry leg is the entry leg for an existing airway segment -> remove all legs from the existing airway segment
                removeLegsSegmentIndex = entrySegmentIndex + 1;
                removeLegsFromIndex = 0;
            }
            // remove legs as required
            if (removeLegsSegmentIndex >= 0) {
                const removeLegsSegment = plan.getSegment(removeLegsSegmentIndex);
                if (this.getAirwayLegType(plan, removeLegsSegmentIndex, removeLegsSegment.legs.length - 1) === exports.AirwayLegType.EXIT_ENTRY) {
                    // preserve the airway entry leg
                    const lastLeg = removeLegsSegment.legs[removeLegsSegment.legs.length - 1];
                    this.planInsertSegmentOfType(msfsSdk.FlightPlanSegmentType.Enroute, removeLegsSegmentIndex + 1);
                    this.planAddLeg(removeLegsSegmentIndex + 1, lastLeg.leg, 0);
                }
                if (removeLegsFromIndex > 0) {
                    while (removeLegsSegment.legs.length > removeLegsFromIndex) {
                        this.planRemoveLeg(removeLegsSegmentIndex, removeLegsSegment.legs.length - 1, true, true);
                    }
                }
                else {
                    this.planRemoveSegment(removeLegsSegmentIndex);
                }
            }
            const entryLegIsNotLastLegInEntrySegment = entrySegment.legs.length - 1 > entrySegmentLegIndex;
            if (entryLegIsNotLastLegInEntrySegment) {
                // split the segment after the entry leg
                airwaySegmentIndex = this.splitSegmentForAirway(plan, entrySegmentIndex, entrySegmentLegIndex);
            }
            else if (plan.getSegment(entrySegmentIndex).segmentType === msfsSdk.FlightPlanSegmentType.Enroute
                && ((nextSegment === null || nextSegment === void 0 ? void 0 : nextSegment.segmentType) !== msfsSdk.FlightPlanSegmentType.Enroute)) {
                // entry leg is the last leg in its segment and the following segment doesn't exist or is not an enroute segment
                plan.insertSegment(airwaySegmentIndex, msfsSdk.FlightPlanSegmentType.Enroute);
            }
            plan.insertSegment(airwaySegmentIndex, msfsSdk.FlightPlanSegmentType.Enroute, airwayName);
            return airwaySegmentIndex;
        }
        /**
         * Splits a segment in preparation for inserting an airway segment after an entry leg within the segment to be split.
         * If the segment containing the entry leg is an enroute segment, it will be split into two enroute segments, with
         * the split occurring immediately after the entry leg. If the segment containing the entry leg is a departure
         * segment, all legs after the entry leg will be removed from the segment.
         * @param plan The flight plan to edit.
         * @param segmentIndex The index of the segment containing the airway entry leg.
         * @param segmentLegIndex The index of the airway entry leg in its containing segment.
         * @returns The index into which to insert the new airway segment now that the segment containing the entry leg has
         * been split.
         */
        splitSegmentForAirway(plan, segmentIndex, segmentLegIndex) {
            const segment = plan.getSegment(segmentIndex);
            if (segment.segmentType === msfsSdk.FlightPlanSegmentType.Enroute) {
                const nextSegmentIndex = this.planInsertSegmentOfType(msfsSdk.FlightPlanSegmentType.Enroute, segmentIndex + 1);
                // Move legs after leg index to new segment
                // It's funky without the i++, but it works correctly because the length of the segment is changing
                for (let i = segmentLegIndex + 1; i < segment.legs.length;) {
                    const leg = segment.legs[i].leg;
                    this.planAddLeg(nextSegmentIndex, leg);
                    this.planRemoveLeg(segmentIndex, i);
                }
                return nextSegmentIndex;
            }
            else if (segment.segmentType === msfsSdk.FlightPlanSegmentType.Departure) {
                // Remove legs after leg index
                // It's funky without the i++, but it works correctly because the length of the segment is changing
                for (let i = segmentLegIndex + 1; i < segment.legs.length;) {
                    this.planRemoveLeg(segmentIndex, i);
                }
                return segmentIndex + 1;
            }
            return segmentIndex;
        }
        /**
         * Builds a legs for an airway.
         * @param airway The airway object.
         * @param entry The entry intersection facility.
         * @param exit The exit intersection facility.
         * @returns the InsertProcedureObject.
         */
        buildAirwayLegs(airway, entry, exit) {
            const insertAirwayObject = { procedureLegs: [] };
            const waypoints = airway.waypoints;
            const entryIndex = waypoints.findIndex((w) => w.icao === entry.icao);
            const exitIndex = waypoints.findIndex((w) => w.icao === exit.icao);
            const ascending = exitIndex > entryIndex;
            if (ascending) {
                for (let i = entryIndex; i <= exitIndex; i++) {
                    const leg = msfsSdk.FlightPlan.createLeg({
                        fixIcao: waypoints[i].icao,
                        type: i === entryIndex ? msfsSdk.LegType.IF : msfsSdk.LegType.TF
                    });
                    insertAirwayObject.procedureLegs.push(leg);
                }
            }
            else {
                for (let i = entryIndex; i >= exitIndex; i--) {
                    const leg = msfsSdk.FlightPlan.createLeg({
                        fixIcao: waypoints[i].icao,
                        type: i === entryIndex ? msfsSdk.LegType.IF : msfsSdk.LegType.TF
                    });
                    insertAirwayObject.procedureLegs.push(leg);
                }
            }
            return insertAirwayObject;
        }
        /**
         * Method to remove an airway from the flight plan.
         * @param segmentIndex is the segment index of the airway to remove.
         */
        removeAirway(segmentIndex) {
            var _a, _b, _c, _d;
            const plan = this.getFlightPlan();
            const segment = plan.tryGetSegment(segmentIndex);
            if (segment === null || segment.airway === undefined) {
                return;
            }
            const wasActiveLegInApproach = this.getDirectToState() === exports.DirectToState.NONE && plan.activeLateralLeg >= ((_b = (_a = FmsUtils.getApproachSegment(plan)) === null || _a === void 0 ? void 0 : _a.offset) !== null && _b !== void 0 ? _b : Infinity);
            let combineSegments = false;
            const nextSegmentIsAirway = plan.getSegment(segmentIndex + 1).airway;
            const priorSegmentEnrouteNonAirway = plan.getSegment(segmentIndex - 1).segmentType === msfsSdk.FlightPlanSegmentType.Enroute
                && plan.getSegment(segmentIndex - 1).airway === undefined;
            const nextSegmentEnrouteNonAirway = plan.getSegment(segmentIndex + 1).segmentType === msfsSdk.FlightPlanSegmentType.Enroute
                && plan.getSegment(segmentIndex + 1).airway === undefined;
            if (priorSegmentEnrouteNonAirway && nextSegmentEnrouteNonAirway) {
                combineSegments = true;
            }
            let entryLeg = undefined;
            if (nextSegmentIsAirway) {
                entryLeg = segment.legs[segment.legs.length - 1].leg;
            }
            this.planRemoveSegment(segmentIndex);
            if (combineSegments) {
                this.mergeSegments(plan, segmentIndex - 1);
            }
            if (priorSegmentEnrouteNonAirway && entryLeg !== undefined) {
                this.planAddLeg(segmentIndex - 1, entryLeg);
            }
            else if (entryLeg !== undefined) {
                const newSegmentIndex = this.planInsertSegmentOfType(msfsSdk.FlightPlanSegmentType.Enroute, segmentIndex);
                this.planAddLeg(newSegmentIndex, entryLeg);
            }
            plan.calculate(0);
            // If removing the segment caused the active leg to move from before the approach into the approach, activate the
            // approach instead.
            if (!wasActiveLegInApproach) {
                const isActiveLegInApproach = this.getDirectToState() === exports.DirectToState.NONE && plan.activeLateralLeg >= ((_d = (_c = FmsUtils.getApproachSegment(plan)) === null || _c === void 0 ? void 0 : _c.offset) !== null && _d !== void 0 ? _d : Infinity);
                if (isActiveLegInApproach) {
                    if (this.isApproachVtf()) {
                        this.activateVtf();
                    }
                    else {
                        this.activateApproach();
                    }
                }
            }
        }
        /**
         * Merges the legs of two consecutive segments into a single segment. All legs in the second segment are moved to the
         * first, and the second segment is removed from the flight plan.
         * @param plan The flight plan to modify.
         * @param segmentIndex The index of the first segment to merge.
         */
        mergeSegments(plan, segmentIndex) {
            const segmentToGrow = plan.getSegment(segmentIndex);
            const segmentToRemove = plan.getSegment(segmentIndex + 1);
            const segmentToGrowOrigLength = segmentToGrow.legs.length;
            segmentToRemove.legs.forEach((l) => {
                plan.addLeg(segmentIndex, l.leg, undefined, l.flags);
            });
            if (plan.directToData.segmentIndex === segmentIndex + 1) {
                plan.setDirectToData(segmentIndex, segmentToGrowOrigLength + plan.directToData.segmentLegIndex);
            }
            this.planRemoveSegment(segmentIndex + 1);
        }
        /**
         * Inserts a hold-at-waypoint leg to a flight plan. The hold leg will be inserted immediately after the specified
         * parent leg. The hold leg must have the same fix as the parent leg.
         * @param planIndex The index of the flight plan to add the hold to.
         * @param segmentIndex The index of the segment that contains the hold's parent leg.
         * @param segmentLegIndex The index of the hold's parent leg in its segment.
         * @param holdLeg The hold leg to add.
         * @returns The inserted hold-at-waypoint leg, or `undefined` if the leg could not be inserted.
         */
        insertHold(planIndex, segmentIndex, segmentLegIndex, holdLeg) {
            const plan = this.hasFlightPlan(planIndex) && this.getFlightPlan(planIndex);
            if (!plan) {
                return undefined;
            }
            const parentLeg = plan.tryGetLeg(segmentIndex, segmentLegIndex);
            if ((parentLeg === null || parentLeg === void 0 ? void 0 : parentLeg.leg.fixIcao) !== holdLeg.fixIcao) {
                return undefined;
            }
            if (planIndex === Fms.PRIMARY_PLAN_INDEX) {
                const airwayLegType = this.getAirwayLegType(plan, segmentIndex, segmentLegIndex);
                switch (airwayLegType) {
                    case exports.AirwayLegType.ONROUTE:
                    case exports.AirwayLegType.EXIT:
                    case exports.AirwayLegType.EXIT_ENTRY: {
                        // Insert the hold leg. The hold leg is guaranteed to end up in a non-airway enroute segment.
                        const insertedHoldLeg = this.handleAirwayInsertLeg(plan, segmentIndex, holdLeg, segmentLegIndex + 1);
                        if (!insertedHoldLeg) {
                            return undefined;
                        }
                        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                        const holdLegSegment = plan.getSegmentFromLeg(insertedHoldLeg);
                        const holdLegSegmentIndex = holdLegSegment.legs.indexOf(insertedHoldLeg);
                        // We need to move the hold's parent leg out of the airway and into the same segment as the hold leg. The
                        // parent leg's segment index and segment leg index should not have been changed by inserting the hold leg.
                        this.planRemoveLeg(segmentIndex, segmentLegIndex);
                        this.planAddLeg(holdLegSegment.segmentIndex, parentLeg.leg, holdLegSegmentIndex);
                        this.setLegVerticalData(plan, holdLegSegment.segmentIndex, holdLegSegmentIndex, parentLeg.verticalData);
                        return insertedHoldLeg;
                    }
                    case exports.AirwayLegType.ENTRY: // Inserting a hold at an airway entry leg turns the hold leg into the entry leg, so there is no extra logic needed.
                    default:
                        return this.planAddLeg(segmentIndex, holdLeg, segmentLegIndex + 1);
                }
            }
            else {
                if (this.getDirectToState() === exports.DirectToState.TORANDOM) {
                    this.cancelObs(plan.activeLateralLeg < 3);
                }
                const insertedHoldLeg = plan.addLeg(segmentIndex, holdLeg);
                this.publisher.pub(this.lnavControlTopicMap['suspend_sequencing'], false, true, false);
                return insertedHoldLeg;
            }
        }
        /**
         * Edits a hold in a flight plan. The existing hold leg will be removed from the flight plan and a new hold leg with
         * the edited parameters will be inserted in its place.
         * @param planIndex The index of the flight plan containing the hold to edit.
         * @param segmentIndex The index of the segment containing the hold to edit.
         * @param segmentLegIndex The index of the hold leg in its containing segment.
         * @param holdLeg A leg describing the new hold parameters to apply.
         * @returns The edited hold leg, or `undefined` if the hold could not be edited.
         */
        editHold(planIndex, segmentIndex, segmentLegIndex, holdLeg) {
            const plan = this.hasFlightPlan(planIndex) && this.getFlightPlan(planIndex);
            if (!plan) {
                return undefined;
            }
            const leg = plan.tryGetLeg(segmentIndex, segmentLegIndex);
            if (!leg || !msfsSdk.FlightPlanUtils.isHoldLeg(leg.leg.type)) {
                return undefined;
            }
            const parentLeg = plan.tryGetLeg(segmentIndex, segmentLegIndex - 1);
            if ((parentLeg === null || parentLeg === void 0 ? void 0 : parentLeg.leg.fixIcao) !== holdLeg.fixIcao) {
                return undefined;
            }
            const verticalData = leg.verticalData;
            plan.removeLeg(segmentIndex, segmentLegIndex);
            const insertedHoldLeg = plan.addLeg(segmentIndex, holdLeg, segmentLegIndex);
            plan.setLegVerticalData(segmentIndex, segmentLegIndex, verticalData);
            return insertedHoldLeg;
        }
        /**
         * Activates the nearest and most applicable leg of the primary flight plan.
         * @param allowMissedApproach Whether to allow activation of missed approach legs. Defaults to `false`.
         * @returns Whether a leg was successfully activated.
         */
        activateNearestLeg(allowMissedApproach = false) {
            const plan = this.hasPrimaryFlightPlan() && this.getPrimaryFlightPlan();
            if (!plan) {
                return false;
            }
            this.cancelObs(this.getDirectToState() !== exports.DirectToState.TORANDOM);
            let index = 0;
            let lastAllowableLegIndex = -1;
            let hasReachedMapr = false;
            //Filter to legs that we are potentially on
            const validLegs = [];
            for (const leg of plan.legs()) {
                const isDto = msfsSdk.BitFlags.isAll(leg.flags, msfsSdk.LegDefinitionFlags.DirectTo);
                const isMapr = msfsSdk.BitFlags.isAll(leg.flags, msfsSdk.LegDefinitionFlags.MissedApproach);
                const isDisco = msfsSdk.FlightPlanUtils.isDiscontinuityLeg(leg.leg.type);
                hasReachedMapr || (hasReachedMapr = isMapr);
                const isLegAllowed = !isDto && !isDisco && (allowMissedApproach || !hasReachedMapr);
                if (isLegAllowed) {
                    lastAllowableLegIndex = index;
                    const calcs = leg.calculated;
                    if (calcs !== undefined) {
                        const position = this.getLegReferencePosition(leg);
                        if (position !== undefined && position >= 0 && position <= 1) {
                            validLegs.push(leg);
                        }
                    }
                }
                index++;
            }
            //Try to activate the second or last leg if we're beyond the end or the beginning of the plan
            if (validLegs.length === 0 && plan.length > 1) {
                let secondLegGlobalIndex = 1;
                // If the first leg is the target of an on-route direct-to, then the second leg's index must be offset to
                // account for this.
                const firstLegSegmentIndex = plan.getSegmentIndex(0);
                if (plan.directToData.segmentIndex === firstLegSegmentIndex && plan.directToData.segmentLegIndex === 0) {
                    secondLegGlobalIndex += FmsUtils.DTO_LEG_OFFSET;
                }
                if (lastAllowableLegIndex >= secondLegGlobalIndex) {
                    const secondLeg = plan.getLeg(secondLegGlobalIndex);
                    const secondLegPosition = this.getLegReferencePosition(secondLeg);
                    if (secondLegPosition !== undefined && secondLegPosition > 1) {
                        const segmentIndex = plan.getSegmentIndex(lastAllowableLegIndex);
                        const segment = plan.getSegment(segmentIndex);
                        if (segment !== null) {
                            this._activateLeg(Fms.PRIMARY_PLAN_INDEX, segment.segmentIndex, lastAllowableLegIndex - segment.offset, false);
                            return true;
                        }
                    }
                    else if (secondLegPosition !== undefined && secondLegPosition <= 1) {
                        const segment = plan.getSegmentFromLeg(secondLeg);
                        if (segment !== null) {
                            this._activateLeg(Fms.PRIMARY_PLAN_INDEX, segment.segmentIndex, secondLegGlobalIndex - segment.offset, false);
                            return true;
                        }
                    }
                }
            }
            let closestLeg = undefined;
            let closestXtk = undefined;
            for (const leg of validLegs) {
                const calcs = leg.calculated;
                if (calcs !== undefined) {
                    const xtk = this.getClosestLegXtk(leg);
                    if (xtk !== undefined) {
                        if (closestXtk === undefined || xtk < closestXtk) {
                            closestLeg = leg;
                            closestXtk = xtk;
                        }
                    }
                }
            }
            if (closestLeg !== undefined) {
                const segment = plan.getSegmentFromLeg(closestLeg);
                if (segment !== null) {
                    this._activateLeg(Fms.PRIMARY_PLAN_INDEX, segment.segmentIndex, segment.legs.indexOf(closestLeg), false);
                    return true;
                }
            }
            return false;
        }
        /**
         * Gets the normalized leg reference position from the leg.
         * @param leg The leg to get the position for.
         * @returns The normalized reference position.
         */
        getLegReferencePosition(leg) {
            if (leg.calculated !== undefined && leg.calculated.flightPath.length > 0) {
                let numBefore = 0;
                let firstBeforePosition = 0;
                let numAfter = 0;
                let lastAfterPosition = 0;
                let latestInside = undefined;
                const flightPath = leg.calculated.flightPath;
                for (let i = 0; i < flightPath.length; i++) {
                    const vector = flightPath[i];
                    const circle = msfsSdk.FlightPathUtils.setGeoCircleFromVector(flightPath[i], Fms.geoCircleCache[0]);
                    const start = Fms.geoPointCache[0].set(vector.startLat, vector.startLon);
                    const end = Fms.geoPointCache[1].set(vector.endLat, vector.endLon);
                    const position = msfsSdk.FlightPathUtils.getAlongArcNormalizedDistance(circle, start, end, this.ppos);
                    if (position < 0) {
                        numBefore++;
                        if (i === 0) {
                            firstBeforePosition = position;
                        }
                    }
                    else if (position > 1) {
                        numAfter++;
                        if (i === flightPath.length - 1) {
                            lastAfterPosition = position;
                        }
                    }
                    else {
                        latestInside = position;
                    }
                }
                if (numBefore === flightPath.length) {
                    return firstBeforePosition;
                }
                else if (numAfter === flightPath.length) {
                    return lastAfterPosition;
                }
                else if (latestInside !== undefined) {
                    return latestInside;
                }
                else {
                    return undefined;
                }
            }
            return undefined;
        }
        /**
         * Gets the XTK of the closest vector on the leg.
         * @param leg The leg to get the XTK for.
         * @returns The closest leg vector XTK.
         */
        getClosestLegXtk(leg) {
            if (leg.calculated !== undefined) {
                return Math.min(...leg.calculated.flightPath.map(vector => {
                    const circle = msfsSdk.FlightPathUtils.setGeoCircleFromVector(vector, Fms.geoCircleCache[0]);
                    const start = Fms.geoPointCache[0].set(vector.startLat, vector.startLon);
                    const end = Fms.geoPointCache[1].set(vector.endLat, vector.endLon);
                    const position = msfsSdk.FlightPathUtils.getAlongArcNormalizedDistance(circle, start, end, this.ppos);
                    if (position >= 0 || position <= 1) {
                        return Math.abs(circle.distance(this.ppos));
                    }
                    else {
                        return Number.MAX_SAFE_INTEGER;
                    }
                }));
            }
            return undefined;
        }
        /**
         * Returns the index of the last element in the array where predicate is true, and -1
         * otherwise.
         * @param array The source array to search in
         * @param predicate find calls predicate once for each element of the array, in descending
         * order, until it finds one where predicate returns true. If such an element is found,
         * findLastIndex immediately returns that element index. Otherwise, findLastIndex returns -1.
         * @param defaultReturn is the default value
         * @returns either the index or the default if the predicate criteria is not met
         */
        findLastSegmentIndex(array, predicate, defaultReturn = -1) {
            let l = array.length;
            while (l--) {
                if (predicate(array[l], l, array)) {
                    return array[l].segmentIndex;
                }
            }
            return defaultReturn;
        }
        /**
         * Adds a leg to the flight plan.
         * @param segmentIndex The segment to add the leg to.
         * @param leg The leg to add to the plan.
         * @param index The index of the leg in the segment to insert. Will add to the end of the segment if ommitted.
         * @param flags Leg definition flags to apply to the new leg. Defaults to `None` (0).
         * @param notify Whether or not to send notifications after the operation.
         * @returns the new leg definition.
         */
        planAddLeg(segmentIndex, leg, index, flags = 0, notify = true) {
            const plan = this.getFlightPlan();
            const dtoLegIndex = plan.directToData.segmentLegIndex;
            const dtoSegmentIndex = plan.directToData.segmentIndex;
            if (dtoSegmentIndex >= 0
                && (segmentIndex < dtoSegmentIndex
                    || (segmentIndex === dtoSegmentIndex && index !== undefined && index <= dtoLegIndex))) {
                this.removeDirectToExisting();
            }
            const segment = plan.getSegment(segmentIndex);
            const addIndex = index !== undefined ? index : Math.max(segment.legs.length - 1, 0);
            if (segment.segmentType === msfsSdk.FlightPlanSegmentType.Approach
                && addIndex > 0
                && msfsSdk.BitFlags.isAll(segment.legs[addIndex - 1].flags, msfsSdk.LegDefinitionFlags.MissedApproach)) {
                flags |= msfsSdk.LegDefinitionFlags.MissedApproach;
            }
            const legDefinition = plan.addLeg(segmentIndex, leg, index, flags, notify);
            plan.calculate(plan.activeLateralLeg - 1);
            const activeSegmentIndex = plan.getSegmentIndex(plan.activeLateralLeg);
            if (activeSegmentIndex !== -1) {
                const activeLegIndex = plan.activeLateralLeg - plan.getSegment(activeSegmentIndex).offset;
                if (segmentIndex < activeSegmentIndex || (index && segmentIndex == activeSegmentIndex && index < activeLegIndex)) {
                    const newActiveLegIndex = plan.activeLateralLeg + 1;
                    plan.setCalculatingLeg(newActiveLegIndex);
                    plan.setLateralLeg(newActiveLegIndex);
                }
            }
            else {
                console.error('planAddLeg: activeSegmentIndex was -1');
            }
            return legDefinition;
        }
        /**
         * Removes a leg from the flight plan.
         * @param segmentIndex The segment to add the leg to.
         * @param segmentLegIndex The index of the leg in the segment to remove.
         * @param notify Whether or not to send notifications after the operation. True by default.
         * @param skipDupCheck Whether to skip checking for duplicates. False by default.
         * @param skipCancelDirectTo Whether to skip canceling a direct to existing if the removed leg is equal to or is
         * located before the direct to target. False by default.
         * @returns whether a leg was removed.
         */
        planRemoveLeg(segmentIndex, segmentLegIndex, notify = true, skipDupCheck = false, skipCancelDirectTo = false) {
            var _a, _b, _c, _d;
            const plan = this.getFlightPlan();
            if (segmentIndex < 0 || segmentIndex >= plan.segmentCount) {
                return false;
            }
            const segment = plan.getSegment(segmentIndex);
            const toRemoveLeg = segment.legs[segmentLegIndex];
            if (!toRemoveLeg) {
                return false;
            }
            const removeLegGlobalIndex = segment.offset + segmentLegIndex;
            const isDirectToExistingActive = this.getDirectToState() === exports.DirectToState.TOEXISTING;
            let removed = false;
            const airwayLegType = this.getAirwayLegType(plan, segmentIndex, segmentLegIndex);
            if (airwayLegType !== exports.AirwayLegType.NONE) {
                removed = this.removeLegAirwayHandler(plan, airwayLegType, segmentIndex, segmentLegIndex);
            }
            else {
                removed = plan.removeLeg(segmentIndex, segmentLegIndex, notify) !== null;
                if (!removed) {
                    return false;
                }
                const dtoLegIndex = plan.directToData.segmentLegIndex;
                const dtoSegmentIndex = plan.directToData.segmentIndex;
                if (!skipCancelDirectTo
                    && dtoSegmentIndex >= 0
                    && (segmentIndex < dtoSegmentIndex
                        || (segmentIndex === dtoSegmentIndex && segmentLegIndex <= dtoLegIndex))) {
                    // Need to adjust direct to data to compensate for removed leg.
                    if (segmentIndex === dtoSegmentIndex) {
                        plan.directToData.segmentLegIndex--;
                    }
                    if (isDirectToExistingActive && segmentIndex === dtoSegmentIndex && segmentLegIndex === dtoLegIndex) {
                        // Create a DTO random to replace the canceled DTO existing if we are directly removing the target leg of the DTO existing.
                        const directIcao = plan.getSegment(plan.directToData.segmentIndex).legs[plan.directToData.segmentLegIndex + FmsUtils.DTO_LEG_OFFSET].leg.fixIcao;
                        this.createDirectToRandom(directIcao);
                    }
                    this.removeDirectToExisting(plan.activeLateralLeg - 1);
                }
                else if (removeLegGlobalIndex < plan.activeLateralLeg || plan.activeLateralLeg >= plan.length) {
                    const newActiveLegIndex = plan.activeLateralLeg - 1;
                    plan.setCalculatingLeg(newActiveLegIndex);
                    plan.setLateralLeg(newActiveLegIndex);
                }
            }
            // Editing the plan prior to an existing vertical direct-to cancels the vertical direct-to
            if (removed && this.verticalPathCalculator) {
                const verticalPlan = this.verticalPathCalculator.getVerticalFlightPlan(Fms.PRIMARY_PLAN_INDEX);
                if (verticalPlan.verticalDirectIndex !== undefined && verticalPlan.verticalDirectIndex >= removeLegGlobalIndex) {
                    this.publishCancelVerticalDirectTo(FmsUtils.PRIMARY_PLAN_INDEX);
                }
            }
            const prevLeg = removeLegGlobalIndex - 1 >= 0 ? plan.getLeg(removeLegGlobalIndex - 1) : null;
            const nextLeg = removeLegGlobalIndex < plan.length ? plan.getLeg(removeLegGlobalIndex) : null;
            // If we are removing a leg before a VTF faf leg, we need to check whether the new leg prior to the faf leg
            // terminates at the same facility as the leg prior to the faf in the published procedure.
            if (segment.segmentType === msfsSdk.FlightPlanSegmentType.Approach && FmsUtils.isVtfApproachLoaded(plan)) {
                const vtfFafLeg = FmsUtils.getApproachVtfLeg(plan);
                if (vtfFafLeg !== undefined) {
                    // Note that by now we have already removed the leg, so all leg indexes after the removed leg have been shifted by -1.
                    const vtfFafSegmentLegIndex = segment.legs.indexOf(vtfFafLeg);
                    const discoLegExists = msfsSdk.BitFlags.isAll((_b = (_a = segment.legs[vtfFafSegmentLegIndex - 1]) === null || _a === void 0 ? void 0 : _a.flags) !== null && _b !== void 0 ? _b : 0, msfsSdk.LegDefinitionFlags.VectorsToFinal);
                    if (vtfFafLeg !== undefined && segmentLegIndex === vtfFafSegmentLegIndex - (discoLegExists ? 1 : 0)) {
                        const publishedLegIcao = (_c = plan.getUserData(Fms.VTF_FAF_DATA_KEY)) !== null && _c !== void 0 ? _c : '';
                        const legTerminatorIcao = prevLeg === null ? '' : (_d = msfsSdk.FlightPlanUtils.getTerminatorIcao(prevLeg.leg)) !== null && _d !== void 0 ? _d : '';
                        const needDisco = !msfsSdk.ICAO.isFacility(publishedLegIcao) || !msfsSdk.ICAO.isFacility(legTerminatorIcao) || publishedLegIcao !== legTerminatorIcao;
                        if (needDisco && !discoLegExists) {
                            plan.addLeg(segmentIndex, msfsSdk.FlightPlan.createLeg({ type: msfsSdk.LegType.ThruDiscontinuity }), vtfFafSegmentLegIndex, msfsSdk.LegDefinitionFlags.VectorsToFinal);
                            if (plan.activeLateralLeg >= segment.offset + vtfFafSegmentLegIndex) {
                                plan.setLateralLeg(plan.activeLateralLeg + 1);
                                plan.setCalculatingLeg(plan.activeCalculatingLeg + 1);
                            }
                        }
                        else if (!needDisco && discoLegExists) {
                            plan.removeLeg(segmentIndex, vtfFafSegmentLegIndex - 1);
                            if (plan.activeLateralLeg >= segment.offset + vtfFafSegmentLegIndex) {
                                plan.setLateralLeg(plan.activeLateralLeg - 1);
                                plan.setCalculatingLeg(plan.activeCalculatingLeg - 1);
                            }
                        }
                    }
                }
            }
            // Detect if we have created consecutive duplicate legs. If we have, we need to delete one of them.
            if (!skipDupCheck && prevLeg && nextLeg && this.isDuplicateLeg(prevLeg.leg, nextLeg.leg)) {
                this.planRemoveDuplicateLeg(prevLeg, nextLeg);
            }
            if (!skipDupCheck) {
                this.checkAndRemoveEmptySegment(plan, segmentIndex);
            }
            plan.calculate(plan.activeLateralLeg - 1);
            return true;
        }
        /**
         * Handles removing a leg that is either in an airway segment or is an entry for an airway segment.
         * @param plan The flight plan containing the leg to remove.
         * @param airwayLegType The type of the leg to remove with respect to its associated airway.
         * @param segmentIndex The index of the segment containing the leg to remove.
         * @param segmentLegIndex The index of the leg to remove in its segment.
         * @returns Whether this handler processed the remove request.
         */
        removeLegAirwayHandler(plan, airwayLegType, segmentIndex, segmentLegIndex) {
            var _a, _b;
            const removeLegGlobalIndex = plan.getSegment(segmentIndex).offset + segmentLegIndex;
            let removed = false;
            let needReconcileDto = plan.directToData.segmentIndex >= 0;
            if (segmentIndex < plan.directToData.segmentIndex
                || (segmentIndex === plan.directToData.segmentIndex && segmentLegIndex <= plan.directToData.segmentLegIndex)) {
                // If there are DTO legs after the leg we are removing, we need to remove them (canceling the active DTO existing if necessary)
                if (this.getDirectToState() === exports.DirectToState.TOEXISTING && segmentLegIndex === plan.directToData.segmentLegIndex) {
                    // Create a DTO random to replace the canceled DTO existing if we are directly removing the target leg of the DTO existing.
                    const directIcao = plan.getSegment(plan.directToData.segmentIndex).legs[plan.directToData.segmentLegIndex + FmsUtils.DTO_LEG_OFFSET].leg.fixIcao;
                    this.createDirectToRandom(directIcao);
                }
                this.removeDirectToExisting();
                needReconcileDto = false;
            }
            switch (airwayLegType) {
                case exports.AirwayLegType.ONROUTE: {
                    const segment = plan.getSegment(segmentIndex);
                    plan.removeLeg(segmentIndex, segmentLegIndex);
                    if (segmentLegIndex > 0) {
                        // Need to rename the airway segment with the new exit (if we removed the first leg after the entry, the
                        // airway segment will be deleted so no need to bother)
                        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                        plan.setAirway(segmentIndex, segment.airway.replace(/\..*/, `.${segment.legs[segmentLegIndex - 1].name}`));
                    }
                    // We need to move the leg immediately after the removed leg to the next non-airway enroute segment
                    // (if the next enroute segment does not exist we will create one)
                    if (plan.segmentCount <= segmentIndex + 1
                        || plan.getSegment(segmentIndex + 1).segmentType !== msfsSdk.FlightPlanSegmentType.Enroute
                        || plan.getSegment(segmentIndex + 1).airway !== undefined) {
                        plan.insertSegment(segmentIndex + 1, msfsSdk.FlightPlanSegmentType.Enroute);
                    }
                    const legAfterRemoved = segment.legs[segmentLegIndex].leg;
                    plan.addLeg(segmentIndex + 1, legAfterRemoved, 0);
                    plan.removeLeg(segmentIndex, segmentLegIndex);
                    if (segmentLegIndex < segment.legs.length) {
                        // There is at least one more leg in the original airway segment after the one we moved to the next enroute
                        // segment -> move these remaining legs into a new airway segment
                        const newEntrySegment = plan.getSegment(segmentIndex + 1);
                        let newAirwaySegmentIndex = segmentIndex + 2;
                        if (newEntrySegment.legs.length > 1) {
                            // need to split the segment containing the entry leg of the new airway segment
                            newAirwaySegmentIndex = this.splitSegmentForAirway(plan, segmentIndex + 1, 0);
                        }
                        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                        const airwayName = segment.airway.replace(/\..*/, `.${segment.legs[segment.legs.length - 1].name}`);
                        plan.insertSegment(newAirwaySegmentIndex, msfsSdk.FlightPlanSegmentType.Enroute, airwayName);
                        while (segment.legs.length > segmentLegIndex) {
                            const leg = segment.legs[segmentLegIndex].leg;
                            plan.removeLeg(segmentIndex, segmentLegIndex);
                            plan.addLeg(newAirwaySegmentIndex, leg);
                        }
                        // If the newly added airway segment is the last enroute segment, we need to insert an empty enroute segment
                        // after it to ensure that the last enroute segment in the plan is not an airway segment
                        if (newAirwaySegmentIndex >= plan.segmentCount - 1 || plan.getSegment(newAirwaySegmentIndex + 1).segmentType !== msfsSdk.FlightPlanSegmentType.Enroute) {
                            plan.insertSegment(newAirwaySegmentIndex + 1, msfsSdk.FlightPlanSegmentType.Enroute);
                        }
                    }
                    removed = true;
                    break;
                }
                case exports.AirwayLegType.ENTRY: {
                    if (plan.getSegment(segmentIndex).segmentType === msfsSdk.FlightPlanSegmentType.Enroute) {
                        // We need to remove the entry leg, then move the first leg in the airway segment out of the airway segment
                        // and into the previous enroute segment to serve as the new entry leg.
                        const airwaySegment = plan.getSegment(segmentIndex + 1);
                        const legToMove = airwaySegment.legs[0].leg;
                        plan.removeLeg(segmentIndex + 1, 0);
                        this.checkAndRemoveEmptySegment(plan, segmentIndex + 1);
                        this.planAddLeg(segmentIndex, legToMove, segmentLegIndex + 1);
                    }
                    else if (plan.getSegment(segmentIndex).segmentType === msfsSdk.FlightPlanSegmentType.Departure) {
                        // We need to remove the entry leg, then move the first leg in the airway segment out of the airway segment
                        // and into a newly created enroute segment placed before the airway segment to serve as the new entry leg.
                        this.planInsertSegmentOfType(msfsSdk.FlightPlanSegmentType.Enroute, segmentIndex + 1);
                        const airwaySegment = plan.getSegment(segmentIndex + 2);
                        const legToMove = airwaySegment.legs[0].leg;
                        plan.removeLeg(segmentIndex + 2, 0);
                        this.checkAndRemoveEmptySegment(plan, segmentIndex + 2);
                        this.planAddLeg(segmentIndex + 1, legToMove, 0);
                    }
                    removed = plan.removeLeg(segmentIndex, segmentLegIndex) !== null;
                    break;
                }
                case exports.AirwayLegType.EXIT: {
                    if (segmentLegIndex < 1) {
                        // We are removing the only leg in the airway segment, so just delete the segment.
                        this.removeAirway(segmentIndex);
                        return true;
                    }
                    else {
                        // Remove the leg, then change the name of the airway segment to reflect the new exit waypoint.
                        const segment = plan.getSegment(segmentIndex);
                        const airway = (_a = segment.airway) === null || _a === void 0 ? void 0 : _a.split('.');
                        segment.airway = airway && airway[0] ? airway[0] + '.' + segment.legs[segmentLegIndex - 1].name : segment.airway;
                        plan.setAirway(segmentIndex, segment.airway);
                        removed = plan.removeLeg(segmentIndex, segmentLegIndex) !== null;
                    }
                    break;
                }
                case exports.AirwayLegType.EXIT_ENTRY: {
                    // We need to move the first leg in the next airway segment out of that segment and into an enroute segment
                    // before the next airway segment.
                    const segment = plan.getSegment(segmentIndex + 1);
                    const leg = segment.legs[0].leg;
                    plan.removeLeg(segmentIndex + 1, 0);
                    if (segmentLegIndex < 1) {
                        // We are removing the only leg in the first airway segment, so just remove the segment.
                        plan.removeSegment(segmentIndex);
                        let prevSegmentIndex = segmentIndex - 1;
                        const prevSegment = plan.getSegment(prevSegmentIndex);
                        if (prevSegment.segmentType !== msfsSdk.FlightPlanSegmentType.Enroute || prevSegment.airway !== undefined) {
                            plan.insertSegment(segmentIndex, msfsSdk.FlightPlanSegmentType.Enroute);
                            prevSegmentIndex = segmentIndex;
                        }
                        plan.addLeg(prevSegmentIndex, leg);
                    }
                    else {
                        // Remove the leg from the first airway segment, then change the name of the airway segment to reflect the
                        // new exit waypoint.
                        plan.removeLeg(segmentIndex, segmentLegIndex);
                        plan.insertSegment(segmentIndex + 1, msfsSdk.FlightPlanSegmentType.Enroute);
                        plan.addLeg(segmentIndex + 1, leg);
                        const firstAirwaySegment = plan.getSegment(segmentIndex);
                        const airway = (_b = firstAirwaySegment.airway) === null || _b === void 0 ? void 0 : _b.split('.');
                        firstAirwaySegment.airway = airway && airway[0] ? airway[0] + '.' + firstAirwaySegment.legs[segmentLegIndex - 1].name : firstAirwaySegment.airway;
                        plan.setAirway(segmentIndex, firstAirwaySegment.airway);
                    }
                    removed = true;
                }
            }
            if (removed) {
                if (needReconcileDto) {
                    FmsUtils.reconcileDirectToData(plan);
                }
                if (removeLegGlobalIndex <= plan.activeLateralLeg || plan.activeLateralLeg >= plan.length) {
                    const newActiveLegIndex = plan.activeLateralLeg - 1;
                    plan.setCalculatingLeg(newActiveLegIndex);
                    plan.setLateralLeg(newActiveLegIndex);
                }
            }
            return removed;
        }
        /**
         * Checks if a flight plan segment is empty, and removes the segment if it is eligible to be removed. Only Enroute
         * segments that are followed by another Enroute segment are eligible to be removed if empty.
         * @param plan A flight plan.
         * @param segmentIndex The index of the segment to check.
         * @returns Whether the segment was removed.
         */
        checkAndRemoveEmptySegment(plan, segmentIndex) {
            if (this.checkIfRemoveLeftEmptySegmentToDelete(plan, segmentIndex)) {
                this.planRemoveSegment(segmentIndex);
                const prevSegmentIndex = segmentIndex - 1;
                const nextSegmentIndex = segmentIndex;
                const prevSegment = prevSegmentIndex >= 0 ? plan.getSegment(prevSegmentIndex) : undefined;
                const nextSegment = nextSegmentIndex < plan.segmentCount ? plan.getSegment(nextSegmentIndex) : undefined;
                if ((prevSegment === null || prevSegment === void 0 ? void 0 : prevSegment.segmentType) === msfsSdk.FlightPlanSegmentType.Enroute
                    && prevSegment.airway === undefined
                    && (nextSegment === null || nextSegment === void 0 ? void 0 : nextSegment.segmentType) === msfsSdk.FlightPlanSegmentType.Enroute
                    && nextSegment.airway === undefined) {
                    // We are left with two consecutive non-airway enroute segments -> merge the two
                    this.mergeSegments(plan, prevSegmentIndex);
                }
                return true;
            }
            else {
                return false;
            }
        }
        /**
         * Checks if a remove left an empty segment that also needs to be removed.
         * @param plan is the flight plan
         * @param segmentIndex The segment to add the leg to.
         * @returns whether to remove the segment.
         */
        checkIfRemoveLeftEmptySegmentToDelete(plan, segmentIndex) {
            const segment = plan.getSegment(segmentIndex);
            let nextSegment;
            if (segmentIndex < plan.segmentCount - 1) {
                nextSegment = plan.getSegment(segmentIndex + 1);
            }
            if (segment.legs.length < 1) {
                switch (segment.segmentType) {
                    case msfsSdk.FlightPlanSegmentType.Enroute:
                        if (nextSegment && nextSegment.segmentType === msfsSdk.FlightPlanSegmentType.Enroute) {
                            return true;
                        }
                        break;
                    //TODO: Add more cases as appropriate
                }
            }
            return false;
        }
        /**
         * Adds an appropriate origin or destination leg (either an airport or runway fix) to the primary flight plan. Origin
         * legs are added to the beginning of the specified segment. Destination legs are added to the end of the specified
         * segment.
         * @param isOrigin Whether to add an origin leg.
         * @param segmentIndex The index of the segment to which to add the leg.
         * @param airportIcao The ICAO of the leg's airport.
         * @param runway The leg's runway.
         */
        planAddOriginDestinationLeg(isOrigin, segmentIndex, airportIcao, runway) {
            let legToAdd;
            if (runway) {
                legToAdd = FmsUtils.buildRunwayLeg(airportIcao, runway, isOrigin);
            }
            else {
                legToAdd = msfsSdk.FlightPlan.createLeg({
                    type: isOrigin ? msfsSdk.LegType.IF : msfsSdk.LegType.TF,
                    fixIcao: airportIcao
                });
            }
            this.planAddLeg(segmentIndex, legToAdd, isOrigin ? 0 : undefined);
            if (!isOrigin) {
                const plan = this.getFlightPlan();
                const lastEnrouteSegment = FmsUtils.getLastEnrouteSegment(plan);
                if (lastEnrouteSegment) {
                    for (let segmentLegIndex = lastEnrouteSegment.legs.length - 1; segmentLegIndex >= 0; segmentLegIndex--) {
                        const leg = lastEnrouteSegment.legs[segmentLegIndex];
                        if (msfsSdk.BitFlags.isAny(leg.flags, msfsSdk.LegDefinitionFlags.DirectTo)) {
                            continue;
                        }
                        if (leg.leg.fixIcao === airportIcao) {
                            this.planRemoveLeg(lastEnrouteSegment.segmentIndex, segmentLegIndex, true, true);
                        }
                        break;
                    }
                }
            }
        }
        /**
         * Method to add a segment to the flightplan.
         * @param segmentType is the FlightPlanSegmentType.
         * @param index is the optional segment index to insert the segment.
         * @returns the segment index of the inserted segment.
         */
        planInsertSegmentOfType(segmentType, index) {
            const plan = this.getFlightPlan();
            let segmentIndex = -1;
            if (index) {
                segmentIndex = index - 1;
            }
            else {
                const segments = [];
                for (const segment of plan.segments()) {
                    segments.push(segment);
                }
                switch (segmentType) {
                    case msfsSdk.FlightPlanSegmentType.Origin:
                        break;
                    case msfsSdk.FlightPlanSegmentType.Departure:
                        segmentIndex = 0;
                        break;
                    case msfsSdk.FlightPlanSegmentType.Arrival:
                        segmentIndex = this.findLastSegmentIndex(segments, (v) => {
                            return v.segmentType === msfsSdk.FlightPlanSegmentType.Enroute;
                        }, 2);
                        break;
                    case msfsSdk.FlightPlanSegmentType.Approach:
                        segmentIndex = this.findLastSegmentIndex(segments, (v) => {
                            return v.segmentType === msfsSdk.FlightPlanSegmentType.Enroute || v.segmentType === msfsSdk.FlightPlanSegmentType.Arrival;
                        }, 2);
                        break;
                    case msfsSdk.FlightPlanSegmentType.MissedApproach:
                        segmentIndex = this.findLastSegmentIndex(segments, (v) => {
                            return v.segmentType === msfsSdk.FlightPlanSegmentType.Approach;
                        }, 2);
                        break;
                    case msfsSdk.FlightPlanSegmentType.Destination:
                        segmentIndex = this.findLastSegmentIndex(segments, (v) => {
                            return v.segmentType === msfsSdk.FlightPlanSegmentType.Enroute || v.segmentType === msfsSdk.FlightPlanSegmentType.Arrival
                                || v.segmentType === msfsSdk.FlightPlanSegmentType.Approach;
                        }, 5);
                        break;
                    default:
                        segmentIndex = this.findLastSegmentIndex(segments, (v) => {
                            return v.segmentType === msfsSdk.FlightPlanSegmentType.Enroute || v.segmentType === msfsSdk.FlightPlanSegmentType.Arrival
                                || v.segmentType === msfsSdk.FlightPlanSegmentType.Approach || v.segmentType === msfsSdk.FlightPlanSegmentType.Destination;
                        }, 1);
                        segmentIndex--;
                        break;
                }
            }
            return this.planInsertSegment(segmentIndex + 1, segmentType).segmentIndex;
        }
        /**
         * Method to remove all legs from a segment.
         * @param segmentIndex is the index of the segment to delete all legs from.
         * @param segmentType is the type if segment to delete all legs from, if known.
         */
        planClearSegment(segmentIndex, segmentType) {
            this.planRemoveSegment(segmentIndex);
            this.planInsertSegment(segmentIndex, segmentType);
        }
        /**
         * Inserts a segment into the flight plan at the specified index and
         * reflows the subsequent segments.
         * @param segmentIndex The index to insert the flight plan segment.
         * @param segmentType The type of segment this will be.
         * @param airway The airway this segment is made up of, if any
         * @param notify Whether or not to send notifications after the operation.
         * @returns The new flight plan segment.
         */
        planInsertSegment(segmentIndex, segmentType = msfsSdk.FlightPlanSegmentType.Enroute, airway, notify = true) {
            const plan = this.getFlightPlan();
            // Editing the plan prior to an existing vertical direct-to cancels the vertical direct-to
            if (this.verticalPathCalculator) {
                const verticalPlan = this.verticalPathCalculator.getVerticalFlightPlan(Fms.PRIMARY_PLAN_INDEX);
                const currentSegment = plan.tryGetSegment(segmentIndex);
                if (verticalPlan.verticalDirectIndex !== undefined && currentSegment !== null && verticalPlan.verticalDirectIndex >= currentSegment.offset) {
                    this.publishCancelVerticalDirectTo(FmsUtils.PRIMARY_PLAN_INDEX);
                }
            }
            const segment = plan.insertSegment(segmentIndex, segmentType, airway, notify);
            plan.calculate(plan.activeLateralLeg - 1);
            if (plan.directToData.segmentIndex >= 0 && segmentIndex <= plan.directToData.segmentIndex) {
                plan.setDirectToData(plan.directToData.segmentIndex + 1, plan.directToData.segmentLegIndex);
            }
            return segment;
        }
        /**
         * Removes a segment from the flight plan and reflows the segments following
         * the removed segment, not leaving an empty segment at the specified index.
         * @param segmentIndex The index of the segment to remove.
         * @param notify Whether or not to send notifications after the operation.
         */
        planRemoveSegment(segmentIndex, notify = true) {
            const plan = this.getFlightPlan();
            const segment = plan.getSegment(segmentIndex);
            const activeSegmentIndex = plan.getSegmentIndex(plan.activeLateralLeg);
            // Editing the plan prior to an existing vertical direct-to cancels the vertical direct-to
            if (this.verticalPathCalculator) {
                const verticalPlan = this.verticalPathCalculator.getVerticalFlightPlan(Fms.PRIMARY_PLAN_INDEX);
                if (verticalPlan.verticalDirectIndex !== undefined && verticalPlan.verticalDirectIndex >= segment.offset) {
                    this.publishCancelVerticalDirectTo(FmsUtils.PRIMARY_PLAN_INDEX);
                }
            }
            if (plan.directToData.segmentIndex >= 0) {
                if (segmentIndex < plan.directToData.segmentIndex) {
                    plan.setDirectToData(plan.directToData.segmentIndex - 1, plan.directToData.segmentLegIndex);
                }
                else if (segmentIndex === plan.directToData.segmentIndex) {
                    plan.setDirectToData(-1);
                }
            }
            if (activeSegmentIndex === segmentIndex && !Simplane.getIsGrounded() && plan.length > 1) {
                const directIcao = plan.getLeg(plan.activeLateralLeg).leg.fixIcao;
                if (msfsSdk.ICAO.isFacility(directIcao) && this.getDirectToState() !== exports.DirectToState.TORANDOM) {
                    this.createDirectToRandom(directIcao);
                }
            }
            const newActiveLegIndex = plan.activeLateralLeg - Utils.Clamp(plan.activeLateralLeg - segment.offset, 0, segment.legs.length);
            plan.setCalculatingLeg(newActiveLegIndex);
            plan.setLateralLeg(newActiveLegIndex);
            plan.removeSegment(segmentIndex, notify);
            plan.calculate(plan.activeLateralLeg - 1);
        }
        /**
         * Checks whether of two consecutive flight plan legs, the second is a duplicate of the first. The second leg is
         * considered a duplicate if and only if it is an IF, TF, or DF leg with the same terminator fix as the first leg,
         * which is also an IF, TF, or DF leg.
         * @param leg1 The first leg.
         * @param leg2 The second leg.
         * @returns whether the second leg is a duplicate of the first.
         */
        isDuplicateLeg(leg1, leg2) {
            if (leg2.type !== msfsSdk.LegType.IF
                && leg2.type !== msfsSdk.LegType.DF
                && leg2.type !== msfsSdk.LegType.TF
                && leg2.type !== msfsSdk.LegType.CF) {
                return false;
            }
            const isLeg1TypeValidForDuplicate = leg1.type === msfsSdk.LegType.IF
                || leg1.type === msfsSdk.LegType.TF
                || leg1.type === msfsSdk.LegType.DF
                || leg1.type === msfsSdk.LegType.CF;
            if (!isLeg1TypeValidForDuplicate) {
                return false;
            }
            if (leg1.fixIcao === leg2.fixIcao) {
                return true;
            }
            const leg1Airport = msfsSdk.ICAO.getAssociatedAirportIdent(leg1.fixIcao);
            const leg2Airport = msfsSdk.ICAO.getAssociatedAirportIdent(leg2.fixIcao);
            if (leg1Airport.length > 0 && leg2Airport.length > 0 && leg1Airport !== leg2Airport) {
                return false;
            }
            if (leg1Airport.length > 0 && leg2Airport.length === 0 || leg2Airport.length > 0 && leg1Airport.length === 0) {
                if (msfsSdk.ICAO.getRegionCode(leg1.fixIcao) === msfsSdk.ICAO.getRegionCode(leg2.fixIcao)) {
                    return msfsSdk.ICAO.getIdent(leg1.fixIcao) === msfsSdk.ICAO.getIdent(leg2.fixIcao);
                }
            }
            return false;
        }
        /**
         * Checks whether of two consecutive flight plan legs, the second is an IF leg and is a duplicate of the first. The
         * IF leg is considered a duplicate if and only if its fix is the same as the fix at which the first leg terminates.
         * @param leg1 The first leg.
         * @param leg2 The second leg.
         * @returns whether the second leg is an duplicate IF leg of the first.
         */
        isDuplicateIFLeg(leg1, leg2) {
            if (leg2.type !== msfsSdk.LegType.IF) {
                return false;
            }
            if (leg1.type !== msfsSdk.LegType.TF
                && leg1.type !== msfsSdk.LegType.DF
                && leg1.type !== msfsSdk.LegType.RF
                && leg1.type !== msfsSdk.LegType.CF
                && leg1.type !== msfsSdk.LegType.AF
                && leg1.type !== msfsSdk.LegType.IF) {
                return false;
            }
            return leg1.fixIcao === leg2.fixIcao;
        }
        /**
         * Merges two duplicate legs such that the new merged leg contains the fix type and altitude data from the source leg
         * and all other data is derived from the target leg.
         * @param target The target leg.
         * @param source The source leg.
         * @returns the merged leg.
         */
        mergeDuplicateLegData(target, source) {
            const merged = msfsSdk.FlightPlan.createLeg(target);
            merged.fixTypeFlags |= source.fixTypeFlags;
            merged.altDesc = source.altDesc;
            merged.altitude1 = source.altitude1;
            merged.altitude2 = source.altitude2;
            return merged;
        }
        /**
         * Deletes one of two consecutive duplicate legs. If one leg is in a procedure and the other is not, the leg that is
         * not in a procedure will be deleted. If the legs are in different procedures, the earlier leg will be deleted.
         * Otherwise, the later leg will be deleted. If the deleted leg is the target leg of a direct to, the legs in the
         * direct to sequence will be copied and moved to immediately follow the duplicate leg that was not deleted.
         * @param leg1 The first duplicate leg.
         * @param leg2 The second duplicate leg.
         * @returns the leg that was deleted, or null if neither leg was deleted.
         * @throws Error if direct to legs could not be updated.
         */
        planRemoveDuplicateLeg(leg1, leg2) {
            const plan = this.getFlightPlan();
            const leg1Segment = plan.getSegmentFromLeg(leg1);
            const leg1Index = plan.getLegIndexFromLeg(leg1);
            const leg2Segment = plan.getSegmentFromLeg(leg2);
            const leg2Index = plan.getLegIndexFromLeg(leg2);
            const prevLegIndex = leg1Index - 1;
            const prevLeg = plan.tryGetLeg(prevLegIndex);
            if (!leg1Segment || !leg2Segment) {
                return null;
            }
            const isLeg1DirectToLeg = msfsSdk.BitFlags.isAll(leg1.flags, msfsSdk.LegDefinitionFlags.DirectTo);
            const isLeg2DirectToLeg = msfsSdk.BitFlags.isAll(leg2.flags, msfsSdk.LegDefinitionFlags.DirectTo);
            const dupDirectToLeg = isLeg1DirectToLeg ? leg1
                : isLeg2DirectToLeg ? leg2
                    : null;
            if (dupDirectToLeg) {
                if (dupDirectToLeg.leg.type === msfsSdk.LegType.IF) {
                    // Technically this should never happen.
                    return null;
                }
                else {
                    // If one of the duplicates is the second leg in a direct to sequence, then the true duplicated leg is the
                    // target leg of the DTO. In this case, we call this method with the DTO target leg replacing the DTO leg.
                    const dtoTargetLeg = plan.getSegment(plan.directToData.segmentIndex).legs[plan.directToData.segmentLegIndex];
                    return isLeg1DirectToLeg ? this.planRemoveDuplicateLeg(dtoTargetLeg, leg2) : this.planRemoveDuplicateLeg(leg1, dtoTargetLeg);
                }
            }
            const isLeg1InProc = leg1Segment.segmentType !== msfsSdk.FlightPlanSegmentType.Enroute;
            const isLeg2InProc = leg2Segment.segmentType !== msfsSdk.FlightPlanSegmentType.Enroute;
            const prevLegIsIntercept = prevLeg !== null && (prevLeg.leg.type === msfsSdk.LegType.CI || prevLeg.leg.type === msfsSdk.LegType.VI);
            let toDeleteSegment;
            let toDeleteIndex;
            let toDeleteLeg;
            if (prevLegIsIntercept
                || (!isLeg1InProc && isLeg2InProc)
                || (isLeg1InProc && isLeg2InProc && leg1Segment !== leg2Segment)
                || msfsSdk.BitFlags.isAny(leg2.leg.fixTypeFlags, msfsSdk.FixTypeFlags.FAF | msfsSdk.FixTypeFlags.MAP)) {
                toDeleteSegment = leg1Segment;
                toDeleteIndex = leg1Index - leg1Segment.offset;
                toDeleteLeg = leg1;
            }
            else {
                toDeleteSegment = leg2Segment;
                toDeleteIndex = leg2Index - leg2Segment.offset;
                //Merge data into first leg and replace
                const mergedLeg = this.mergeDuplicateLegData(leg1.leg, leg2.leg);
                const leg1SegmentLegIndex = leg1Index - leg1Segment.offset;
                plan.removeLeg(leg1Segment.segmentIndex, leg1SegmentLegIndex);
                plan.addLeg(leg1Segment.segmentIndex, mergedLeg, leg1SegmentLegIndex, leg2.flags);
                toDeleteLeg = leg2;
            }
            if (toDeleteIndex >= 0) {
                const dtoTargetLeg = plan.directToData.segmentIndex < 0 ? null : plan.getSegment(plan.directToData.segmentIndex).legs[plan.directToData.segmentLegIndex];
                const needMoveDtoLegs = toDeleteLeg === dtoTargetLeg;
                if (needMoveDtoLegs) {
                    const isDtoExistingActive = this.getDirectToState() === exports.DirectToState.TOEXISTING;
                    // If the removed leg was the target leg of a DTO existing, we need to shift the DTO legs to target the leg
                    // that was not removed.
                    const oldDiscoLeg = plan.removeLeg(plan.directToData.segmentIndex, plan.directToData.segmentLegIndex + 1);
                    const oldDtoLeg1 = plan.removeLeg(plan.directToData.segmentIndex, plan.directToData.segmentLegIndex + 1);
                    const oldDtoLeg2 = plan.removeLeg(plan.directToData.segmentIndex, plan.directToData.segmentLegIndex + 1);
                    if (!oldDtoLeg1 || !oldDtoLeg2 || !oldDiscoLeg) {
                        throw new Error(`Fms: Could not remove direct to legs starting at segment index ${plan.directToData.segmentIndex}, leg index ${plan.directToData.segmentLegIndex} during duplicate leg resolution.`);
                    }
                    const preservedLeg = toDeleteLeg === leg1 ? leg2 : leg1;
                    const preservedLegIndex = plan.getLegIndexFromLeg(preservedLeg);
                    const newTargetSegmentIndex = plan.getSegmentIndex(preservedLegIndex);
                    const newTargetSegmentLegIndex = preservedLegIndex - plan.getSegment(newTargetSegmentIndex).offset;
                    plan.setDirectToData(newTargetSegmentIndex, newTargetSegmentLegIndex);
                    plan.addLeg(newTargetSegmentIndex, msfsSdk.FlightPlan.createLeg(oldDiscoLeg.leg), newTargetSegmentLegIndex + 1, msfsSdk.LegDefinitionFlags.DirectTo);
                    plan.addLeg(newTargetSegmentIndex, msfsSdk.FlightPlan.createLeg(oldDtoLeg1.leg), newTargetSegmentLegIndex + 2, msfsSdk.LegDefinitionFlags.DirectTo);
                    plan.addLeg(newTargetSegmentIndex, msfsSdk.FlightPlan.createLeg(oldDtoLeg2.leg), newTargetSegmentLegIndex + 3, msfsSdk.LegDefinitionFlags.DirectTo);
                    if (isDtoExistingActive) {
                        const newActiveLegIndex = preservedLegIndex + FmsUtils.DTO_LEG_OFFSET;
                        plan.setCalculatingLeg(newActiveLegIndex);
                        plan.setLateralLeg(newActiveLegIndex);
                    }
                }
                const success = this.planRemoveLeg(toDeleteSegment.segmentIndex, toDeleteIndex, true, false, needMoveDtoLegs);
                if (success) {
                    return toDeleteLeg;
                }
            }
            return null;
        }
        /**
         * Cancels OBS and optionally converts the OBS course to a Direct-To.
         * @param convertToDto Whether to convert the OBS course to a Direct-To.
         * @returns The net number of legs inserted into the active flight plan as a result of converting the OBS course to
         * a Direct-To.
         */
        cancelObs(convertToDto) {
            if (!this.isObsActive.get()) {
                return 0;
            }
            SimVar.SetSimVarValue(this.needConvertObsToDtoSimVar, msfsSdk.SimVarValueType.Bool, false);
            if (this.useSimObsState) {
                SimVar.SetSimVarValue('K:GPS_OBS_OFF', msfsSdk.SimVarValueType.Number, 0);
            }
            else {
                this.publisher.pub(this.obsControlTopicMap['lnav_obs_set_active'], false, true, false);
            }
            if (convertToDto) {
                return this.convertObsToDirectTo();
            }
            else {
                return 0;
            }
        }
        /**
         * Converts an OBS course to a Direct-To. The OBS's target leg is assumed to be the currently active flight plan leg.
         * @returns The net number of legs inserted into the active flight plan as a result of converting the OBS course to
         * a Direct-To.
         */
        convertObsToDirectTo() {
            const obsCourse = this.obsCourse.get();
            const dtoState = this.getDirectToState();
            if (dtoState === exports.DirectToState.TORANDOM) {
                // Just replace the DTO random with one with a custom course
                const dtoTargetIcao = this.getDirectToTargetIcao();
                this.createDirectToRandom(dtoTargetIcao, obsCourse);
                return 0;
            }
            else if (dtoState === exports.DirectToState.TOEXISTING) {
                const dtoData = this.getPrimaryFlightPlan().directToData;
                this.createDirectToExisting(dtoData.segmentIndex, dtoData.segmentLegIndex, obsCourse);
                return 0;
            }
            else {
                const plan = this.getPrimaryFlightPlan();
                const segmentIndex = plan.getSegmentIndex(plan.activeLateralLeg);
                const segmentLegIndex = plan.getSegmentLegIndex(plan.activeLateralLeg);
                if (segmentIndex >= 0 && segmentLegIndex >= 0) {
                    const didDtoExist = plan.directToData.segmentIndex >= 0 && plan.directToData.segmentLegIndex >= 0;
                    this.createDirectToExisting(segmentIndex, segmentLegIndex, obsCourse);
                    return didDtoExist ? 0 : FmsUtils.DTO_LEG_OFFSET;
                }
                else {
                    return 0;
                }
            }
        }
        /**
         * Loads an approach frequency into a NAV radio.
         * @param radioIndex The index of the NAV radio into which to load the frequency.
         */
        setLocFrequency(radioIndex) {
            const approachReferenceFac = this.approachDetails.get().referenceFacility;
            if (!approachReferenceFac || Math.abs(this.navActiveFreqs[radioIndex].get() - approachReferenceFac.freqMHz) < 0.001) {
                return;
            }
            const setActive = this.cdiSource.type === msfsSdk.NavSourceType.Gps || this.cdiSource.index !== radioIndex;
            SimVar.SetSimVarValue(`K:NAV${radioIndex}_STBY_SET_HZ`, 'Hz', approachReferenceFac.freqMHz * 1000000);
            if (setActive) {
                SimVar.SetSimVarValue(`K:NAV${radioIndex}_RADIO_SWAP`, 'Bool', 1);
            }
        }
        /**
         * Sets the approach details for the loaded approach and sends an event across the bus.
         * @param sync Whether to sync the details to other instruments.
         * @param isLoaded Whether an approach is loaded.
         * @param type The approach type.
         * @param bestRnavType The best available approach RNAV type.
         * @param rnavTypeFlags The RNAV minimum type flags for the approach.
         * @param isCircling Whether the approach is a circling approach.
         * @param isVtf Whether the approach is a vectors-to-final approach.
         * @param referenceFacility The approach's reference facility.
         * @param runway The assigned runway for the approach
         */
        setApproachDetails(sync, isLoaded, type, bestRnavType, rnavTypeFlags, isCircling, isVtf, referenceFacility, runway) {
            isLoaded !== undefined && this.approachDetails.set('isLoaded', isLoaded);
            type !== undefined && this.approachDetails.set('type', type);
            bestRnavType !== undefined && this.approachDetails.set('bestRnavType', bestRnavType);
            rnavTypeFlags !== undefined && this.approachDetails.set('rnavTypeFlags', rnavTypeFlags);
            isCircling !== undefined && this.approachDetails.set('isCircling', isCircling);
            isVtf !== undefined && this.approachDetails.set('isVtf', isVtf);
            referenceFacility !== undefined && this.approachDetails.set('referenceFacility', referenceFacility);
            runway !== undefined && this.approachDetails.set('runway', runway);
            const approachDetails = this.approachDetails.get();
            // If an approach is flagged as RNAV but has no defined RNAV minima, assume it is an RNP (AR) approach if it is not circling.
            this.approachDetails.set('isRnpAr', approachDetails.type === ApproachType.APPROACH_TYPE_RNAV && approachDetails.bestRnavType === 0 && !approachDetails.isCircling);
            if (this.needPublishApproachDetails) {
                this.needPublishApproachDetails = false;
                this.publisher.pub(this.fmsTopicMap['fms_approach_details'], Object.assign({}, approachDetails), false, true);
                this.publisher.pub(this.fmsTopicMap['fms_approach_details_sync'], approachDetails, sync, false);
            }
        }
        /**
         * Checks whether the approach details indicate that vertical guidance (GP) can be supported.
         * @returns whether or not vertical guidance is supported.
         */
        doesApproachSupportGp() {
            const approachDetails = this.approachDetails.get();
            if (approachDetails.isLoaded && !approachDetails.isCircling && this.flightPhase.get().isApproachActive) {
                switch (approachDetails.type) {
                    case ApproachType.APPROACH_TYPE_GPS:
                    case ApproachType.APPROACH_TYPE_RNAV:
                    case msfsSdk.AdditionalApproachType.APPROACH_TYPE_VISUAL:
                        return true;
                }
            }
            return false;
        }
        /**
         * Sets the approach details when an approach_details_set event is received from the bus.
         * @param approachDetails The approachDetails received from the bus.
         */
        onApproachDetailsSet(approachDetails) {
            // If the event came from this Fms, then the objects will be equal by reference, so we only bother setting the
            // object if they are different.
            if (approachDetails !== this.approachDetails.get()) {
                this.approachDetails.set(approachDetails);
                if (this.needPublishApproachDetails) {
                    this.needPublishApproachDetails = false;
                    this.publisher.pub(this.fmsTopicMap['fms_approach_details'], Object.assign({}, this.approachDetails.get()), false, true);
                }
            }
        }
    }
    /** The index of the primary flight plan. */
    Fms.PRIMARY_PLAN_INDEX = FmsUtils.PRIMARY_PLAN_INDEX;
    /** The index of the off-route direct-to flight plan. */
    Fms.DTO_RANDOM_PLAN_INDEX = FmsUtils.DTO_RANDOM_PLAN_INDEX;
    /** The index of the procedure preview flight plan. */
    Fms.PROC_PREVIEW_PLAN_INDEX = FmsUtils.PROC_PREVIEW_PLAN_INDEX;
    /** Amount to offset runway leg altitude constraints from runway elevation, in meters. */
    Fms.RUNWAY_LEG_ALTITUDE_OFFSET = 15;
    Fms.DEFAULT_VISUAL_APPROACH_OPTIONS = {
        finalFixDistance: 2.5,
        strghtFixDistance: 2.5
    };
    Fms.VTF_FAF_DATA_KEY = 'vtf_faf_data';
    Fms.geoPointCache = [new msfsSdk.GeoPoint(0, 0), new msfsSdk.GeoPoint(0, 0)];
    Fms.geoCircleCache = [new msfsSdk.GeoCircle(new Float64Array(3), 0)];

    /**
     * A utility class for working with Garmin autopilots.
     */
    class GarminAPUtils {
        /**
         * Calculates intercept angles for radio nav.
         * @param distanceToSource The distance from the plane to the source of the navigation signal, in nautical miles.
         * @param deflection The lateral deflection of the desired track relative to the plane, normalized from `-1` to `1`.
         * Positive values indicate that the desired track is to the right of the plane.
         * @param xtk The cross-track error of the plane from the desired track, in nautical miles. Positive values indicate
         * indicate that the plane is to the right of the track.
         * @param tas The true airspeed of the plane, in knots.
         * @param isLoc Whether the source of the navigation signal is a localizer. Defaults to `false`.
         * @returns The intercept angle, in degrees, to capture the desired track from the navigation signal.
         */
        static navIntercept(distanceToSource, deflection, xtk, tas, isLoc) {
            if (isLoc) {
                return GarminAPUtils.localizerIntercept(xtk, tas);
            }
            else {
                return GarminAPUtils.defaultIntercept(xtk, tas);
            }
        }
        /**
         * Calculates intercept angles for LNAV.
         * @param dtk The desired track, in degrees true.
         * @param xtk The cross-track error, in nautical miles. Negative values indicate that the plane is to the left of the
         * desired track.
         * @param tas The true airspeed of the plane, in knots.
         * @returns The intercept angle, in degrees, to capture the desired track from the navigation signal.
         */
        static lnavIntercept(dtk, xtk, tas) {
            return GarminAPUtils.defaultIntercept(xtk, tas);
        }
        /**
         * Calculates intercept angles for localizers.
         * @param xtk The cross-track error of the plane from the desired track, in nautical miles. Positive values indicate
         * indicate that the plane is to the right of the track.
         * @param tas The true airspeed of the plane, in knots.
         * @returns The intercept angle, in degrees, to capture the localizer course.
         */
        static localizerIntercept(xtk, tas) {
            const xtkMeters = msfsSdk.UnitType.NMILE.convertTo(xtk, msfsSdk.UnitType.METER);
            const xtkMetersAbs = Math.abs(xtkMeters);
            if (xtkMetersAbs < 4) {
                return 0;
            }
            else if (xtkMetersAbs < 250) {
                return msfsSdk.MathUtils.clamp(Math.abs(xtk * 75), 1, 5);
            }
            const turnRadiusMeters = msfsSdk.NavMath.turnRadius(tas, 22.5);
            const interceptAngle = GarminAPUtils.calculateTurnBasedInterceptAngle(turnRadiusMeters, xtkMeters);
            return msfsSdk.MathUtils.clamp(interceptAngle, 0, 20);
        }
        /**
         * Calculates non-localizer intercept angles.
         * @param xtk The cross-track error, in nautical miles. Negative values indicate that the plane is to the left of the
         * desired track.
         * @param tas The true airspeed of the plane, in knots.
         * @returns The intercept angle, in degrees, to capture the desired track.
         */
        static defaultIntercept(xtk, tas) {
            const xtkMeters = msfsSdk.UnitType.NMILE.convertTo(xtk, msfsSdk.UnitType.METER);
            const xtkMetersAbs = Math.abs(xtkMeters);
            if (xtkMetersAbs < 250) {
                return msfsSdk.MathUtils.clamp(Math.abs(xtk * 75), 0, 5);
            }
            const turnRadiusMeters = msfsSdk.NavMath.turnRadius(tas, 22.5);
            const interceptAngle = GarminAPUtils.calculateTurnBasedInterceptAngle(turnRadiusMeters, xtkMeters);
            return msfsSdk.MathUtils.clamp(interceptAngle, 0, 45);
        }
        /**
         * Calculates an intercept angle to a track such that the intercept course, projected forward from the plane's
         * position, intercepts the desired track at the same point as a constant-radius turn overlapping the plane's
         * position configured to be tangent to the desired track. This has the effect of producing an intercept angle which
         * guarantees a no-overshoot intercept for all initial ground tracks for which a no-overshoot intercept is possible
         * given the specified turn radius and cross-track error.
         *
         * If the magnitude of the cross-track error is greater than twice the turn radius, no constant-radius turn
         * overlapping the plane's position will be tangent to the desired track; in this case the maximum possible intercept
         * angle of 90 degrees is returned.
         * @param turnRadius The turn radius, in the same units as `xtk`.
         * @param xtk The cross-track error, in the same units as `turnRadius`.
         * @returns The calculated intercept angle, in degrees.
         */
        static calculateTurnBasedInterceptAngle(turnRadius, xtk) {
            // The following formula is derived by solving for the intercept angle in Euclidean rather than spherical space.
            // The error from this simplification is negligible when turn radius and cross-track are small (less than 1% of
            // earth radius, or ~63km).
            // The Euclidean solution is chosen over the spherical one: asin(sin(xtk) / sqrt(1 - (1 - sin(xtk) * tan(radius))^2))
            // for performance reasons.
            return Math.asin(Math.min(Math.sqrt(Math.abs(xtk) / (2 * turnRadius)), 1)) * Avionics.Utils.RAD2DEG;
        }
    }

    /**
     * A Garmin Autopilot Configuration.
     */
    class GarminAPConfig {
        // eslint-disable-next-line jsdoc/require-jsdoc
        constructor(bus, arg2, verticalPathCalculator, options) {
            //PM Modif added TO/GA angles
            var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
            this.bus = bus;
            this.verticalPathCalculator = verticalPathCalculator;
            this.defaultLateralMode = msfsSdk.APLateralModes.ROLL;
            this.defaultVerticalMode = msfsSdk.APVerticalModes.PITCH;
            // End PM modif
            //PM Modif default roll/pitch made taken from the sim
            const defaultRollMode = SimVar.GetSimVarValue('AUTOPILOT DEFAULT ROLL MODE', msfssdk.SimVarValueType.Number);
            const defaultPitchMode = SimVar.GetSimVarValue('AUTOPILOT DEFAULT PITCH MODE', msfssdk.SimVarValueType.Number);
            switch (defaultRollMode) {
                case 1:
                    this.defaultLateralMode = msfssdk.APLateralModes.LEVEL;
                    break;
                case 2:
                    this.defaultLateralMode = msfssdk.APLateralModes.HEADING;
                    break;
                case 3:
                    this.defaultLateralMode = msfssdk.APLateralModes.ROLL;
                    break;
                default:
                    this.defaultLateralMode = msfssdk.APLateralModes.NONE;
            }
            switch (defaultPitchMode) {
                case 1:
                    this.defaultVerticalMode = msfssdk.APVerticalModes.PITCH;
                    break;
                case 2:
                    this.defaultVerticalMode = msfssdk.APVerticalModes.ALT;
                    break;
                case 3:
                    this.defaultVerticalMode = msfssdk.APVerticalModes.VS;
                    break;
                default:
                    this.defaultVerticalMode = msfssdk.APVerticalModes.NONE;
            }
            //End PM Modif


            this.defaultMaxBankAngle = GarminAPConfig.DEFAULT_MAX_BANK_ANGLE;
            this.defaultVerticalPathGuidance = {
                isValid: false,
                fpa: 0,
                deviation: 0
            };
            this.defaultGlidepathGuidance = {
                approachHasGlidepath: false,
                isValid: false,
                canCapture: false,
                fpa: 0,
                deviation: 0
            };
            this.cdiId = (_a = options === null || options === void 0 ? void 0 : options.cdiId) !== null && _a !== void 0 ? _a : '';
            if (arg2 instanceof msfsSdk.FlightPlanner) {
                this.flightPlanner = arg2;
            }
            else {
                this.fms = arg2;
                if (this.fms instanceof Fms) {
                    this.flightPlanner = this.fms.flightPlanner;
                }
            }
            this.autopilotDriverOptions = {
                pitchServoRate: (_b = options === null || options === void 0 ? void 0 : options.defaultPitchRate) !== null && _b !== void 0 ? _b : GarminAPConfig.DEFAULT_PITCH_RATE,
                bankServoRate: (_c = options === null || options === void 0 ? void 0 : options.defaultBankRate) !== null && _c !== void 0 ? _c : GarminAPConfig.DEFAULT_BANK_RATE
            };
            this.useIndicatedMach = (_d = options === null || options === void 0 ? void 0 : options.useIndicatedMach) !== null && _d !== void 0 ? _d : false;
            this.lnavOptions = Object.assign({}, options === null || options === void 0 ? void 0 : options.lnavOptions);
            this.useInternalVNavComputer = (_e = options === null || options === void 0 ? void 0 : options.useInternalVNavComputer) !== null && _e !== void 0 ? _e : true;
            this.vnavGuidance = options === null || options === void 0 ? void 0 : options.vnavGuidance;
            this.vnavOptions = this.useInternalVNavComputer ? options === null || options === void 0 ? void 0 : options.vnavOptions : undefined;
            this.verticalPathGuidance = this.useInternalVNavComputer
                ? {
                    /** @inheritDoc */
                    get: () => {
                        return this.vnavManager && this.vnavManager.vnavComputer
                            ? this.vnavManager.vnavComputer.pathGuidance.get()
                            : this.defaultVerticalPathGuidance;
                    }
                }
                : (_f = options === null || options === void 0 ? void 0 : options.verticalPathGuidance) !== null && _f !== void 0 ? _f : { get: () => this.defaultVerticalPathGuidance };
            this.glidepathGuidance = this.useInternalVNavComputer
                ? {
                    /** @inheritDoc */
                    get: () => {
                        return this.vnavManager && this.vnavManager.glidepathComputer
                            ? this.vnavManager.glidepathComputer.glidepathGuidance.get()
                            : this.defaultGlidepathGuidance;
                    }
                }
                : (_g = options === null || options === void 0 ? void 0 : options.glidepathGuidance) !== null && _g !== void 0 ? _g : { get: () => this.defaultGlidepathGuidance };
            this.navToNavGuidance = options === null || options === void 0 ? void 0 : options.navToNavGuidance;
            this.rollMinBankAngle = (_h = options === null || options === void 0 ? void 0 : options.rollMinBankAngle) !== null && _h !== void 0 ? _h : GarminAPConfig.DEFAULT_ROLL_MIN_BANK_ANGLE;
            this.rollMaxBankAngle = (_j = options === null || options === void 0 ? void 0 : options.rollMaxBankAngle) !== null && _j !== void 0 ? _j : GarminAPConfig.DEFAULT_MAX_BANK_ANGLE;
            this.hdgMaxBankAngle = (_k = options === null || options === void 0 ? void 0 : options.hdgMaxBankAngle) !== null && _k !== void 0 ? _k : GarminAPConfig.DEFAULT_MAX_BANK_ANGLE;
            this.vorMaxBankAngle = (_l = options === null || options === void 0 ? void 0 : options.vorMaxBankAngle) !== null && _l !== void 0 ? _l : GarminAPConfig.DEFAULT_MAX_BANK_ANGLE;
            this.locMaxBankAngle = (_m = options === null || options === void 0 ? void 0 : options.locMaxBankAngle) !== null && _m !== void 0 ? _m : GarminAPConfig.DEFAULT_MAX_BANK_ANGLE;
            this.lnavMaxBankAngle = (_o = options === null || options === void 0 ? void 0 : options.lnavMaxBankAngle) !== null && _o !== void 0 ? _o : GarminAPConfig.DEFAULT_MAX_BANK_ANGLE;
            this.lowBankAngle = (_p = options === null || options === void 0 ? void 0 : options.lowBankAngle) !== null && _p !== void 0 ? _p : GarminAPConfig.DEFAULT_LOW_BANK_ANGLE;
            this.hdgTurnReversalThreshold = (_q = options === null || options === void 0 ? void 0 : options.hdgTurnReversalThreshold) !== null && _q !== void 0 ? _q : GarminAPConfig.DEFAULT_HDG_DIRECTION_UNLOCK_THRESHOLD;
            //PM Modif added TO/GA angles
            this.toPitchAngle = (_q = options === null || options === void 0 ? void 0 : options.toPitchAngle) !== null && _q !== void 0 ? _q : GarminAPConfig.DEFAULT_TO_PITCH_ANGLE;
            this.gaPitchAngle = (_r = options === null || options === void 0 ? void 0 : options.gaPitchAngle) !== null && _r !== void 0 ? _r : GarminAPConfig.DEFAULT_GA_PITCH_ANGLE;
            //End PM Modif
        }
        /** @inheritdoc */
        createHeadingDirector(apValues) {
            return new msfsSdk.APHdgDirector(this.bus, apValues, {
                maxBankAngle: () => apValues.maxBankId.get() === 1 ? Math.min(this.hdgMaxBankAngle, this.lowBankAngle) : this.hdgMaxBankAngle,
                turnReversalThreshold: this.hdgTurnReversalThreshold
            });
        }
        /** @inheritdoc */
        createRollDirector(apValues) {
            return new msfsSdk.APRollDirector(apValues, { minBankAngle: this.rollMinBankAngle, maxBankAngle: this.rollMaxBankAngle });
        }
        /** @inheritdoc */
        createWingLevelerDirector(apValues) {
            return new msfsSdk.APLvlDirector(this.bus, apValues);
        }
        /** @inheritdoc */
        createPitchLevelerDirector(apValues) {
            return new msfsSdk.APPitchLvlDirector(apValues);
        }
        /** @inheritdoc */
        createGpssDirector(apValues) {
            const maxBankAngle = () => apValues.maxBankId.get() === 1 ? Math.min(this.lnavMaxBankAngle, this.lowBankAngle) : this.lnavMaxBankAngle;
            if (this.lnavOptions.steerCommand) {
                return new msfsSdk.APGpsSteerDirector(this.bus, apValues, this.lnavOptions.steerCommand, {
                    maxBankAngle,
                    canActivate: state => Math.abs(state.xtk) < 0.6 && Math.abs(state.tae) < 110,
                    disableArming: this.lnavOptions.disableArming
                });
            }
            else {
                if (this.flightPlanner) {
                    return new msfsSdk.LNavDirector(this.bus, apValues, this.flightPlanner, new GarminObsDirector(this.bus, apValues, {
                        maxBankAngle,
                        lateralInterceptCurve: GarminAPUtils.lnavIntercept,
                    }), {
                        maxBankAngle,
                        lateralInterceptCurve: GarminAPUtils.lnavIntercept,
                        hasVectorAnticipation: true,
                        disableArming: this.lnavOptions.disableArming
                    });
                }
                else {
                    return undefined;
                }
            }
        }
        /** @inheritdoc */
        createVorDirector(apValues) {
            return new msfsSdk.APNavDirector(this.bus, apValues, msfsSdk.APLateralModes.VOR, {
                maxBankAngle: () => apValues.maxBankId.get() === 1 ? Math.min(this.vorMaxBankAngle, this.lowBankAngle) : this.vorMaxBankAngle,
                lateralInterceptCurve: GarminAPUtils.navIntercept
            });
        }
        /** @inheritdoc */
        createLocDirector(apValues) {
            return new msfsSdk.APNavDirector(this.bus, apValues, msfsSdk.APLateralModes.LOC, {
                maxBankAngle: () => apValues.maxBankId.get() === 1 ? Math.min(this.locMaxBankAngle, this.lowBankAngle) : this.locMaxBankAngle,
                lateralInterceptCurve: GarminAPUtils.navIntercept
            });
        }
        /** @inheritdoc */
        createBcDirector(apValues) {
            return new msfsSdk.APBackCourseDirector(this.bus, apValues, {
                maxBankAngle: () => apValues.maxBankId.get() === 1 ? Math.min(this.locMaxBankAngle, this.lowBankAngle) : this.locMaxBankAngle,
                lateralInterceptCurve: (distanceToSource, deflection, xtk, tas) => GarminAPUtils.localizerIntercept(xtk, tas)
            });
        }
        /** @inheritdoc */
        createPitchDirector(apValues) {
            return new msfsSdk.APPitchDirector(this.bus, apValues);
        }
        /** @inheritdoc */
        createVsDirector(apValues) {
            return new msfsSdk.APVSDirector(apValues);
        }
        /** @inheritdoc */
        createFlcDirector(apValues) {
            return new msfsSdk.APFLCDirector(apValues, { useIndicatedMach: this.useIndicatedMach });
        }
        /** @inheritdoc */
        createAltHoldDirector(apValues) {
            return new msfsSdk.APAltDirector(apValues);
        }
        /** @inheritdoc */
        createAltCapDirector(apValues) {
            return new msfsSdk.APAltCapDirector(apValues);
        }
        /** @inheritdoc */
        createVNavManager(apValues) {
            if (this.useInternalVNavComputer) {
                if (this.flightPlanner && this.verticalPathCalculator) {
                    return this.vnavManager = new GarminVNavManager2(this.bus, this.flightPlanner, this.verticalPathCalculator, apValues, this.vnavOptions);
                }
                else {
                    return undefined;
                }
            }
            else {
                return this.vnavManager = new GarminVNavManager2(this.bus, apValues, this.vnavGuidance, this.glidepathGuidance);
            }
        }
        /** @inheritdoc */
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        createVNavPathDirector(apValues) {
            return new msfsSdk.APVNavPathDirector(this.bus, { guidance: this.verticalPathGuidance });
        }
        /** @inheritdoc */
        createGpDirector(apValues) {
            return new msfsSdk.APGPDirector(this.bus, apValues, {
                guidance: this.glidepathGuidance,
                canCapture: this.glidepathGuidance
                    ? () => {
                        return apValues.lateralActive.get() === msfsSdk.APLateralModes.GPSS && this.glidepathGuidance.get().canCapture;
                    }
                    : undefined
            });
        }
        /** @inheritdoc */
        createGsDirector(apValues) {
            return new msfsSdk.APGSDirector(this.bus, apValues);
        }
        //PM Modif added TO/GA angles
        /** @inheritdoc */
        createToVerticalDirector() {
            //TODO: This value should be read in from the systems.cfg 'pitch_takeoff_ga' value
            return new msfsSdk.APTogaPitchDirector(this.toPitchAngle);
        }
        /** @inheritdoc */
        createGaVerticalDirector() {
            return new msfsSdk.APTogaPitchDirector(this.gaPitchAngle);
        }
        // End PM modif
        /** @inheritdoc */
        createToLateralDirector(apValues) {
            return new msfsSdk.APLvlDirector(this.bus, apValues, { isToGaMode: true });
        }
        /** @inheritdoc */
        createGaLateralDirector(apValues) {
            return new msfsSdk.APLvlDirector(this.bus, apValues, { isToGaMode: true });
        }
        /** @inheritdoc */
        createNavToNavManager(apValues) {
            if (this.navToNavGuidance) {
                return new GarminNavToNavManager2(this.bus, apValues, this.navToNavGuidance, this.navToNavOptions);
            }
            if (this.fms) {
                return new GarminNavToNavManager(this.bus, this.fms, apValues);
            }
            else if (this.flightPlanner) {
                return new GarminNavToNavManager(this.bus, this.flightPlanner, apValues);
            }
            else {
                return undefined;
            }
        }
    }
    /** The default commanded pitch angle rate, in degrees per second. */
    GarminAPConfig.DEFAULT_PITCH_RATE = 5;
    /** The default commanded bank angle rate, in degrees per second. */
    GarminAPConfig.DEFAULT_BANK_RATE = 10;
    /** The default minimum bank angle, in degrees, for ROL director. */
    GarminAPConfig.DEFAULT_ROLL_MIN_BANK_ANGLE = 6;
    /** The default maximum bank angle, in degrees, for ROL, HDG, NAV, and LNAV directors. */
    GarminAPConfig.DEFAULT_MAX_BANK_ANGLE = 25;
    /** The default maximum bank angle, in degrees, in Low Bank Mode. */
    GarminAPConfig.DEFAULT_LOW_BANK_ANGLE = 15;
    /** The default HDG director turn direction unlock threshold, in degrees. */
    GarminAPConfig.DEFAULT_HDG_DIRECTION_UNLOCK_THRESHOLD = 331;
    //PM Modif added TO/GA angles
    /** The default TO pitch angle, in degrees. */
    GarminAPConfig.DEFAULT_TO_PITCH_ANGLE = 10;
    /** The default GA pitch angle, in degrees. */
    GarminAPConfig.DEFAULT_GA_PITCH_ANGLE = 7.5;
    // End PM modif

    /**
     * A Garmin autopilot state manager.
     */
    class GarminAPStateManager extends msfsSdk.APStateManager {
        constructor() {
            super(...arguments);
            this.vsLastPressed = 0;
            this.controlEventPub = this.bus.getPublisher();
        }
        /** @inheritdoc */
        onAPListenerRegistered() {
            super.onAPListenerRegistered();
            const hEvent = this.bus.getSubscriber();
            hEvent.on('hEvent').handle((e) => {
                if (e === 'AS1000_VNAV_TOGGLE') {
                    this.toggleVnav();
                }
            });
        }
        /** @inheritdoc */
        setupKeyIntercepts(manager) {
            //alt modes
            manager.interceptKey('AP_ALT_HOLD', false);
            manager.interceptKey('AP_ALT_HOLD', false);
            manager.interceptKey('AP_ALT_HOLD_ON', false);
            manager.interceptKey('AP_ALT_HOLD_OFF', false);
            manager.interceptKey('AP_PANEL_ALTITUDE_HOLD', false);
            manager.interceptKey('AP_PANEL_ALTITUDE_ON', false);
            manager.interceptKey('AP_PANEL_ALTITUDE_OFF', false);
            manager.interceptKey('AP_PANEL_ALTITUDE_SET', false);
            //vs modes
            manager.interceptKey('AP_PANEL_VS_HOLD', false);
            manager.interceptKey('AP_PANEL_VS_ON', false);
            manager.interceptKey('AP_PANEL_VS_OFF', false);
            manager.interceptKey('AP_PANEL_VS_SET', false);
            manager.interceptKey('AP_VS_HOLD', false);
            manager.interceptKey('AP_VS_ON', false);
            manager.interceptKey('AP_VS_OFF', false);
            manager.interceptKey('AP_VS_SET', false);
            //pitch modes
            manager.interceptKey('AP_ATT_HOLD', false);
            manager.interceptKey('AP_ATT_HOLD_ON', false);
            manager.interceptKey('AP_ATT_HOLD_OFF', false);
            manager.interceptKey('AP_PITCH_LEVELER', false);
            manager.interceptKey('AP_PITCH_LEVELER_ON', false);
            manager.interceptKey('AP_PITCH_LEVELER_OFF', false);
            //roll modes
            manager.interceptKey('AP_BANK_HOLD', false);
            manager.interceptKey('AP_BANK_HOLD_ON', false);
            manager.interceptKey('AP_BANK_HOLD_OFF', false);
            manager.interceptKey('AP_WING_LEVELER', false);
            manager.interceptKey('AP_WING_LEVELER_ON', false);
            manager.interceptKey('AP_WING_LEVELER_OFF', false);
            //flc modes
            manager.interceptKey('FLIGHT_LEVEL_CHANGE', false);
            manager.interceptKey('FLIGHT_LEVEL_CHANGE_ON', false);
            manager.interceptKey('FLIGHT_LEVEL_CHANGE_OFF', false);
            //nav modes
            manager.interceptKey('AP_NAV1_HOLD', false);
            manager.interceptKey('AP_NAV1_HOLD_ON', false);
            manager.interceptKey('AP_NAV1_HOLD_OFF', false);
            //hdg modes
            manager.interceptKey('AP_HDG_HOLD', false);
            manager.interceptKey('AP_HDG_HOLD_ON', false);
            manager.interceptKey('AP_HDG_HOLD_OFF', false);
            manager.interceptKey('AP_PANEL_HEADING_HOLD', false);
            manager.interceptKey('AP_PANEL_HEADING_ON', false);
            manager.interceptKey('AP_PANEL_HEADING_OFF', false);
            manager.interceptKey('AP_PANEL_HEADING_SET', false);
            //bank modes
            manager.interceptKey('AP_BANK_HOLD', false);
            manager.interceptKey('AP_BANK_HOLD_ON', false);
            manager.interceptKey('AP_BANK_HOLD_OFF', false);
            //appr modes
            manager.interceptKey('AP_LOC_HOLD', false);
            manager.interceptKey('AP_LOC_HOLD_ON', false);
            manager.interceptKey('AP_LOC_HOLD_OFF', false);
            manager.interceptKey('AP_APR_HOLD', false);
            manager.interceptKey('AP_APR_HOLD_ON', false);
            manager.interceptKey('AP_APR_HOLD_OFF', false);
            manager.interceptKey('AP_BC_HOLD', false);
            manager.interceptKey('AP_BC_HOLD_ON', false);
            manager.interceptKey('AP_BC_HOLD_OFF', false);
            //TOGA intercept
            manager.interceptKey('AUTO_THROTTLE_TO_GA', false);
        }
        /** @inheritdoc */
        handleKeyIntercepted({ key, value0 }) {
            switch (key) {
                case 'AP_NAV1_HOLD':
                    this.sendApModeEvent(msfsSdk.APModeType.LATERAL, msfsSdk.APLateralModes.NAV);
                    break;
                case 'AP_NAV1_HOLD_ON':
                    this.sendApModeEvent(msfsSdk.APModeType.LATERAL, msfsSdk.APLateralModes.NAV, true);
                    break;
                case 'AP_NAV1_HOLD_OFF':
                    this.sendApModeEvent(msfsSdk.APModeType.LATERAL, msfsSdk.APLateralModes.NAV, false);
                    break;
                case 'AP_LOC_HOLD':
                    this.sendApModeEvent(msfsSdk.APModeType.LATERAL, msfsSdk.APLateralModes.LOC);
                    break;
                case 'AP_LOC_HOLD_ON':
                    this.sendApModeEvent(msfsSdk.APModeType.LATERAL, msfsSdk.APLateralModes.LOC, true);
                    break;
                case 'AP_LOC_HOLD_OFF':
                    this.sendApModeEvent(msfsSdk.APModeType.LATERAL, msfsSdk.APLateralModes.LOC, false);
                    break;
                case 'AP_APR_HOLD':
                    this.sendApModeEvent(msfsSdk.APModeType.APPROACH);
                    break;
                case 'AP_APR_HOLD_ON':
                    this.sendApModeEvent(msfsSdk.APModeType.APPROACH, undefined, true);
                    break;
                case 'AP_APR_HOLD_OFF':
                    this.sendApModeEvent(msfsSdk.APModeType.APPROACH, undefined, false);
                    break;
                case 'AP_BC_HOLD':
                    this.sendApModeEvent(msfsSdk.APModeType.LATERAL, msfsSdk.APLateralModes.BC);
                    break;
                case 'AP_BC_HOLD_ON':
                    this.sendApModeEvent(msfsSdk.APModeType.LATERAL, msfsSdk.APLateralModes.BC, true);
                    break;
                case 'AP_BC_HOLD_OFF':
                    this.sendApModeEvent(msfsSdk.APModeType.LATERAL, msfsSdk.APLateralModes.BC, false);
                    break;
                case 'AP_HDG_HOLD':
                case 'AP_PANEL_HEADING_HOLD':
                    this.sendApModeEvent(msfsSdk.APModeType.LATERAL, msfsSdk.APLateralModes.HEADING);
                    break;
                case 'AP_PANEL_HEADING_ON':
                case 'AP_HDG_HOLD_ON':
                    this.sendApModeEvent(msfsSdk.APModeType.LATERAL, msfsSdk.APLateralModes.HEADING, true);
                    break;
                case 'AP_PANEL_HEADING_OFF':
                case 'AP_HDG_HOLD_OFF':
                    this.sendApModeEvent(msfsSdk.APModeType.LATERAL, msfsSdk.APLateralModes.HEADING, false);
                    break;
                case 'AP_PANEL_HEADING_SET':
                    if (value0 !== undefined) {
                        this.sendApModeEvent(msfsSdk.APModeType.LATERAL, msfsSdk.APLateralModes.HEADING, value0 === 1 ? true : false);
                    }
                    break;
                case 'AP_BANK_HOLD':
                case 'AP_BANK_HOLD_ON':
                    this.sendApModeEvent(msfsSdk.APModeType.LATERAL, msfsSdk.APLateralModes.ROLL, true);
                    break;
                case 'AP_WING_LEVELER':
                    this.sendApModeEvent(msfsSdk.APModeType.LATERAL, msfsSdk.APLateralModes.LEVEL);
                    break;
                case 'AP_WING_LEVELER_ON':
                    this.sendApModeEvent(msfsSdk.APModeType.LATERAL, msfsSdk.APLateralModes.LEVEL, true);
                    break;
                case 'AP_WING_LEVELER_OFF':
                    this.sendApModeEvent(msfsSdk.APModeType.LATERAL, msfsSdk.APLateralModes.LEVEL, false);
                    break;
                case 'AP_PITCH_LEVELER':
                    this.sendApModeEvent(msfsSdk.APModeType.VERTICAL, msfsSdk.APVerticalModes.LEVEL);
                    break;
                case 'AP_PITCH_LEVELER_ON':
                    this.sendApModeEvent(msfsSdk.APModeType.VERTICAL, msfsSdk.APVerticalModes.LEVEL, true);
                    break;
                case 'AP_PITCH_LEVELER_OFF':
                    this.sendApModeEvent(msfsSdk.APModeType.VERTICAL, msfsSdk.APVerticalModes.LEVEL, false);
                    break;
                case 'AP_PANEL_VS_HOLD':
                case 'AP_VS_HOLD':
                    this.sendApModeEvent(msfsSdk.APModeType.VERTICAL, msfsSdk.APVerticalModes.VS);
                    break;
                case 'AP_PANEL_VS_ON':
                case 'AP_VS_ON':
                    this.sendApModeEvent(msfsSdk.APModeType.VERTICAL, msfsSdk.APVerticalModes.VS, true);
                    break;
                case 'AP_PANEL_VS_OFF':
                case 'AP_VS_OFF':
                    this.sendApModeEvent(msfsSdk.APModeType.VERTICAL, msfsSdk.APVerticalModes.VS, false);
                    break;
                case 'AP_PANEL_VS_SET':
                case 'AP_VS_SET':
                    // TODO Remove this when the Bravo default mapping is fixed.
                    if (value0 !== undefined && this.vsLastPressed < Date.now() - 100) {
                        this.sendApModeEvent(msfsSdk.APModeType.VERTICAL, msfsSdk.APVerticalModes.VS, value0 === 1 ? true : false);
                    }
                    this.vsLastPressed = Date.now();
                    break;
                case 'AP_ALT_HOLD':
                case 'AP_PANEL_ALTITUDE_HOLD':
                    this.sendApModeEvent(msfsSdk.APModeType.VERTICAL, msfsSdk.APVerticalModes.ALT);
                    break;
                case 'AP_ALT_HOLD_ON':
                case 'AP_PANEL_ALTITUDE_ON':
                    this.sendApModeEvent(msfsSdk.APModeType.VERTICAL, msfsSdk.APVerticalModes.ALT, true);
                    break;
                case 'AP_ALT_HOLD_OFF':
                case 'AP_PANEL_ALTITUDE_OFF':
                    this.sendApModeEvent(msfsSdk.APModeType.VERTICAL, msfsSdk.APVerticalModes.ALT, false);
                    break;
                case 'AP_PANEL_ALTITUDE_SET':
                    if (value0 !== undefined) {
                        this.sendApModeEvent(msfsSdk.APModeType.VERTICAL, msfsSdk.APVerticalModes.ALT, value0 === 1 ? true : false);
                    }
                    break;
                case 'FLIGHT_LEVEL_CHANGE':
                    this.sendApModeEvent(msfsSdk.APModeType.VERTICAL, msfsSdk.APVerticalModes.FLC);
                    break;
                case 'FLIGHT_LEVEL_CHANGE_ON':
                    this.sendApModeEvent(msfsSdk.APModeType.VERTICAL, msfsSdk.APVerticalModes.FLC, true);
                    break;
                case 'FLIGHT_LEVEL_CHANGE_OFF':
                    this.sendApModeEvent(msfsSdk.APModeType.VERTICAL, msfsSdk.APVerticalModes.FLC, false);
                    break;
                case 'AUTO_THROTTLE_TO_GA':
                    this.sendApModeEvent(msfsSdk.APModeType.VERTICAL, msfsSdk.APVerticalModes.TO);
            }
        }
        /** @inheritdoc */
        onBeforeInitialize() {
            SimVar.SetSimVarValue('L:WT1000_AP_G1000_INSTALLED', msfsSdk.SimVarValueType.Bool, true);
        }
    }

    /**
     * A Garmin autopilot.
     */
    class GarminAutopilot extends msfsSdk.Autopilot {
        /**
         * Creates a new instance of GarminAutopilot.
         * @param bus The event bus.
         * @param flightPlanner This autopilot's associated flight planner.
         * @param config This autopilot's configuration.
         * @param stateManager This autopilot's state manager.
         * @param options Options with which to configure the new autopilot.
         */
        constructor(bus, flightPlanner, config, stateManager, options) {
            var _a;
            super(bus, flightPlanner, config, stateManager);
            this.altSelectStops = msfsSdk.SetSubject.create();
            this.fmaData = msfsSdk.ObjectSubject.create({
                verticalActive: msfsSdk.APVerticalModes.NONE,
                verticalArmed: msfsSdk.APVerticalModes.NONE,
                verticalApproachArmed: msfsSdk.APVerticalModes.NONE,
                verticalAltitudeArmed: msfsSdk.APAltitudeModes.NONE,
                altitideCaptureArmed: false,
                altitideCaptureValue: -1,
                lateralActive: msfsSdk.APLateralModes.NONE,
                lateralArmed: msfsSdk.APLateralModes.NONE,
                lateralModeFailed: false,
                vnavState: exports.FmaVNavState.OFF
            });
            this.fmaDataPublisher = this.bus.getPublisher();
            this.needPublishFmaData = false;
            this.machToKias = msfsSdk.ConsumerValue.create(null, 1);
            this.selSpeedIsMach = msfsSdk.ConsumerSubject.create(null, false);
            const altSelectManagerOptions = Object.assign({}, options === null || options === void 0 ? void 0 : options.altSelectOptions);
            for (const key in GarminAutopilot.ALT_SELECT_OPTIONS_DEFAULT) {
                const opt = key;
                if (altSelectManagerOptions[opt] === undefined) {
                    altSelectManagerOptions[opt] = GarminAutopilot.ALT_SELECT_OPTIONS_DEFAULT[opt];
                }
            }
            this.supportMachSelect = (_a = options === null || options === void 0 ? void 0 : options.supportMachSelect) !== null && _a !== void 0 ? _a : false;
            this.altSelectManager = new msfsSdk.AltitudeSelectManager(this.bus, options === null || options === void 0 ? void 0 : options.metricAltSettingsManager, altSelectManagerOptions, this.altSelectStops);
            this.fmaData.sub(() => {
                this.needPublishFmaData = true;
            }, true);
            if (options === null || options === void 0 ? void 0 : options.minimumsDataProvider) {
                // Add a stop for the altitude preselector at the minimums altitude if BARO minimums are active.
                msfsSdk.MappedSubject.create(options.minimumsDataProvider.mode, options.minimumsDataProvider.minimums).sub(([mode, minimums]) => {
                    this.altSelectStops.clear();
                    if (mode === msfsSdk.MinimumsMode.BARO && minimums !== null) {
                        this.altSelectStops.add(minimums);
                    }
                }, true);
            }
        }
        /**
         * Resets this autopilot. Resets the altitude preselector, sets AP MASTER to off, and deactivates the flight
         * director.
         */
        reset() {
            this.altSelectManager.reset(0, true);
            if (SimVar.GetSimVarValue('AUTOPILOT MASTER', msfsSdk.SimVarValueType.Bool) !== 0) {
                SimVar.SetSimVarValue('K:AP_MASTER', msfsSdk.SimVarValueType.Number, 0);
            }
            if (SimVar.GetSimVarValue('AUTOPILOT FLIGHT DIRECTOR ACTIVE', msfsSdk.SimVarValueType.Bool) !== 0) {
                SimVar.SetSimVarValue('K:TOGGLE_FLIGHT_DIRECTOR', msfsSdk.SimVarValueType.Number, 0);
            }
        }
        /** @inheritDoc */
        initVerticalModes() {
            super.initVerticalModes();
            if (this.directors.gpDirector) {
                this.directors.gpDirector.onActivate = () => {
                    this.setVerticalActive(msfsSdk.APVerticalModes.GP);
                    this.setVerticalArmed(msfsSdk.APVerticalModes.NONE);
                };
            }
        }
        /** @inheritDoc */
        onInitialized() {
            this.monitorAdditionalEvents();
        }
        /** @inheritDoc */
        monitorAdditionalEvents() {
            const sub = this.bus.getSubscriber();
            if (this.supportMachSelect) {
                // Whenever we switch between mach and IAS hold and we are in manual speed mode, we need to set the value to which
                // we are switching to be equal to the value we are switching from.
                this.machToKias.setConsumer(sub.on(this.config.useIndicatedMach ? 'indicated_mach_to_kias_factor_1' : 'mach_to_kias_factor_1'));
                this.selSpeedIsMach.setConsumer(sub.on('ap_selected_speed_is_mach'));
                const speedIsMachSub = this.selSpeedIsMach.sub(isMach => {
                    if (isMach) {
                        // Round mach to nearest 0.001.
                        SimVar.SetSimVarValue('K:AP_MACH_VAR_SET_EX1', msfsSdk.SimVarValueType.Number, Math.round(this.apValues.selectedIas.get() / this.machToKias.get() * 1e3) * 1e3);
                    }
                    else {
                        // Round IAS to nearest knot.
                        SimVar.SetSimVarValue('K:AP_SPD_VAR_SET', msfsSdk.SimVarValueType.Number, Math.round(this.apValues.selectedMach.get() * this.machToKias.get()));
                    }
                }, false, true);
                sub.on('ap_selected_speed_is_manual').whenChanged().handle(isManual => {
                    if (isManual) {
                        speedIsMachSub.resume();
                    }
                    else {
                        speedIsMachSub.pause();
                    }
                });
            }
        }
        /**
         * Checks and sets the proper armed altitude mode.
         */
        manageAltitudeCapture() {
            var _a, _b, _c;
            let altCapType = msfsSdk.APAltitudeModes.NONE;
            let armAltCap = false;
            switch (this.apValues.verticalActive.get()) {
                case msfsSdk.APVerticalModes.VS:
                case msfsSdk.APVerticalModes.FLC:
                case msfsSdk.APVerticalModes.PITCH:
                case msfsSdk.APVerticalModes.TO:
                case msfsSdk.APVerticalModes.GA:
                    armAltCap = true;
                    altCapType = this.vnavCaptureType === msfsSdk.VNavAltCaptureType.VNAV ? msfsSdk.APAltitudeModes.ALTV : msfsSdk.APAltitudeModes.ALTS;
                    break;
                case msfsSdk.APVerticalModes.PATH: {
                    altCapType = this.vnavCaptureType === msfsSdk.VNavAltCaptureType.VNAV ? msfsSdk.APAltitudeModes.ALTV : msfsSdk.APAltitudeModes.ALTS;
                    break;
                }
                case msfsSdk.APVerticalModes.CAP:
                    altCapType = this.verticalAltitudeArmed;
                    break;
            }
            if (this.verticalAltitudeArmed !== altCapType) {
                this.verticalAltitudeArmed = altCapType;
            }
            if (armAltCap && (!this.altCapArmed || ((_a = this.verticalModes.get(msfsSdk.APVerticalModes.CAP)) === null || _a === void 0 ? void 0 : _a.state) === msfsSdk.DirectorState.Inactive)) {
                (_b = this.verticalModes.get(msfsSdk.APVerticalModes.CAP)) === null || _b === void 0 ? void 0 : _b.arm();
            }
            else if (!armAltCap && this.altCapArmed) {
                (_c = this.verticalModes.get(msfsSdk.APVerticalModes.CAP)) === null || _c === void 0 ? void 0 : _c.deactivate();
                this.altCapArmed = false;
            }
        }
        /** @inheritDoc */
        onAfterUpdate() {
            this.updateFma();
        }
        /**
         * Publishes data for the FMA.
         */
        updateFma() {
            const fmaData = this.fmaData;
            const vnavManager = this.vnavManager;
            let fmaVNavState = exports.FmaVNavState.OFF;
            if (vnavManager) {
                fmaVNavState = vnavManager.isActive
                    ? exports.FmaVNavState.ACTIVE
                    : vnavManager.state === msfsSdk.VNavState.Enabled_Active ? exports.FmaVNavState.ARMED : exports.FmaVNavState.OFF;
            }
            fmaData.set('verticalApproachArmed', this.verticalApproachArmed);
            fmaData.set('verticalArmed', this.apValues.verticalArmed.get());
            fmaData.set('verticalActive', this.apValues.verticalActive.get());
            fmaData.set('verticalAltitudeArmed', this.verticalAltitudeArmed);
            fmaData.set('altitideCaptureArmed', this.altCapArmed);
            fmaData.set('altitideCaptureValue', this.apValues.capturedAltitude.get());
            fmaData.set('lateralActive', this.apValues.lateralActive.get());
            fmaData.set('lateralArmed', this.apValues.lateralArmed.get());
            fmaData.set('lateralModeFailed', this.lateralModeFailed);
            fmaData.set('vnavState', fmaVNavState);
            if (this.needPublishFmaData) {
                this.needPublishFmaData = false;
                this.fmaDataPublisher.pub('fma_data', Object.assign({}, fmaData.get()), true, true);
            }
        }
    }
    GarminAutopilot.ALT_SELECT_OPTIONS_DEFAULT = {
        supportMetric: true,
        minValue: msfsSdk.UnitType.FOOT.createNumber(-1000),
        maxValue: msfsSdk.UnitType.FOOT.createNumber(50000),
        inputIncrLargeThreshold: 999,
        incrSmall: msfsSdk.UnitType.FOOT.createNumber(100),
        incrLarge: msfsSdk.UnitType.FOOT.createNumber(1000),
        incrSmallMetric: msfsSdk.UnitType.METER.createNumber(50),
        incrLargeMetric: msfsSdk.UnitType.METER.createNumber(500),
        initOnInput: true,
        initToIndicatedAlt: true,
        transformSetToIncDec: false
    };

    /**
     * A manager which responds to autopilot go-around mode activation by attempting to switch the active navigation
     * soruce to GPS and activate the missed approach.
     */
    class GarminGoAroundManager {
        /**
         * Creates a new instance of GarminGoAroundManager.
         * @param bus The event bus.
         * @param fms The FMS.
         * @param options Options with which to configure the manager.
         */
        constructor(bus, fms, options) {
            var _a;
            this.bus = bus;
            this.isLNavIndexValid = false;
            this.isLNavTracking = msfsSdk.ConsumerValue.create(null, false).pause();
            this.activeNavSource = msfsSdk.ConsumerSubject.create(null, { type: msfsSdk.NavSourceType.Nav, index: 1 }, msfsSdk.CdiUtils.navSourceIdEquals).pause();
            this.gpsSelectedDebounceTimer = new msfsSdk.DebounceTimer();
            this.gpsSelectedCallback = () => {
                if (this.activeNavSource.get().type === msfsSdk.NavSourceType.Gps) {
                    this.onGpsNavSourceSelected();
                }
            };
            this.isInit = false;
            this.isAlive = true;
            this.isPaused = true;
            this.fms = msfsSdk.SubscribableUtils.toSubscribable(fms, true);
            this.cdiId = msfsSdk.SubscribableUtils.toSubscribable((_a = options === null || options === void 0 ? void 0 : options.cdiId) !== null && _a !== void 0 ? _a : '', true);
        }
        /**
         * Initializes this manager. Once initialized, this manager will automatically attempt to switch the active
         * navigation source to GPS and activate the missed approach and when autopilot go-around mode is activated.
         * @param paused Whether to initialize this manager as paused. Defaults to `false`.
         * @throws Error if this manager has been destroyed.
         */
        init(paused = false) {
            if (!this.isAlive) {
                throw new Error('GarminGoAroundManager: cannot initialize a dead manager');
            }
            if (this.isInit) {
                return;
            }
            this.isInit = true;
            const sub = this.bus.getSubscriber();
            this.fmsSub = this.fms.sub(fms => {
                if (msfsSdk.LNavUtils.isValidLNavIndex(fms.lnavIndex)) {
                    this.isLNavIndexValid = true;
                    this.isLNavTracking.setConsumer(sub.on(`lnav_is_tracking${msfsSdk.LNavUtils.getEventBusTopicSuffix(fms.lnavIndex)}`));
                }
                else {
                    this.isLNavIndexValid = false;
                    this.isLNavTracking.setConsumer(null);
                }
            }, true);
            this.cdiIdSub = this.cdiId.sub(id => {
                this.activeNavSource.setConsumer(sub.on(`cdi_select${msfsSdk.CdiUtils.getEventBusTopicSuffix(id)}`));
            }, true);
            this.activeNavSourceSub = this.activeNavSource.sub(source => {
                var _a;
                (_a = this.activeNavSourceSub) === null || _a === void 0 ? void 0 : _a.pause();
                if (source.type === msfsSdk.NavSourceType.Gps) {
                    // Delay the callback by one frame in order to ensure that Fms gets the active nav source change event
                    // before we try to activate the missed approach.
                    this.gpsSelectedDebounceTimer.schedule(this.gpsSelectedCallback, 0);
                }
            }, false, true);
            this.fmaDataSub = sub.on('fma_data').handle(fmaData => {
                const isGaActive = fmaData.verticalActive === msfsSdk.APVerticalModes.GA;
                if (isGaActive && this.isGaActive === false) {
                    this.onGaActivated();
                }
                this.isGaActive = isGaActive;
            }, true);
            if (!paused) {
                this.resume();
            }
        }
        /**
         * Responds to when go-around mode has been activated. This will switch the active navigation source to GPS and the
         * autopilot lateral mode to NAV (GPS/FMS) if an approach is loaded and LNAV guidance is available. Additionally,
         * this will activate the missed approach if such an action is possible.
         */
        onGaActivated() {
            var _a, _b;
            (_a = this.activeNavSourceSub) === null || _a === void 0 ? void 0 : _a.pause();
            const fms = this.fms.get();
            if (this.isLNavIndexValid
                && this.isLNavTracking.get()
                && fms.hasPrimaryFlightPlan()
                && FmsUtils.isApproachLoaded(fms.getPrimaryFlightPlan())) {
                if (this.activeNavSource.get().type === msfsSdk.NavSourceType.Gps) {
                    this.onGpsNavSourceSelected();
                }
                else {
                    (_b = this.activeNavSourceSub) === null || _b === void 0 ? void 0 : _b.resume();
                    this.bus.getPublisher().pub(`cdi_src_set${msfsSdk.CdiUtils.getEventBusTopicSuffix(this.cdiId.get())}`, {
                        type: msfsSdk.NavSourceType.Gps,
                        index: 1
                    }, true, false);
                }
            }
        }
        /**
         * Responds to when GPS has been selected as the active navigation source after go-around mode has been activated.
         */
        onGpsNavSourceSelected() {
            // Switch AP lateral mode to NAV.
            SimVar.SetSimVarValue('K:AP_NAV1_HOLD_ON', msfsSdk.SimVarValueType.Number, 0);
            const fms = this.fms.get();
            // Attempt to activate missed approach.
            if (fms.canMissedApproachActivate()) {
                fms.activateMissedApproach();
            }
        }
        /**
         * Resumes this manager. Once resumed, this manager will automatically attempt to switch the active navigation source
         * to GPS and activate the missed approach and when autopilot go-around mode is activated.
         * @throws Error if this manager has been destroyed.
         */
        resume() {
            var _a;
            if (!this.isAlive) {
                throw new Error('GarminGoAroundManager: cannot resume a dead manager');
            }
            if (!this.isInit || !this.isPaused) {
                return;
            }
            this.isPaused = false;
            this.isLNavTracking.resume();
            this.activeNavSource.resume();
            (_a = this.fmaDataSub) === null || _a === void 0 ? void 0 : _a.resume();
        }
        /**
         * Pauses this manager. Once paused, this data provider will not respond to go-around mode activations until it is
         * resumed.
         * @throws Error if this manager has been destroyed.
         */
        pause() {
            var _a, _b;
            if (!this.isAlive) {
                throw new Error('GarminGoAroundManager: cannot pause a dead manager');
            }
            if (!this.isInit || this.isPaused) {
                return;
            }
            this.isPaused = true;
            this.isLNavTracking.pause();
            this.activeNavSource.pause();
            (_a = this.fmaDataSub) === null || _a === void 0 ? void 0 : _a.pause();
            (_b = this.activeNavSourceSub) === null || _b === void 0 ? void 0 : _b.pause();
            this.isGaActive = undefined;
        }
        /**
         * Destroys this manager. Once destroyed, this manager will cease responding to go-around mode activations, and can
         * no longer be paused or resumed.
         */
        destroy() {
            var _a, _b, _c, _d;
            this.isAlive = false;
            this.gpsSelectedDebounceTimer.clear();
            this.isLNavTracking.destroy();
            this.activeNavSource.destroy();
            (_a = this.fmsSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.cdiIdSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.activeNavSourceSub) === null || _c === void 0 ? void 0 : _c.destroy();
            (_d = this.fmaDataSub) === null || _d === void 0 ? void 0 : _d.destroy();
        }
    }

    /* eslint-disable @typescript-eslint/no-non-null-assertion */
    /**
     * A manager which handles heading sync events for the Garmin autopilot. The manager syncs the autopilot's selected
     * heading to current heading in response to heading sync H events. In addition, the manager has option support for
     * automatic adjustment of selected heading during turns while HDG mode is active and for heading sync mode while an
     * autopilot NAV mode is active.
     */
    class GarminHeadingSyncManager {
        /**
         * Creates a new instance of GarminHeadingSyncManager. The new manager is created uninitialized and paused.
         * @param bus The event bus.
         * @param ahrsIndex The index of the AHRS used by the autopilot.
         * @param options Options with which to configure the manager.
         */
        constructor(bus, ahrsIndex, options) {
            var _a, _b, _c, _d;
            this.bus = bus;
            this.publisher = this.bus.getPublisher();
            this.apFmaData = msfsSdk.ConsumerSubject.create(null, null);
            this.isApHdgModeActive = this.apFmaData.map(fmaData => {
                if (fmaData === null) {
                    return false;
                }
                return fmaData.lateralActive === msfsSdk.APLateralModes.HEADING;
            });
            this.isApNavModeActive = this.apFmaData.map(fmaData => {
                if (fmaData === null) {
                    return false;
                }
                return GarminHeadingSyncManager.AP_NAV_MODES.has(fmaData.lateralActive);
            });
            this.isAttitudeDataValid = msfsSdk.ConsumerValue.create(null, false);
            this.isHeadingDataValid = msfsSdk.ConsumerValue.create(null, false);
            this.bank = msfsSdk.ConsumerValue.create(null, 0);
            this.heading = msfsSdk.ConsumerValue.create(null, 0);
            this.lastSetHeadingValue = 0;
            this.isTurnHeadingAdjustActive = false;
            this.isHeadingSyncModeActive = false;
            this.isAlive = true;
            this.isInit = false;
            this.isPaused = true;
            this.supportTurnHeadingAdjust = (_a = options === null || options === void 0 ? void 0 : options.supportTurnHeadingAdjust) !== null && _a !== void 0 ? _a : false;
            this.supportHeadingSyncMode = (_b = options === null || options === void 0 ? void 0 : options.supportHeadingSyncMode) !== null && _b !== void 0 ? _b : false;
            this.turnHeadingAdjustBankThreshold = (_c = options === null || options === void 0 ? void 0 : options.turnHeadingAdjustBankThreshold) !== null && _c !== void 0 ? _c : GarminHeadingSyncManager.DEFAULT_TURN_HDG_ADJUST_BANK_THRESHOLD;
            this.headingSyncHEvent = (_d = options === null || options === void 0 ? void 0 : options.headingSyncHEvent) !== null && _d !== void 0 ? _d : GarminHeadingSyncManager.DEFAULT_HDG_SYNC_H_EVENT;
            this.ahrsIndex = msfsSdk.SubscribableUtils.toSubscribable(ahrsIndex, true);
            this.keyEventManagerReadyPromise = new Promise((resolve, reject) => {
                this.keyEventManagerReadyPromiseReject = reject;
                msfsSdk.KeyEventManager.getManager(this.bus).then(manager => {
                    if (this.isAlive) {
                        this.keyEventManagerReadyPromiseReject = undefined;
                        this.keyEventManager = manager;
                        resolve(manager);
                    }
                });
            });
            // Initialize published event bus topics.
            this.publisher.pub('hdg_sync_turn_adjust_active', false, true, true);
            this.publisher.pub('hdg_sync_mode_active', false, true, true);
        }
        /**
         * Waits for this manager's key event manager to be ready.
         * @returns A Promise which will be fulfilled when this manager's key event manager is ready, or rejected if this
         * manager is destroyed before then.
         */
        awaitKeyEventManagerReady() {
            return this.keyEventManagerReadyPromise;
        }
        /**
         * Initializes this manager. Once this manager is initialized, it will automatically adjust the autopilot's selected
         * heading in response to heading sync H events and whether turn heading adjustment or heading sync mode are active.
         * @throws Error if this manager has been destroyed.
         */
        async init() {
            if (!this.isAlive) {
                throw new Error('GarminHeadingSyncManager: cannot initialize a dead manager');
            }
            const keyEventManager = await this.awaitKeyEventManagerReady();
            if (this.isInit) {
                return;
            }
            this.isInit = true;
            this.apFmaData.setConsumer(this.bus.getSubscriber().on('fma_data'));
            keyEventManager.interceptKey('HEADING_BUG_INC', true);
            keyEventManager.interceptKey('HEADING_BUG_DEC', true);
            keyEventManager.interceptKey('HEADING_BUG_SET', true);
            keyEventManager.interceptKey('AP_HEADING_BUG_SET_EX1', true);
            const sub = this.bus.getSubscriber();
            this.ahrsIndexSub = this.ahrsIndex.sub(index => {
                if (index <= 0) {
                    this.isHeadingDataValid.setConsumer(null);
                    this.heading.setConsumer(null);
                    if (this.supportTurnHeadingAdjust) {
                        this.isAttitudeDataValid.setConsumer(null);
                        this.bank.setConsumer(null);
                    }
                }
                else {
                    this.isHeadingDataValid.setConsumer(sub.on(`ahrs_heading_data_valid_${index}`));
                    this.heading.setConsumer(sub.on(`ahrs_hdg_deg_${index}`));
                    if (this.supportTurnHeadingAdjust) {
                        this.isAttitudeDataValid.setConsumer(sub.on(`ahrs_attitude_data_valid_${index}`));
                        this.bank.setConsumer(sub.on(`ahrs_roll_deg_${index}`));
                    }
                }
            }, true);
            this.keyEventSub = sub.on('key_intercept').handle(this.onKeyIntercepted.bind(this), this.isPaused);
            this.hEventSub = sub.on('hEvent').handle(this.onHEvent.bind(this), this.isPaused);
            this.updateSub = sub.on('realTime').handle(this.update.bind(this), this.isPaused);
        }
        /**
         * Resumes this manager. Once resumed, this manager will automatically adjust the autopilot's selected heading in
         * response to heading sync H events and whether turn heading adjustment or heading sync mode are active.
         * @throws Error if this manager has been destroyed.
         */
        resume() {
            var _a, _b, _c;
            if (!this.isAlive) {
                throw new Error('GarminHeadingSyncManager: cannot resume a dead manager');
            }
            if (!this.isPaused) {
                return;
            }
            this.apFmaData.resume();
            this.isAttitudeDataValid.resume();
            this.isHeadingDataValid.resume();
            this.bank.resume();
            this.heading.resume();
            this.isPaused = false;
            (_a = this.keyEventSub) === null || _a === void 0 ? void 0 : _a.resume();
            (_b = this.hEventSub) === null || _b === void 0 ? void 0 : _b.resume();
            (_c = this.updateSub) === null || _c === void 0 ? void 0 : _c.resume(true);
        }
        /**
         * Pauses this manager. Once paused, this manager will no longer automatically adjust the autopilot's selected
         * heading.
         * @throws Error if this manager has been destroyed.
         */
        pause() {
            var _a, _b, _c;
            if (!this.isAlive) {
                throw new Error('GarminHeadingSyncManager: cannot pause a dead manager');
            }
            if (this.isPaused) {
                return;
            }
            this.isPaused = true;
            (_a = this.keyEventSub) === null || _a === void 0 ? void 0 : _a.pause();
            (_b = this.hEventSub) === null || _b === void 0 ? void 0 : _b.pause();
            (_c = this.updateSub) === null || _c === void 0 ? void 0 : _c.pause();
            this.apFmaData.pause();
            this.isAttitudeDataValid.pause();
            this.isHeadingDataValid.pause();
            this.bank.pause();
            this.heading.pause();
        }
        /**
         * Resets this manager. Deactivates both turn heading adjustment and heading sync mode if they were active.
         */
        reset() {
            this.setTurnHeadingAdjustActive(false);
            this.setHeadingSyncModeActive(false);
        }
        /**
         * Responds to a key event intercept.
         * @param data Data describing the intercepted key event.
         */
        onKeyIntercepted(data) {
            switch (data.key) {
                case 'HEADING_BUG_INC':
                case 'HEADING_BUG_DEC':
                case 'HEADING_BUG_SET':
                case 'AP_HEADING_BUG_SET_EX1':
                    this.setTurnHeadingAdjustActive(false);
                    this.setHeadingSyncModeActive(false);
                    this.publisher.pub('hdg_sync_manual_select', undefined, true, false);
                    break;
            }
        }
        /**
         * Responds to when an H event is triggered.
         * @param hEvent The triggered H event.
         */
        onHEvent(hEvent) {
            if (hEvent === this.headingSyncHEvent) {
                if (this.isHeadingSyncModeActive) {
                    this.setHeadingSyncModeActive(false);
                }
                else {
                    if (this.ahrsIndex.get() > 0 && this.isHeadingDataValid.get()) {
                        this.setSelectedHeading(Math.round(this.heading.get()));
                    }
                    this.publisher.pub('hdg_sync_manual_select', undefined, true, false);
                    if (this.supportHeadingSyncMode && this.isApNavModeActive.get()) {
                        this.setHeadingSyncModeActive(true);
                    }
                    else if (this.supportTurnHeadingAdjust && this.isApHdgModeActive.get()) {
                        this.setTurnHeadingAdjustActive(true);
                    }
                }
            }
        }
        /**
         * Sets the autopilot's selected heading, in degrees.
         * @param heading The selected heading to set, in degrees.
         */
        setSelectedHeading(heading) {
            this.keyEventManager.triggerKey('HEADING_BUG_SET', true, this.lastSetHeadingValue = heading, 1);
        }
        /**
         * Checks whether turn heading adjustment can be activated.
         * @returns Whether turn heading adjustment  can be activated.
         */
        canActivateTurnHeadingAdjust() {
            return this.isApHdgModeActive.get()
                && this.ahrsIndex.get() > 0
                && this.isAttitudeDataValid.get()
                && this.isHeadingDataValid.get()
                && Math.abs(this.bank.get()) >= this.turnHeadingAdjustBankThreshold;
        }
        /**
         * Checks whether heading sync mode can be activated.
         * @returns Whether heading sync mode can be activated.
         */
        canActivateHeadingSyncMode() {
            return this.isApNavModeActive.get()
                && this.ahrsIndex.get() > 0
                && this.isHeadingDataValid.get();
        }
        /**
         * Sets whether turn heading adjustment is active. This method will not activate turn heading adjustment if current
         * conditions do not allow for its activation. If this manager does not support turn heading adjustment, then this
         * method does nothing.
         * @param active Whether to activate turn heading adjustment.
         */
        setTurnHeadingAdjustActive(active) {
            if (!this.supportTurnHeadingAdjust || active === this.isTurnHeadingAdjustActive) {
                return;
            }
            if (active && !this.canActivateTurnHeadingAdjust()) {
                return;
            }
            this.isTurnHeadingAdjustActive = active;
            this.publisher.pub('hdg_sync_turn_adjust_active', active, true, true);
        }
        /**
         * Sets whether heading sync mode is active. This method will not activate heading sync mode if current conditions do
         * not allow for its activation. If this manager does not support heading sync mode, then this method does nothing.
         * @param active Whether to activate heading sync mode.
         */
        setHeadingSyncModeActive(active) {
            if (!this.supportHeadingSyncMode || active === this.isHeadingSyncModeActive) {
                return;
            }
            if (active && !this.canActivateHeadingSyncMode()) {
                return;
            }
            this.isHeadingSyncModeActive = active;
            this.publisher.pub('hdg_sync_mode_active', active, true, true);
        }
        /**
         * Updates this manager.
         */
        update() {
            if (this.isTurnHeadingAdjustActive && !this.canActivateTurnHeadingAdjust()) {
                this.setTurnHeadingAdjustActive(false);
            }
            if (this.isHeadingSyncModeActive && !this.canActivateHeadingSyncMode()) {
                this.setHeadingSyncModeActive(false);
            }
            if (this.isTurnHeadingAdjustActive || this.isHeadingSyncModeActive) {
                // Sync selected heading to current heading if current heading differs from the selected heading
                // by at least 0.7 degrees (this provides 0.2 degrees of hysteresis after rounding).
                const currentHeading = this.heading.get();
                if (Math.abs(currentHeading - this.lastSetHeadingValue) >= 0.7) {
                    this.setSelectedHeading(Math.round(currentHeading));
                }
            }
        }
        /**
         * Destroys this manager.
         */
        destroy() {
            var _a, _b, _c, _d, _e;
            this.isAlive = false;
            (_a = this.keyEventManagerReadyPromiseReject) === null || _a === void 0 ? void 0 : _a.call(this, 'GarminHeadingSyncManager: handler was destroyed');
            this.apFmaData.destroy();
            this.isAttitudeDataValid.destroy();
            this.isHeadingDataValid.destroy();
            this.bank.destroy();
            (_b = this.keyEventSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.hEventSub) === null || _c === void 0 ? void 0 : _c.destroy();
            (_d = this.ahrsIndexSub) === null || _d === void 0 ? void 0 : _d.destroy();
            (_e = this.updateSub) === null || _e === void 0 ? void 0 : _e.destroy();
        }
    }
    GarminHeadingSyncManager.AP_NAV_MODES = new Set([
        msfsSdk.APLateralModes.NAV,
        msfsSdk.APLateralModes.VOR,
        msfsSdk.APLateralModes.LOC,
        msfsSdk.APLateralModes.BC,
        msfsSdk.APLateralModes.GPSS
    ]);
    GarminHeadingSyncManager.DEFAULT_TURN_HDG_ADJUST_BANK_THRESHOLD = 6;
    GarminHeadingSyncManager.DEFAULT_HDG_SYNC_H_EVENT = 'Garmin_AP_HDG_Sync';

    /* eslint-disable @typescript-eslint/no-non-null-assertion */
    /**
     * A manager which handles activation and deactivation of Low Bank Mode for Garmin autopilots. The manager ensures that
     * Low Bank Mode is active only when supported by the autopilot's active lateral mode. Optionally, it will also
     * automatically toggle Low Bank Mode in response to changes in indicated altitude.
     */
    class GarminLowBankManager {
        /**
         * Creates a new instance of GarminLowBankManager. The new manager is created as uninitialized and with auto-toggle
         * paused.
         * @param bus The event bus.
         * @param activateAltitude The indicated altitude threshold, in feet, for automatic activation of low-bank mode. If
         * defined and auto-toggle is supported, this manager will automatically activate low-bank mode when the airplane
         * climbs from below to above this altitude.
         * @param deactivateAltitude The indicated altitude threshold, in feet, for automatic deactivation of low-bank mode.
         * If defined and auto-toggle is supported, this manager will automatically deactivate low-bank mode when the
         * airplane descends from above to below this altitude.
         * @param supportedAdcIndexes An iterable of ADC system indexes from which the manager can source indicated altitude
         * data. Required for auto-toggle behavior. The manager will source data from the first ADC system (in the order
         * returned by the iterable) whose state is equal to `AvionicsSystemState.On`.
         * @param autoToggleBankLimit The bank angle, in degrees, above which auto-toggle is disabled.
         * @param supportedAhrsIndexes An iterable of AHRS system indexes from which the manager can source bank angle data.
         * Required for auto-toggle behavior to be limited by aircraft bank. The manager will source data from the first AHRS
         * system (in the order returned by the iterable) whose state is equal to `AvionicsSystemState.On`.
         * @throws Error if `activateAltitude` is less than `deactivateAltitude`.
         */
        constructor(bus, activateAltitude, deactivateAltitude, supportedAdcIndexes, autoToggleBankLimit = 0, supportedAhrsIndexes) {
            this.bus = bus;
            this.activateAltitude = activateAltitude;
            this.deactivateAltitude = deactivateAltitude;
            this.autoToggleBankLimit = autoToggleBankLimit;
            this.apFmaData = msfsSdk.ConsumerSubject.create(null, null);
            this.isLowBankSupported = this.apFmaData.map(fmaData => {
                if (fmaData === null) {
                    return false;
                }
                return GarminLowBankManager.LOW_BANK_SUPPORTED_MODES.has(fmaData.lateralActive);
            });
            this.adcStates = [];
            this.ahrsStates = [];
            this.indicatedAlt = msfsSdk.ConsumerSubject.create(null, 0);
            this.bank = msfsSdk.ConsumerValue.create(null, 0);
            this.isActivateArmed = false;
            this.isDeactivateArmed = false;
            this.isAlive = true;
            this.isInit = false;
            this.isAutoTogglePaused = true;
            // eslint-disable-next-line jsdoc/require-jsdoc
            this.keyEventManagerReadyPromises = [];
            if (activateAltitude !== undefined && deactivateAltitude !== undefined && activateAltitude < deactivateAltitude) {
                throw new Error(`GarminLowBankManager: activateAltitude (${activateAltitude}) is lower than deactivateAltitude (${deactivateAltitude})`);
            }
            // Remove duplicates
            this.adcIndexes = Array.from(new Set(supportedAdcIndexes));
            this.ahrsIndexes = Array.from(new Set(supportedAhrsIndexes));
            msfsSdk.KeyEventManager.getManager(this.bus).then(manager => {
                this.keyEventManager = manager;
                while (this.isAlive && this.keyEventManagerReadyPromises.length > 0) {
                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                    this.keyEventManagerReadyPromises.shift().resolve();
                }
            });
        }
        /**
         * Waits for this handler's key event manager to be ready.
         * @returns A Promise which will be fulfilled when this handler's key event manager is ready, or rejected if this
         * handler is destroyed before then.
         */
        awaitKeyEventManagerReady() {
            if (this.keyEventManager !== undefined) {
                return Promise.resolve();
            }
            return new Promise((resolve, reject) => { this.keyEventManagerReadyPromises.push({ resolve, reject }); });
        }
        /**
         * Initializes this manager. Once this manager is initialized, it will ensure that Low Bank Mode is active only when
         * it is supported by the autopilot's active lateral mode. Additionally, it will automatically toggle Low Bank Mode
         * in response to changes in indicated altitude if the function is supported and resumed.
         * @throws Error if this manager has been destroyed.
         */
        async init() {
            if (!this.isAlive) {
                throw new Error('GarminLowBankManager: cannot initialize a dead manager');
            }
            await this.awaitKeyEventManagerReady();
            if (this.isInit) {
                return;
            }
            this.isInit = true;
            this.apFmaData.setConsumer(this.bus.getSubscriber().on('fma_data'));
            this.keyEventManager.interceptKey('AP_MAX_BANK_SET', false);
            this.keyEventManager.interceptKey('AP_MAX_BANK_INC', false);
            this.keyEventManager.interceptKey('AP_MAX_BANK_DEC', false);
            // Wait one frame for the key intercepts to be established.
            await msfsSdk.Wait.awaitDelay(0);
            const sub = this.bus.getSubscriber();
            this.keyEventSub = sub.on('key_intercept').handle(this.onKeyIntercepted.bind(this));
            this.isLowBankSupported.sub(this.onLowBankSupportedChanged.bind(this), true);
            if (this.adcIndexes.length > 0 && (this.activateAltitude !== undefined || this.deactivateAltitude !== undefined)) {
                // Initialize bank data.
                if (this.ahrsIndexes.length > 0 && this.autoToggleBankLimit > 0) {
                    for (let i = 0; i < this.ahrsIndexes.length; i++) {
                        this.ahrsStates[i] = msfsSdk.ConsumerSubject.create(sub.on(`ahrs_state_${this.ahrsIndexes[i]}`), { previous: undefined, current: msfsSdk.AvionicsSystemState.Off });
                    }
                    this.ahrsIndex = msfsSdk.MappedSubject.create(GarminLowBankManager.selectSystemIndex.bind(undefined, this.ahrsIndexes), ...this.ahrsStates);
                    this.ahrsIndex.sub(index => {
                        if (index <= 0) {
                            this.bank.setConsumer(null);
                        }
                        else {
                            this.bank.setConsumer(sub.on(`ahrs_roll_deg_${index}`));
                        }
                    }, true);
                }
                // Initialize indicated altitude data.
                for (let i = 0; i < this.adcIndexes.length; i++) {
                    this.adcStates[i] = msfsSdk.ConsumerSubject.create(sub.on(`adc_state_${this.adcIndexes[i]}`), { previous: undefined, current: msfsSdk.AvionicsSystemState.Off });
                }
                this.adcIndex = msfsSdk.MappedSubject.create(GarminLowBankManager.selectSystemIndex.bind(undefined, this.adcIndexes), ...this.adcStates);
                this.adcIndex.sub(index => {
                    var _a, _b;
                    if (index <= 0) {
                        (_a = this.altitudeSub) === null || _a === void 0 ? void 0 : _a.pause();
                        this.indicatedAlt.setConsumer(null);
                        this.isActivateArmed = false;
                        this.isDeactivateArmed = false;
                    }
                    else {
                        this.indicatedAlt.setConsumer(sub.on(`adc_indicated_alt_${index}`));
                        if (!this.isAutoTogglePaused) {
                            (_b = this.altitudeSub) === null || _b === void 0 ? void 0 : _b.resume(true);
                        }
                    }
                }, true);
                this.altitudeSub = this.indicatedAlt.sub(this.onAltitudeChanged.bind(this), !this.isAutoTogglePaused, this.isAutoTogglePaused);
            }
        }
        /**
         * Resumes this manager's auto-toggle behavior. Once resumed, this manager will automatically activate/deactivate
         * Low Bank Mode based on changes in indicated altitude. If this manager has not been initialized, auto-toggle
         * behavior will begin when initialization is complete.
         * @throws Error if this manager has been destroyed.
         */
        resumeAutoToggle() {
            if (!this.isAlive) {
                throw new Error('GarminLowBankManager: cannot resume auto-toggle on a dead manager');
            }
            if (!this.isAutoTogglePaused) {
                return;
            }
            this.isAutoTogglePaused = false;
            if (this.altitudeSub !== undefined && this.adcIndex !== undefined && this.adcIndex.get() > 0) {
                this.altitudeSub.resume(true);
            }
        }
        /**
         * Pauses this manager's auto-toggle behavior. Once paused, this manager will no longer automatically
         * activate/deactivate Low Bank Mode until auto-toggle is resumed.
         * @throws Error if this manager has been destroyed.
         */
        pauseAutoToggle() {
            var _a;
            if (!this.isAlive) {
                throw new Error('GarminLowBankManager: cannot pause auto-toggle on a dead manager');
            }
            if (this.isAutoTogglePaused) {
                return;
            }
            this.isAutoTogglePaused = true;
            (_a = this.altitudeSub) === null || _a === void 0 ? void 0 : _a.pause();
            this.isActivateArmed = false;
            this.isDeactivateArmed = false;
        }
        /**
         * Sets the active state of Low Bank Mode.
         * @param active Whether to activate Low Bank Mode.
         */
        setLowBankActive(active) {
            if (!this.isLowBankSupported.get() && active) {
                return;
            }
            this.keyEventManager.triggerKey('AP_MAX_BANK_SET', true, active ? 1 : 0);
        }
        /**
         * Responds to when Low Bank Mode support changes.
         * @param isLowBankSupported Whether Low Bank Mode is supported.
         */
        onLowBankSupportedChanged(isLowBankSupported) {
            if (!isLowBankSupported) {
                this.setLowBankActive(false);
            }
        }
        /**
         * Responds to when the indicated altitude changes.
         * @param altitude The indicated altitude, in feet.
         */
        onAltitudeChanged(altitude) {
            if (this.activateAltitude !== undefined) {
                if (altitude < this.activateAltitude) {
                    this.isActivateArmed = true;
                }
                else {
                    if (this.isActivateArmed) {
                        if (this.ahrsIndex === undefined || (this.ahrsIndex.get() > 0 && Math.abs(this.bank.get()) <= this.autoToggleBankLimit)) {
                            this.setLowBankActive(true);
                        }
                        this.isActivateArmed = false;
                    }
                }
            }
            if (this.deactivateAltitude !== undefined) {
                if (altitude >= this.deactivateAltitude) {
                    this.isDeactivateArmed = true;
                }
                else {
                    if (this.isDeactivateArmed) {
                        if (this.ahrsIndex === undefined || (this.ahrsIndex.get() > 0 && Math.abs(this.bank.get()) <= this.autoToggleBankLimit)) {
                            this.setLowBankActive(false);
                        }
                        this.isDeactivateArmed = false;
                    }
                }
            }
        }
        /**
         * Handles a key event intercept.
         * @param data Data describing the intercepted key event.
         */
        onKeyIntercepted(data) {
            var _a;
            switch (data.key) {
                case 'AP_MAX_BANK_SET': {
                    const value = (_a = data.value0) !== null && _a !== void 0 ? _a : 0;
                    if (value < 2) {
                        this.setLowBankActive(value === 1);
                    }
                    break;
                }
                case 'AP_MAX_BANK_INC':
                case 'AP_MAX_BANK_DEC': {
                    this.setLowBankActive(SimVar.GetSimVarValue('AUTOPILOT MAX BANK ID', msfsSdk.SimVarValueType.Number) !== 1);
                    break;
                }
            }
        }
        /**
         * Destroys this manager.
         */
        destroy() {
            var _a;
            this.isAlive = false;
            this.keyEventManagerReadyPromises.forEach(promise => { promise.reject('GarminLowBankManager: handler was destroyed'); });
            this.apFmaData.destroy();
            this.adcStates.forEach(sub => { sub.destroy(); });
            this.ahrsStates.forEach(sub => { sub.destroy(); });
            this.indicatedAlt.destroy();
            this.bank.destroy();
            (_a = this.keyEventSub) === null || _a === void 0 ? void 0 : _a.destroy();
        }
        /**
         * Selects the index of the first system in an array whose state is equal to `AvionicsSystemState.On`.
         * @param indexes The indexes of the systems from which to select.
         * @param states The states of the systems from which to select.
         * @returns The index of the first system in the specified array whose state is equal to `AvionicsSystemState.On`.
         */
        static selectSystemIndex(indexes, states) {
            for (let i = 0; i < states.length; i++) {
                const state = states[i];
                if (state.current === undefined || state.current === msfsSdk.AvionicsSystemState.On) {
                    return indexes[i];
                }
            }
            return -1;
        }
    }
    GarminLowBankManager.LOW_BANK_SUPPORTED_MODES = new Set([
        msfsSdk.APLateralModes.HEADING,
        msfsSdk.APLateralModes.NAV,
        msfsSdk.APLateralModes.VOR,
        msfsSdk.APLateralModes.LOC,
        msfsSdk.APLateralModes.BC,
        msfsSdk.APLateralModes.GPSS
    ]);

    /**
     * A store which keeps track of the speed constraints in the active flight plan.
     */
    class GarminSpeedConstraintStore {
        /**
         * Creates the store.
         * @param bus The Event Bus.
         * @param flightPlanner The Flight Planner.
         */
        constructor(bus, flightPlanner) {
            this.bus = bus;
            this.flightPlanner = flightPlanner;
            this.speedConstraints = [];
            this.activePlanIndex = 0;
            this.planChanged = false;
            this.iterator = new msfsSdk.FlightPlanLegIterator();
            this.onPlanIndexChanged = (e) => {
                this.activePlanIndex = e.planIndex;
                this.planChanged = true;
            };
            this.onPlanCalculated = (e) => {
                if (e.planIndex === this.activePlanIndex && this.planChanged) {
                    const lateralPlan = this.flightPlanner.getActiveFlightPlan();
                    this.speedConstraints.length = 0;
                    this.iterator.iterateForward(lateralPlan, (cursor) => {
                        const isDirectToLeg = msfsSdk.BitFlags.isAny(cursor.legDefinition.flags, msfsSdk.LegDefinitionFlags.DirectTo)
                            && lateralPlan.directToData.segmentIndex === cursor.segment.segmentIndex
                            && lateralPlan.directToData.segmentLegIndex === cursor.legIndex - FmsUtils.DTO_LEG_OFFSET;
                        // If the leg is a direct-to leg with an associated direct-to target leg, skip it since its constraint is
                        // duplicated from that of the target leg.
                        if (cursor.legDefinition.verticalData.speedDesc !== msfsSdk.SpeedRestrictionType.Unused && !isDirectToLeg) {
                            const isMissedApproach = msfsSdk.BitFlags.isAny(cursor.legDefinition.flags, msfsSdk.LegDefinitionFlags.MissedApproach);
                            const isCursorDepartureOrMissedApproach = cursor.segment.segmentType === msfsSdk.FlightPlanSegmentType.Departure || isMissedApproach;
                            // If the constraint leg is a direct-to target, shift the leg index to that of the direct-to leg.
                            const isDirectToTarget = lateralPlan.directToData.segmentIndex === cursor.segment.segmentIndex
                                && lateralPlan.directToData.segmentLegIndex === cursor.legIndex;
                            const constraint = {
                                globalLegIndex: cursor.index + (isDirectToTarget ? FmsUtils.DTO_LEG_OFFSET : 0),
                                flightPhase: isCursorDepartureOrMissedApproach ? msfsSdk.VerticalFlightPhase.Climb : msfsSdk.VerticalFlightPhase.Descent,
                                speedConstraint: {
                                    speedDesc: cursor.legDefinition.verticalData.speedDesc,
                                    speed: cursor.legDefinition.verticalData.speed,
                                    speedUnit: cursor.legDefinition.verticalData.speedUnit
                                },
                                isMissedApproach
                            };
                            this.speedConstraints.push(constraint);
                        }
                    });
                    this.planChanged = false;
                }
            };
            this.flightPlanner.onEvent('fplCopied').handle(e => this.onPlanChanged(e.targetPlanIndex));
            this.flightPlanner.onEvent('fplLoaded').handle(e => this.onPlanChanged(e.planIndex));
            this.flightPlanner.onEvent('fplLegChange').handle(e => this.onPlanChanged(e.planIndex));
            this.flightPlanner.onEvent('fplSegmentChange').handle(e => this.onPlanChanged(e.planIndex));
            this.flightPlanner.onEvent('fplIndexChanged').handle(this.onPlanIndexChanged);
            this.flightPlanner.onEvent('fplCalculated').handle(this.onPlanCalculated);
        }
        /**
         * Sets the planChanged flag if the plan change is for the active plan index.
         * @param planIndex The plan index from the event.
         */
        onPlanChanged(planIndex) {
            if (planIndex === this.activePlanIndex) {
                this.planChanged = true;
            }
        }
        /**
         * Gets the speed constraint in effect for a given flight plan leg and vertical flight phase.
         * @param globalLegIndex The global index of the flight plan leg for which to get the speed constraint in effect.
         * @param flightPhase The vertical flight phase for which to get the speed constraint in effect.
         * @returns The speed constraint in effect for the specified flight plan leg and vertical flight phase, or
         * `undefined` if there is no speed constraint in effect.
         */
        getCurrentSpeedConstraint(globalLegIndex, flightPhase) {
            return this.speedConstraints[this.getCurrentSpeedConstraintIndex(globalLegIndex, flightPhase)];
        }
        /**
         * Gets the next speed constraint to take effect for a given flight plan leg and vertical flight phase.
         * @param globalLegIndex The global index of the flight plan leg for which to get the speed constraint in effect.
         * @param flightPhase The vertical flight phase for which to get the speed constraint in effect.
         * @returns The next speed constraint to take effect for the specified flight plan leg and vertical flight phase,
         * or `undefined` if there is no speed constraint in effect.
         */
        getNextSpeedConstraint(globalLegIndex, flightPhase) {
            const currentIndex = this.getCurrentSpeedConstraintIndex(globalLegIndex, flightPhase);
            if (currentIndex < 0) {
                if (flightPhase === msfsSdk.VerticalFlightPhase.Climb) {
                    // If there is no current constraint in effect during climb, there can be no next climb constraint because
                    // either we have sequenced all climb constraints or there is at least one non-climb constraint to sequence
                    // before the next climb constraint.
                    return undefined;
                }
                else {
                    // If there is no current constraint in effect during descent, it is still possible that the next upcoming
                    // constraint to be sequenced is the next descent constraint. This constraint, if it exists, is located at the
                    // index where the current constraint would be located if it existed, so we need to check if the constraint at
                    // that index is a descent constraint and has yet to be sequenced.
                    const constraint = this.speedConstraints[-currentIndex - 1];
                    if (constraint !== undefined && flightPhase === constraint.flightPhase && constraint.globalLegIndex >= globalLegIndex) {
                        return constraint;
                    }
                    else {
                        return undefined;
                    }
                }
            }
            const isMapr = this.speedConstraints[currentIndex].isMissedApproach;
            const constraint = this.speedConstraints[currentIndex + 1];
            if (constraint !== undefined && flightPhase === constraint.flightPhase && isMapr === constraint.isMissedApproach) {
                return constraint;
            }
            return undefined;
        }
        /**
         * Gets the index of the speed constraint in this store in effect for a given flight plan leg and vertical flight
         * phase.
         * @param globalLegIndex The global index of the flight plan leg for which to get the speed constraint in effect.
         * @param flightPhase The vertical flight phase for which to get the speed constraint in effect.
         * @returns The index of the speed constraint in this store in effect for the specified flight plan leg and vertical
         * flight phase. If there is no speed constraint in effect, `-(i + 1)` is returned instead, where `i` is the index
         * at which the constraint would be located if it existed.
         */
        getCurrentSpeedConstraintIndex(globalLegIndex, flightPhase) {
            var _a, _b;
            if (flightPhase === msfsSdk.VerticalFlightPhase.Climb) {
                const inMissedApproach = this.flightPlanner.hasActiveFlightPlan()
                    && msfsSdk.BitFlags.isAll((_b = (_a = this.flightPlanner.getActiveFlightPlan().tryGetLeg(globalLegIndex)) === null || _a === void 0 ? void 0 : _a.flags) !== null && _b !== void 0 ? _b : 0, msfsSdk.LegDefinitionFlags.MissedApproach);
                // During climb, the next upcoming climb speed constraint to be sequenced is in effect.
                for (let i = 0; i < this.speedConstraints.length; i++) {
                    const constraint = this.speedConstraints[i];
                    // Speed constraints in the missed approach are not active until we are in the missed approach.
                    if (constraint.isMissedApproach && !inMissedApproach) {
                        return -(i + 1);
                    }
                    if (constraint.globalLegIndex >= globalLegIndex) {
                        return flightPhase === constraint.flightPhase ? i : -(i + 1);
                    }
                }
                return -(this.speedConstraints.length + 1);
            }
            else {
                // During descent, the last sequenced descent speed constraint is in effect.
                for (let i = this.speedConstraints.length - 1; i >= 0; i--) {
                    const constraint = this.speedConstraints[i];
                    if (constraint.globalLegIndex < globalLegIndex) {
                        return flightPhase === constraint.flightPhase ? i : -(i + 2);
                    }
                }
                return -1;
            }
        }
    }

    /**
     * A Garmin VNAV Manager.
     * @deprecated
     */
    class GarminVNavManager {
        /**
         * Creates an instance of the Garmin VNAV Manager.
         * @param bus The event bus to use with this instance.
         * @param flightPlanner The flight planner to use with this instance.
         * @param calculator The VNAV path calculator to use with this instance.
         * @param apValues are the autopilot ap values.
         * @param primaryPlanIndex The index of the flightplan to follow vertical guidance from.
         * @param hasNonPathVnav Whether this VNav Director provides non-path climb and descent restriction adherence (false by default).
         * @param guidanceEndsAtFaf Whether this VNav Director terminates vertical guidance at the FAF (true by default).
         */
        constructor(bus, flightPlanner, calculator, apValues, primaryPlanIndex, hasNonPathVnav = false, guidanceEndsAtFaf = true) {
            this.bus = bus;
            this.flightPlanner = flightPlanner;
            this.calculator = calculator;
            this.apValues = apValues;
            this.primaryPlanIndex = primaryPlanIndex;
            this.hasNonPathVnav = hasNonPathVnav;
            this.guidanceEndsAtFaf = guidanceEndsAtFaf;
            this.state = msfsSdk.VNavState.Disabled;
            this.pathMode = msfsSdk.VNavPathMode.None;
            this.planePos = new msfsSdk.GeoPoint(0, 0);
            this.currentAltitude = 0;
            this.currentGpsAltitude = 0;
            this.preselectedAltitude = 0;
            this.currentGroundSpeed = 0;
            this.currentVS = 0;
            this.approachDetails = msfsSdk.ConsumerSubject.create(null, {
                isLoaded: false,
                type: ApproachType.APPROACH_TYPE_UNKNOWN,
                isRnpAr: false,
                bestRnavType: msfsSdk.RnavTypeFlags.None,
                rnavTypeFlags: msfsSdk.RnavTypeFlags.None,
                isCircling: false,
                isVtf: false,
                referenceFacility: null,
                runway: null
            }, FmsUtils.approachDetailsEquals);
            this.gpAvailable = msfsSdk.ConsumerSubject.create(null, false);
            this.vnavUnavailable = msfsSdk.Subject.create(false);
            this.cdiSource = { type: msfsSdk.NavSourceType.Nav, index: 0 };
            this.capturedAltitude = 0;
            this.isAltCaptured = false;
            this.awaitingAltCap = -1;
            this.awaitingRearm = -1;
            /** The leg distance from the current leg to the constraint leg, not including the distance from ppos to the current leg target. */
            this.constraintDistance = -1;
            this.vnavNextConstraintAltitude = msfsSdk.Subject.create(undefined);
            /** The active leg altitude from VNAV in Meters. */
            this.vnavNextLegTargetAltitude = msfsSdk.Subject.create(undefined);
            this.lpvDeviation = msfsSdk.Subject.create(0);
            this.glidepathCalculator = new msfsSdk.GlidePathCalculator(this.bus, this.flightPlanner, this.primaryPlanIndex);
            this.todBodDetails = {
                todLegIndex: -1,
                bodLegIndex: -1,
                todLegDistance: 0,
                distanceFromTod: 0,
                distanceFromBod: 0,
                currentConstraintLegIndex: -1
            };
            this.todBodDetailsSub = msfsSdk.ObjectSubject.create(Object.assign({}, this.todBodDetails));
            const lnav = this.bus.getSubscriber();
            this.lnavLegIndex = msfsSdk.ConsumerSubject.create(lnav.on('lnav_tracked_leg_index'), 0);
            this.lnavLegDistanceAlong = msfsSdk.ConsumerSubject.create(lnav.on('lnav_leg_distance_along'), 0);
            this.bus.getSubscriber().on('ap_altitude_selected').handle(selected => this.preselectedAltitude = selected);
            this.bus.getSubscriber().on('indicated_alt').whenChangedBy(1).handle(alt => this.currentAltitude = alt);
            this.bus.getSubscriber().on('vertical_speed').whenChangedBy(1).handle(vs => this.currentVS = vs);
            const gnss = this.bus.getSubscriber();
            gnss.on('gps-position').handle(lla => {
                this.planePos.set(lla.lat, lla.long);
                this.currentGpsAltitude = msfsSdk.UnitType.METER.convertTo(lla.alt, msfsSdk.UnitType.FOOT);
            });
            gnss.on('ground_speed').handle(gs => this.currentGroundSpeed = gs);
            this.approachDetails.setConsumer(bus.getSubscriber().on('fms_approach_details'));
            this.gpAvailable.setConsumer(bus.getSubscriber().on('approach_supports_gp'));
            this.bus.getSubscriber().on('vnav_set_state').handle(d => {
                if (d) {
                    this.setState(msfsSdk.VNavState.Enabled_Inactive);
                }
                else {
                    this.setState(msfsSdk.VNavState.Disabled);
                }
            });
            this.apValues.verticalActive.sub(mode => {
                if (mode === msfsSdk.APVerticalModes.ALT && this.awaitingAltCap !== -1) {
                    this.awaitingRearm = this.awaitingAltCap;
                    this.awaitingAltCap = -1;
                }
                if (this.awaitingRearm > -1 && mode !== msfsSdk.APVerticalModes.ALT && mode !== msfsSdk.APVerticalModes.CAP) {
                    this.awaitingRearm = -1;
                    this.awaitingAltCap = -1;
                }
                if (mode === msfsSdk.APVerticalModes.GS || mode === msfsSdk.APVerticalModes.GP) {
                    this.tryDeactivate(msfsSdk.APVerticalModes.NONE);
                }
            });
            this.apValues.lateralActive.sub(mode => {
                if (mode === msfsSdk.APLateralModes.LOC && this.pathMode === msfsSdk.VNavPathMode.PathArmed) {
                    this.awaitingAltCap = -1;
                    this.awaitingRearm = -1;
                    this.tryDeactivate(msfsSdk.APVerticalModes.NONE);
                }
            });
            this.vnavNextConstraintAltitude.sub((v) => {
                SimVar.SetSimVarValue(msfsSdk.VNavVars.NextConstraintAltitude, msfsSdk.SimVarValueType.Feet, v === undefined ? -1 : msfsSdk.UnitType.METER.convertTo(v, msfsSdk.UnitType.FOOT));
            });
            this.calculator.vnavCalculated.on((sender, data) => {
                if (data === this.primaryPlanIndex) {
                    this.bus.getPublisher().pub('vnav_path_calculated', data, true, false);
                }
            });
            const nav = this.bus.getSubscriber();
            nav.on('cdi_select').handle((src) => {
                this.cdiSource = src;
            });
            SimVar.SetSimVarValue(msfsSdk.VNavVars.GPVerticalDeviation, msfsSdk.SimVarValueType.Feet, Number.MAX_SAFE_INTEGER);
            SimVar.SetSimVarValue(msfsSdk.VNavVars.PathMode, msfsSdk.SimVarValueType.Number, msfsSdk.VNavPathMode.None);
            this.todBodDetailsSub.sub(this.publishTODBODDetails.bind(this), true);
            this.vnavNextLegTargetAltitude.sub(v => {
                this.bus.getPublisher().pub('vnav_active_leg_alt', v !== null && v !== void 0 ? v : 0, true, false);
            });
            this.vnavUnavailable.sub(v => this.onVnavUnavailable(v));
            this.setState(msfsSdk.VNavState.Enabled_Active);
        }
        /** @inheritdoc */
        setState(vnavState) {
            if (vnavState !== this.state) {
                this.state = vnavState;
                switch (this.state) {
                    case msfsSdk.VNavState.Disabled:
                        this.pathMode = msfsSdk.VNavPathMode.None;
                        this.awaitingAltCap = -1;
                        this.awaitingRearm = -1;
                        break;
                    case msfsSdk.VNavState.Enabled_Active:
                        this.tryActivate();
                        break;
                    case msfsSdk.VNavState.Enabled_Inactive:
                        this.tryDeactivate();
                        break;
                }
                SimVar.SetSimVarValue(msfsSdk.VNavVars.VNAVState, msfsSdk.SimVarValueType.Number, this.state);
            }
        }
        /** @inheritdoc */
        tryActivate() {
            if (this.state !== msfsSdk.VNavState.Disabled && this.cdiSource.type === msfsSdk.NavSourceType.Gps) {
                this.state = msfsSdk.VNavState.Enabled_Active;
                SimVar.SetSimVarValue(msfsSdk.VNavVars.VNAVState, msfsSdk.SimVarValueType.Number, this.state);
                this.pathMode = msfsSdk.VNavPathMode.None;
                this.awaitingAltCap = -1;
                this.awaitingRearm = -1;
            }
        }
        /** @inheritdoc */
        tryDeactivate(newMode) {
            if (this.state !== msfsSdk.VNavState.Disabled) {
                if (this.state !== msfsSdk.VNavState.Enabled_Inactive) {
                    this.state = msfsSdk.VNavState.Enabled_Inactive;
                    SimVar.SetSimVarValue(msfsSdk.VNavVars.VNAVState, msfsSdk.SimVarValueType.Number, this.state);
                    if (this.pathMode !== msfsSdk.VNavPathMode.None) {
                        this.disarmPath(newMode);
                    }
                    this.isAltCaptured = false;
                    if (this.awaitingAltCap === -1 && this.awaitingRearm === -1) {
                        SimVar.SetSimVarValue('L:XMLVAR_VNAVButtonValue', 'Bool', false);
                    }
                }
            }
        }
        /** @inheritdoc */
        canVerticalModeActivate() {
            // Method not required for Garmin VNav Manager.
            return true;
        }
        /** @inheritdoc */
        onPathDirectorDeactivated() {
            // Method not required for Garmin VNav Manager.
        }
        /**
         * Method to call when VNAV Encounters a failed state.
         */
        failed() {
            if (this.pathMode === msfsSdk.VNavPathMode.PathActive && !this.isAltCaptured) {
                this.apValues.capturedAltitude.set(this.currentAltitude);
            }
            this.tryDeactivate(msfsSdk.APVerticalModes.CAP);
        }
        /**
         * Method called to delegate altitude capture to the Alt Cap Director.
         * @param newPathMode The new path mode to set on delegating.
         */
        onDelegateAltCap(newPathMode) {
            this.disarmPath(msfsSdk.APVerticalModes.CAP);
            switch (newPathMode) {
                case msfsSdk.VNavPathMode.PathArmed:
                    this.armPath();
                    break;
            }
        }
        /** Method called to arm Path Mode. */
        armPath() {
            if (this.awaitingAltCap < 0 && this.awaitingRearm < 0 && this.apValues.verticalActive.get() !== msfsSdk.APVerticalModes.CAP) {
                if (this.pathMode !== msfsSdk.VNavPathMode.PathArmed) {
                    this.pathMode = msfsSdk.VNavPathMode.PathArmed;
                    SimVar.SetSimVarValue(msfsSdk.VNavVars.PathMode, msfsSdk.SimVarValueType.Number, this.pathMode);
                }
                if (this.pathMode === msfsSdk.VNavPathMode.PathArmed) {
                    this.isAltCaptured = false;
                    this.armMode && this.armMode(msfsSdk.APVerticalModes.PATH);
                }
                this.awaitingRearm = -1;
            }
        }
        /** Method called to activate Path Mode. */
        activatePath() {
            if (this.pathMode !== msfsSdk.VNavPathMode.PathActive) {
                this.pathMode = msfsSdk.VNavPathMode.PathActive;
                SimVar.SetSimVarValue(msfsSdk.VNavVars.PathMode, msfsSdk.SimVarValueType.Number, this.pathMode);
            }
            if (this.pathMode === msfsSdk.VNavPathMode.PathActive) {
                this.activateMode && this.activateMode(msfsSdk.APVerticalModes.PATH);
            }
        }
        /**
         * Method to call when VNAV needs to disarm the path, without deactivating VNAV entirely.
         * @param newMode is the vertical mode to set the Autopilot to if Path is currently active.
         */
        disarmPath(newMode = msfsSdk.APVerticalModes.PITCH) {
            if (this.pathMode !== msfsSdk.VNavPathMode.None) {
                if (this.pathMode === msfsSdk.VNavPathMode.PathActive && newMode !== msfsSdk.APVerticalModes.NONE) {
                    this.activateMode && this.activateMode(newMode);
                }
                if (this.pathMode === msfsSdk.VNavPathMode.PathArmed) {
                    this.armMode && this.armMode(msfsSdk.APVerticalModes.NONE);
                }
                this.pathMode = msfsSdk.VNavPathMode.None;
                this.isAltCaptured = false;
                SimVar.SetSimVarValue(msfsSdk.VNavVars.PathMode, msfsSdk.SimVarValueType.Number, this.pathMode);
            }
        }
        /**
         * Updates the VNAV director.
         */
        update() {
            var _a;
            let alongLegDistance = -1;
            let gpManaged = false;
            let needResetTodBodVars = true;
            let activeLegAltitude = undefined;
            if (this.flightPlanner.hasFlightPlan(this.primaryPlanIndex)) {
                const lateralPlan = this.flightPlanner.getFlightPlan(this.primaryPlanIndex);
                const verticalPlan = this.calculator.getVerticalFlightPlan(this.primaryPlanIndex);
                let desiredAltFeet = Number.POSITIVE_INFINITY;
                let targetAltitudeFeet;
                let verticalDeviation = Number.MAX_SAFE_INTEGER;
                let requiredVs = 0;
                alongLegDistance = msfsSdk.UnitType.NMILE.convertTo(this.lnavLegDistanceAlong.get(), msfsSdk.UnitType.METER);
                this.calculator.setCurrentAlongLegDistance(this.primaryPlanIndex, alongLegDistance);
                const currentLegIndex = this.lnavLegIndex.get();
                // const nextLegIndex = currentLegIndex + 1;
                // const nextLeg = nextLegIndex >= 0 && nextLegIndex < lateralPlan.length ? lateralPlan.getLeg(nextLegIndex) : undefined;
                if (currentLegIndex < lateralPlan.length && verticalPlan.constraints.length > 0 && currentLegIndex <= verticalPlan.constraints[0].index) {
                    const currentAltitudeMetric = msfsSdk.UnitType.FOOT.convertTo(this.currentAltitude, msfsSdk.UnitType.METER);
                    const currentVSMetric = msfsSdk.UnitType.FPM.convertTo(this.currentVS, msfsSdk.UnitType.MPM);
                    const todBodDetails = msfsSdk.VNavUtils.getTodBodDetails(verticalPlan, lateralPlan.activeLateralLeg, alongLegDistance, currentAltitudeMetric, currentVSMetric, this.todBodDetails);
                    this.todBodDetailsSub.set('bodLegIndex', todBodDetails.bodLegIndex);
                    this.todBodDetailsSub.set('todLegIndex', todBodDetails.todLegIndex);
                    this.todBodDetailsSub.set('todLegDistance', todBodDetails.todLegDistance);
                    this.todBodDetailsSub.set('distanceFromBod', todBodDetails.distanceFromBod);
                    this.todBodDetailsSub.set('distanceFromTod', todBodDetails.distanceFromTod);
                    this.todBodDetailsSub.set('currentConstraintLegIndex', todBodDetails.currentConstraintLegIndex);
                    needResetTodBodVars = false;
                    this.vnavNextConstraintAltitude.set(this.calculator.getNextConstraintAltitude(this.primaryPlanIndex, lateralPlan.activeLateralLeg));
                    const constraintAltitude = this.calculator.getCurrentConstraintAltitude(this.primaryPlanIndex, lateralPlan.activeLateralLeg);
                    this.vnavUnavailable.set(this.vnavNextConstraintAltitude.get() !== constraintAltitude);
                    const simvarAltitudeSet = constraintAltitude !== undefined ? msfsSdk.UnitType.METER.convertTo(constraintAltitude, msfsSdk.UnitType.FOOT) : -1;
                    SimVar.SetSimVarValue(msfsSdk.VNavVars.CurrentConstraintAltitude, msfsSdk.SimVarValueType.Feet, simvarAltitudeSet);
                    const desiredAltitude = msfsSdk.VNavUtils.getDesiredAltitude(verticalPlan, lateralPlan.activeLateralLeg, alongLegDistance);
                    desiredAltFeet = msfsSdk.UnitType.METER.convertTo(desiredAltitude, msfsSdk.UnitType.FOOT);
                    this.setConstraintDistance(lateralPlan, todBodDetails.currentConstraintLegIndex);
                    const currentLeg = currentLegIndex >= 0 && currentLegIndex < lateralPlan.length ? lateralPlan.getLeg(currentLegIndex) : undefined;
                    if (((_a = currentLeg === null || currentLeg === void 0 ? void 0 : currentLeg.calculated) === null || _a === void 0 ? void 0 : _a.distanceWithTransitions) && constraintAltitude !== undefined) {
                        const distance = this.constraintDistance +
                            msfsSdk.UnitType.METER.convertTo(currentLeg.calculated.distanceWithTransitions - alongLegDistance, msfsSdk.UnitType.NMILE);
                        requiredVs = this.getRequiredVs(distance, msfsSdk.UnitType.METER.convertTo(constraintAltitude, msfsSdk.UnitType.FOOT));
                    }
                }
                else {
                    this.failed();
                }
                if (lateralPlan.length > 0) {
                    const finalLeg = lateralPlan.getLeg(lateralPlan.length - 1);
                    const lpvDistance = this.manageGP(finalLeg, lateralPlan, alongLegDistance);
                    gpManaged = true;
                    verticalDeviation = desiredAltFeet - this.currentAltitude;
                    SimVar.SetSimVarValue(msfsSdk.VNavVars.VerticalDeviation, msfsSdk.SimVarValueType.Feet, verticalDeviation);
                    const targetAltitude = this.calculator.getTargetAltitude(this.primaryPlanIndex, lateralPlan.activeLateralLeg);
                    if (targetAltitude !== undefined) {
                        targetAltitudeFeet = msfsSdk.UnitType.METER.convertTo(targetAltitude, msfsSdk.UnitType.FOOT);
                        SimVar.SetSimVarValue(msfsSdk.VNavVars.TargetAltitude, msfsSdk.SimVarValueType.Feet, targetAltitudeFeet);
                    }
                    else {
                        SimVar.SetSimVarValue(msfsSdk.VNavVars.TargetAltitude, msfsSdk.SimVarValueType.Feet, -1);
                    }
                    const fafLegIndex = verticalPlan.fafLegIndex;
                    if (this.apValues.verticalActive.get() === msfsSdk.APVerticalModes.GP
                        || (fafLegIndex !== undefined && this.apValues.approachHasGP.get() && this.state !== msfsSdk.VNavState.Enabled_Active && lateralPlan.activeLateralLeg >= fafLegIndex)) {
                        requiredVs = this.getRequiredVs(msfsSdk.UnitType.METER.convertTo(lpvDistance, msfsSdk.UnitType.NMILE), msfsSdk.UnitType.METER.convertTo(this.glidepathCalculator.getRunwayAltitude(), msfsSdk.UnitType.FOOT), this.currentGpsAltitude);
                    }
                    SimVar.SetSimVarValue(msfsSdk.VNavVars.RequiredVS, msfsSdk.SimVarValueType.FPM, requiredVs);
                    if (lateralPlan.activeLateralLeg === this.awaitingRearm && this.state === msfsSdk.VNavState.Enabled_Active) {
                        this.awaitingRearm = -1;
                        this.armPath();
                    }
                    const verticalLeg = verticalPlan.constraints.length > 0 ? msfsSdk.VNavUtils.getVerticalLegFromPlan(verticalPlan, lateralPlan.activeLateralLeg) : undefined;
                    if (this.state === msfsSdk.VNavState.Enabled_Active && this.awaitingAltCap === -1 && this.awaitingRearm === -1) {
                        const flightPhase = this.calculator.getFlightPhase(this.primaryPlanIndex);
                        this.manageAltHold(targetAltitudeFeet, flightPhase);
                        this.trackVerticalPath(verticalPlan, lateralPlan, targetAltitudeFeet, verticalDeviation, flightPhase);
                    }
                    else if (lateralPlan.activeLateralLeg < lateralPlan.length && verticalLeg !== undefined) {
                        SimVar.SetSimVarValue(msfsSdk.VNavVars.FPA, msfsSdk.SimVarValueType.Degree, verticalLeg.fpa);
                    }
                    else {
                        SimVar.SetSimVarValue(msfsSdk.VNavVars.FPA, msfsSdk.SimVarValueType.Degree, 0);
                    }
                    activeLegAltitude = verticalLeg && verticalLeg.altitude ? Math.round(verticalLeg.altitude) : undefined;
                }
                else {
                    // TODO: remove this once we have a better way to get LPV state - does an LPV exist or not
                    SimVar.SetSimVarValue(msfsSdk.VNavVars.GPDistance, msfsSdk.SimVarValueType.Meters, Number.MAX_SAFE_INTEGER);
                    this.lpvDeviation.set(-1001);
                }
            }
            else {
                this.calculator.setCurrentAlongLegDistance(this.primaryPlanIndex, alongLegDistance);
                this.failed();
            }
            if (!gpManaged) {
                this.manageGP(undefined, undefined, alongLegDistance);
            }
            if (needResetTodBodVars) {
                this.todBodDetailsSub.set('bodLegIndex', -1);
                this.todBodDetailsSub.set('todLegIndex', -1);
                this.todBodDetailsSub.set('todLegDistance', 0);
                this.todBodDetailsSub.set('distanceFromBod', 0);
                this.todBodDetailsSub.set('distanceFromTod', 0);
                this.todBodDetailsSub.set('currentConstraintLegIndex', -1);
            }
            this.vnavNextLegTargetAltitude.set(activeLegAltitude);
        }
        /**
         * Tracks the vertical path.
         * @param verticalPlan The Vertical Flight Plan.
         * @param lateralPlan The Lateral Flight Plan.
         * @param targetAltitude The current VNAV target altitude, if any.
         * @param verticalDeviation The current vertical deviation.
         * @param flightPhase The current flight phase
         */
        trackVerticalPath(verticalPlan, lateralPlan, targetAltitude, verticalDeviation, flightPhase = msfsSdk.VerticalFlightPhase.Descent) {
            if (targetAltitude === undefined) {
                targetAltitude = Number.NEGATIVE_INFINITY;
            }
            if (lateralPlan.activeLateralLeg >= lateralPlan.length) {
                this.isAltCaptured = false;
                this.failed();
                return;
            }
            if (this.vnavUnavailable.get()) {
                return;
            }
            const targetIsSelectedAltitude = this.preselectedAltitude > targetAltitude;
            targetAltitude = Math.max(targetAltitude, this.preselectedAltitude);
            const deviationFromTarget = targetAltitude - this.currentAltitude;
            const fpa = msfsSdk.VNavUtils.getVerticalLegFromPlan(verticalPlan, lateralPlan.activeLateralLeg).fpa;
            SimVar.SetSimVarValue(msfsSdk.VNavVars.FPA, msfsSdk.SimVarValueType.Degree, fpa);
            if (this.pathMode === msfsSdk.VNavPathMode.None && flightPhase === msfsSdk.VerticalFlightPhase.Descent) {
                if (this.preselectedAltitude + 75 < this.currentAltitude) {
                    this.armPath();
                }
                else if (!this.hasNonPathVnav) {
                    this.tryDeactivate();
                }
            }
            if (!this.isAltCaptured && this.pathMode === msfsSdk.VNavPathMode.PathActive && fpa === 0) {
                this.apValues.capturedAltitude.set(100 * Math.round(targetAltitude / 100));
                const fafAltitude = msfsSdk.VNavUtils.getFafAltitude(verticalPlan);
                if (this.guidanceEndsAtFaf && fafAltitude !== undefined && msfsSdk.UnitType.METER.convertTo(fafAltitude, msfsSdk.UnitType.FOOT) === targetAltitude) {
                    this.onDelegateAltCap(msfsSdk.VNavPathMode.None);
                    this.tryDeactivate();
                }
                else {
                    this.onDelegateAltCap(msfsSdk.VNavPathMode.PathArmed);
                }
            }
            const destLegIndex = verticalPlan.destLegIndex;
            const fafLegIndex = verticalPlan.fafLegIndex;
            if ((destLegIndex !== undefined && lateralPlan.activeLateralLeg > destLegIndex) || fafLegIndex !== undefined && lateralPlan.activeLateralLeg > fafLegIndex) {
                this.tryDeactivate();
                return;
            }
            if (this.pathMode === msfsSdk.VNavPathMode.PathArmed || this.pathMode == msfsSdk.VNavPathMode.PathActive) {
                if (verticalDeviation <= msfsSdk.VNavUtils.getPathErrorDistance(this.currentGroundSpeed) && verticalDeviation >= -50 && this.pathMode === msfsSdk.VNavPathMode.PathArmed) {
                    if (Math.abs(deviationFromTarget) > 75 && (!this.isAltCaptured && fpa !== 0)) {
                        this.activatePath();
                    }
                }
                if (!this.isAltCaptured && Math.abs(deviationFromTarget) <= 250 && this.pathMode == msfsSdk.VNavPathMode.PathActive) {
                    this.capturedAltitude = targetAltitude;
                    this.isAltCaptured = true;
                }
                if (this.isAltCaptured && this.pathMode === msfsSdk.VNavPathMode.PathActive) {
                    const altCapDeviation = Math.abs(this.capturedAltitude - this.currentAltitude);
                    const captureActivationValue = Math.tan(msfsSdk.UnitType.DEGREE.convertTo(fpa, msfsSdk.UnitType.RADIAN)) * msfsSdk.UnitType.NMILE.convertTo(this.currentGroundSpeed / 360, msfsSdk.UnitType.FOOT);
                    if (altCapDeviation < Math.abs(captureActivationValue)) {
                        this.apValues.capturedAltitude.set(Math.round(this.capturedAltitude));
                        if (targetIsSelectedAltitude) {
                            this.onDelegateAltCap(msfsSdk.VNavPathMode.None);
                        }
                        else if (msfsSdk.VNavUtils.getIsPathEnd(verticalPlan, lateralPlan.activeLateralLeg)) {
                            this.tryDeactivate(msfsSdk.APVerticalModes.CAP);
                        }
                        else {
                            this.awaitingAltCap = lateralPlan.activeLateralLeg + 1;
                            this.onDelegateAltCap(msfsSdk.VNavPathMode.None);
                        }
                        return;
                    }
                }
                if (this.pathMode === msfsSdk.VNavPathMode.PathActive) ;
                else if (this.hasNonPathVnav) {
                    const constraint = msfsSdk.VNavUtils.getConstraintFromLegIndex(verticalPlan, lateralPlan.activeLateralLeg);
                    if (constraint && !this.isAltCaptured) {
                        const constraintAltitudeFeet = msfsSdk.UnitType.METER.convertTo(flightPhase === msfsSdk.VerticalFlightPhase.Descent ? constraint.minAltitude : constraint.maxAltitude, msfsSdk.UnitType.FOOT);
                        const nonPathTargetAlt = flightPhase === msfsSdk.VerticalFlightPhase.Descent ? Math.max(constraintAltitudeFeet, this.apValues.selectedAltitude.get())
                            : Math.min(constraintAltitudeFeet, this.apValues.selectedAltitude.get());
                        const nonPathDeviation = nonPathTargetAlt - this.currentAltitude;
                        if (Math.abs(nonPathDeviation) <= 250) {
                            this.capturedAltitude = Math.round(nonPathTargetAlt);
                            this.apValues.capturedAltitude.set(this.capturedAltitude);
                            this.isAltCaptured = true;
                            this.onDelegateAltCap(msfsSdk.VNavPathMode.PathArmed);
                        }
                    }
                }
            }
        }
        /**
         * Method to handle when VNAV becomes available or unavailable due to invalid vnav legs.
         * @param v Whether VNAV is currently unavailable.
         */
        onVnavUnavailable(v) {
            if (v) {
                this.bus.getPublisher().pub('vnav_availability', msfsSdk.VNavAvailability.InvalidLegs, true, false);
                const lateralPlan = this.flightPlanner.getFlightPlan(this.primaryPlanIndex);
                const constraintAlt = this.calculator.getCurrentConstraintAltitude(this.primaryPlanIndex, lateralPlan.activeLateralLeg);
                const verticalPlan = this.calculator.getVerticalFlightPlan(this.primaryPlanIndex);
                if (!verticalPlan.fafLegIndex || lateralPlan.activeLateralLeg <= verticalPlan.fafLegIndex) {
                    this.bus.getPublisher().pub('vnav_availability', msfsSdk.VNavAvailability.InvalidLegs, true, false);
                }
                if (this.pathMode === msfsSdk.VNavPathMode.PathActive && constraintAlt !== undefined && Math.abs(this.currentAltitude - constraintAlt) < 300) {
                    this.apValues.capturedAltitude.set(100 * Math.round(constraintAlt / 100));
                    this.tryDeactivate(msfsSdk.APVerticalModes.CAP);
                }
                else {
                    this.tryDeactivate();
                }
            }
            else {
                this.bus.getPublisher().pub('vnav_availability', msfsSdk.VNavAvailability.Available, true, false);
            }
        }
        /**
         * Manages Altitude Hold Target.
         * @param targetAltitude The current VNAV target altitude, if any.
         * @param flightPhase The current vertical flight phase (defaults to descent).
         */
        manageAltHold(targetAltitude, flightPhase = msfsSdk.VerticalFlightPhase.Descent) {
            if (targetAltitude !== undefined) {
                const targetAltFeet = targetAltitude;
                if (flightPhase === msfsSdk.VerticalFlightPhase.Descent) {
                    if (this.preselectedAltitude >= targetAltFeet) {
                        SimVar.SetSimVarValue(msfsSdk.VNavVars.CaptureType, msfsSdk.SimVarValueType.Number, msfsSdk.VNavAltCaptureType.Selected);
                    }
                    else {
                        SimVar.SetSimVarValue(msfsSdk.VNavVars.CaptureType, msfsSdk.SimVarValueType.Number, msfsSdk.VNavAltCaptureType.VNAV);
                    }
                }
                else {
                    if (this.preselectedAltitude <= targetAltFeet) {
                        SimVar.SetSimVarValue(msfsSdk.VNavVars.CaptureType, msfsSdk.SimVarValueType.Number, msfsSdk.VNavAltCaptureType.Selected);
                    }
                    else {
                        SimVar.SetSimVarValue(msfsSdk.VNavVars.CaptureType, msfsSdk.SimVarValueType.Number, msfsSdk.VNavAltCaptureType.VNAV);
                    }
                }
            }
            else {
                SimVar.SetSimVarValue(msfsSdk.VNavVars.TargetAltitude, msfsSdk.SimVarValueType.Feet, -1);
                SimVar.SetSimVarValue(msfsSdk.VNavVars.CaptureType, msfsSdk.SimVarValueType.Number, msfsSdk.VNavAltCaptureType.None);
            }
        }
        /**
         * Manages the GP State and sets required data for GP guidance.
         * @param finalLeg The LegDefinition for the last flight lateralPlan leg.
         * @param lateralPlan The FlightPlan.
         * @param alongLegDistance The Along Leg Distance
         * @returns The LPV Distance
         */
        manageGP(finalLeg, lateralPlan, alongLegDistance) {
            let lpvDeviation = -1001;
            let lpvDistance = -1;
            let gpCalculated = false;
            let gpServiceLevel = exports.GlidepathServiceLevel.None;
            if (lateralPlan && this.gpAvailable.get() && (finalLeg === null || finalLeg === void 0 ? void 0 : finalLeg.calculated) !== undefined) {
                // TODO: Maybe one day we will model service degradation, but for now we will just use the maximum possible
                // service level for a given approach
                // Note: because the GP available flag is true, we don't have to check for circling approaches
                const approachDetails = this.approachDetails.get();
                if (approachDetails.type === msfsSdk.AdditionalApproachType.APPROACH_TYPE_VISUAL) {
                    gpServiceLevel = exports.GlidepathServiceLevel.Visual;
                }
                else {
                    switch (approachDetails.bestRnavType) {
                        case msfsSdk.RnavTypeFlags.LNAV:
                            gpServiceLevel = exports.GlidepathServiceLevel.LNavPlusV;
                            break;
                        case msfsSdk.RnavTypeFlags.LNAVVNAV:
                            gpServiceLevel = exports.GlidepathServiceLevel.LNavVNav;
                            break;
                        case msfsSdk.RnavTypeFlags.LP:
                            gpServiceLevel = exports.GlidepathServiceLevel.LpPlusV;
                            break;
                        case msfsSdk.RnavTypeFlags.LPV:
                            gpServiceLevel = exports.GlidepathServiceLevel.Lpv;
                            break;
                    }
                }
                lpvDistance = this.glidepathCalculator.getGlidepathDistance(lateralPlan.activeLateralLeg, alongLegDistance);
                const desiredLPVAltitude = this.glidepathCalculator.getDesiredGlidepathAltitude(lpvDistance);
                const desiredLPVAltitudeFeet = msfsSdk.UnitType.METER.convertTo(desiredLPVAltitude, msfsSdk.UnitType.FOOT);
                lpvDeviation = msfsSdk.MathUtils.clamp(desiredLPVAltitudeFeet - this.currentGpsAltitude, -1000, 1000);
                gpCalculated = true;
            }
            SimVar.SetSimVarValue(msfsSdk.VNavVars.GPServiceLevel, msfsSdk.SimVarValueType.Number, gpServiceLevel);
            SimVar.SetSimVarValue(msfsSdk.VNavVars.GPVerticalDeviation, msfsSdk.SimVarValueType.Feet, lpvDeviation);
            SimVar.SetSimVarValue(msfsSdk.VNavVars.GPDistance, msfsSdk.SimVarValueType.Meters, lpvDistance);
            SimVar.SetSimVarValue(msfsSdk.VNavVars.GPFpa, msfsSdk.SimVarValueType.Degree, this.glidepathCalculator.glidepathFpa);
            this.apValues.approachHasGP.set(gpCalculated);
            return lpvDistance;
        }
        /**
         * Gets the current required vertical speed.
         * @param distance The distance to the constraint, in nautical miles.
         * @param targetAltitude The target altitude for the constraint, in feet.
         * @param currentAltitude The current altitude, in feet. (defaults to baro alt)
         * @returns The required vertical speed in feet per minute.
         */
        getRequiredVs(distance, targetAltitude, currentAltitude = this.currentAltitude) {
            if (targetAltitude > 0) {
                return msfsSdk.VNavUtils.getRequiredVs(distance, targetAltitude, currentAltitude, this.currentGroundSpeed);
            }
            return 0;
        }
        /**
         * Sets the leg distance from the current leg to the constraint leg, not include the distance to the current active leg.
         * @param lateralPlan is the flight lateralPlan.
         * @param constraintLegIndex is the leg index of the current constraint.
         */
        setConstraintDistance(lateralPlan, constraintLegIndex) {
            var _a, _b;
            if (constraintLegIndex !== undefined && constraintLegIndex > -1) {
                const currentLeg = lateralPlan.getLeg(lateralPlan.activeLateralLeg);
                const constraintLeg = lateralPlan.getLeg(constraintLegIndex);
                if (((_a = constraintLeg.calculated) === null || _a === void 0 ? void 0 : _a.cumulativeDistanceWithTransitions) && ((_b = currentLeg.calculated) === null || _b === void 0 ? void 0 : _b.cumulativeDistanceWithTransitions)) {
                    const currentLegCumulativeNM = msfsSdk.UnitType.METER.convertTo(currentLeg.calculated.cumulativeDistanceWithTransitions, msfsSdk.UnitType.NMILE);
                    const bodCumulativeNM = msfsSdk.UnitType.METER.convertTo(constraintLeg.calculated.cumulativeDistanceWithTransitions, msfsSdk.UnitType.NMILE);
                    this.constraintDistance = (bodCumulativeNM - currentLegCumulativeNM);
                    return;
                }
            }
            this.constraintDistance = -1;
        }
        /**
         * Publishes TOD/BOD details to simvars.
         * @param details The TOD/BOD details object.
         * @param key The key to publish.
         * @param value The value to publish.
         */
        publishTODBODDetails(details, key, value) {
            switch (key) {
                case 'bodLegIndex':
                    SimVar.SetSimVarValue(msfsSdk.VNavVars.BODLegIndex, msfsSdk.SimVarValueType.Number, value);
                    break;
                case 'todLegIndex':
                    SimVar.SetSimVarValue(msfsSdk.VNavVars.TODLegIndex, msfsSdk.SimVarValueType.Number, value);
                    break;
                case 'todLegDistance':
                    SimVar.SetSimVarValue(msfsSdk.VNavVars.TODDistanceInLeg, msfsSdk.SimVarValueType.Meters, value);
                    break;
                case 'distanceFromBod':
                    SimVar.SetSimVarValue(msfsSdk.VNavVars.BODDistance, msfsSdk.SimVarValueType.Meters, value);
                    break;
                case 'distanceFromTod':
                    SimVar.SetSimVarValue(msfsSdk.VNavVars.TODDistance, msfsSdk.SimVarValueType.Meters, value);
                    break;
                case 'currentConstraintLegIndex':
                    SimVar.SetSimVarValue(msfsSdk.VNavVars.CurrentConstraintLegIndex, msfsSdk.SimVarValueType.Number, value);
                    break;
            }
        }
    }

    /** A component for displaying CAS messages. */
    class CASDisplay extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.casDiv = msfsSdk.FSComponent.createRef();
            this.messageCount = msfsSdk.SubscribableUtils.toSubscribable(this.props.numAnnunciationsShown, true);
            this.activeAnns = [];
            this.messageSlots = [];
            /** The index of the message that is currently displayed in the top (first) slot. */
            this.topAlertIndex = 0;
            this.refreshNewMessageCssClassTimer = new msfsSdk.DebounceTimer();
            this.refreshNewMessageCssClassCallback = this.refreshNewMessageCssClass.bind(this);
        }
        /** @inheritdoc */
        onAfterRender() {
            this.renderMessageSlots();
            this.updateAlertCounts();
            this.messageCountSub = this.messageCount.sub(this.onMessageCountChanged.bind(this));
            this.messageSub = this.props.annunciations.sub(this.onMessagesChanged.bind(this), true);
        }
        /**
         * Responds to when the maximum displayed message count changes.
         */
        onMessageCountChanged() {
            this.renderMessageSlots();
            // Ensure we are not scrolled past the last slot.
            this.topAlertIndex = Math.min(this.topAlertIndex, Math.max(0, this.activeAnns.length - this.messageCount.get()));
            this.updateAlertCounts();
            this.updateDisplayedAnnunciations();
        }
        /**
         * Render our the divs for our alerts.
         */
        renderMessageSlots() {
            const count = Math.max(0, this.messageCount.get());
            if (count > this.messageSlots.length) {
                // Add additional slots.
                while (this.messageSlots.length < count) {
                    const entry = {
                        isVisible: msfsSdk.Subject.create(false),
                        text: msfsSdk.Subject.create(''),
                        cssClass: msfsSdk.SetSubject.create(['annunciation']),
                        isAcknowledged: undefined
                    };
                    this.messageSlots.push(entry);
                    msfsSdk.FSComponent.render(msfsSdk.FSComponent.buildComponent("div", { class: entry.cssClass, style: { 'display': entry.isVisible.map(visible => visible ? '' : 'none') } }, entry.text), this.casDiv.instance);
                }
            }
            else if (count < this.messageSlots.length) {
                // Remove excess slots.
                while (this.messageSlots.length > count) {
                    this.messageSlots.pop();
                    const element = this.casDiv.instance.lastElementChild;
                    if (element) {
                        this.casDiv.instance.removeChild(element);
                    }
                }
            }
        }
        /**
         * Responds to when the list of displayed CAS messages changes.
         * @param idx The index of the first message that changed.
         * @param type The type of change that occurred.
         * @param item The message or messages that changed.
         */
        onMessagesChanged(idx, type, item) {
            if (type === msfsSdk.SubscribableArrayEventType.Cleared) {
                this.clearAnnunciations();
                return;
            }
            if (Array.isArray(item)) {
                // Insert into the specified index from the end of the array to keep
                // the provided order.
                for (let i = item.length - 1; i >= 0; i--) {
                    this.handleMessageChanged(idx, type, item[i]);
                }
            }
            else {
                this.handleMessageChanged(idx, type, item);
            }
        }
        /**
         * Handles when a displayed CAS message is added or removed.
         * @param idx The index of the message that changed.
         * @param type The type of change that occurred.
         * @param item The message that changed.
         */
        handleMessageChanged(idx, type, item) {
            switch (type) {
                case msfsSdk.SubscribableArrayEventType.Added:
                    this.addAnnunciation(idx, item);
                    break;
                case msfsSdk.SubscribableArrayEventType.Removed:
                    this.removeAnnunciation(idx, item);
                    break;
            }
        }
        /**
         * Add an annunciation to the active list.
         * @param idx The index of the annunciations array for the annunciation.
         * @param item The annunciation configuration to add
         */
        addAnnunciation(idx, item) {
            this.activeAnns.splice(idx, 0, item);
            // If we're passing back counts, update those.
            if (this.props.alertCounts !== undefined) {
                switch (item.priority) {
                    case msfsSdk.AnnunciationType.Warning:
                        this.props.alertCounts.set('numWarning', this.props.alertCounts.get().numWarning + 1);
                        break;
                    case msfsSdk.AnnunciationType.Caution:
                        this.props.alertCounts.set('numCaution', this.props.alertCounts.get().numCaution + 1);
                        break;
                    case msfsSdk.AnnunciationType.Advisory:
                        this.props.alertCounts.set('numAdvisory', this.props.alertCounts.get().numAdvisory + 1);
                        break;
                    case msfsSdk.AnnunciationType.SafeOp:
                        this.props.alertCounts.set('numSafeOp', this.props.alertCounts.get().numSafeOp + 1);
                        break;
                }
                this.updateAlertCounts();
            }
            this.updateDisplayedAnnunciations();
        }
        /**
         * Remove an annunciation from the active list if present.
         * @param idx The index of the annunciations array for the annunciation.
         * @param item The item that's being removed.
         */
        removeAnnunciation(idx, item) {
            this.activeAnns.splice(idx, 1);
            // We need to be sure to move the display up if a) the removed alert was above our top line, or
            // b) the removal is at the bottom and brings the alert count within the number of annunciations
            // allowed to be shown, meaning we need to display stuff higher up -- UNLESS we're already at the
            // top.  Got it?
            if ((idx <= this.topAlertIndex || this.activeAnns.length - this.messageCount.get() - this.topAlertIndex <= 0) && this.topAlertIndex > 0) {
                this.topAlertIndex--;
            }
            // If we're passing back counts, update those.
            if (this.props.alertCounts !== undefined) {
                switch (item.priority) {
                    case msfsSdk.AnnunciationType.Warning:
                        this.props.alertCounts.set('numWarning', this.props.alertCounts.get().numWarning - 1);
                        break;
                    case msfsSdk.AnnunciationType.Caution:
                        this.props.alertCounts.set('numCaution', this.props.alertCounts.get().numCaution - 1);
                        break;
                    case msfsSdk.AnnunciationType.Advisory:
                        this.props.alertCounts.set('numAdvisory', this.props.alertCounts.get().numAdvisory - 1);
                        break;
                    case msfsSdk.AnnunciationType.SafeOp:
                        this.props.alertCounts.set('numSafeOp', this.props.alertCounts.get().numSafeOp - 1);
                        break;
                }
                this.updateAlertCounts();
            }
            this.updateDisplayedAnnunciations();
        }
        /**
         * Clear the annunciation display.
         */
        clearAnnunciations() {
            this.activeAnns.length = 0;
            if (this.props.alertCounts !== undefined) {
                this.props.alertCounts.set('numWarning', 0);
                this.props.alertCounts.set('numCaution', 0);
                this.props.alertCounts.set('numAdvisory', 0);
                this.props.alertCounts.set('numSafeOp', 0);
                this.updateAlertCounts();
            }
            this.updateDisplayedAnnunciations();
        }
        /**
         * Update our internal alert counts.  For efficiency we only update aggregate totals here,
         * it's up to other code to individually increment the per-alert-class totals when an alert
         * gets added or removed.
         */
        updateAlertCounts() {
            if (this.props.alertCounts !== undefined) {
                this.props.alertCounts.set('totalAlerts', this.activeAnns.length);
                this.props.alertCounts.set('countAboveWindow', this.topAlertIndex);
                this.props.alertCounts.set('countBelowWindow', Math.max(this.activeAnns.length - this.messageCount.get() - this.topAlertIndex, 0));
            }
        }
        /**
         * Update the displayed divs with current annunciation status.
         */
        updateDisplayedAnnunciations() {
            var _a;
            const end = Math.min(this.messageSlots.length, this.activeAnns.length - this.topAlertIndex);
            for (let i = 0; i < end; i++) {
                const ann = this.activeAnns[i + this.topAlertIndex];
                const entry = this.messageSlots[i];
                entry.isVisible.set(true);
                entry.text.set(ann.message + (ann.suffixes !== undefined ? ' ' + ((_a = ann.suffixes) === null || _a === void 0 ? void 0 : _a.join('-')) : ''));
                entry.cssClass.toggle('warning', ann.priority === msfsSdk.AnnunciationType.Warning);
                entry.cssClass.toggle('caution', ann.priority === msfsSdk.AnnunciationType.Caution);
                entry.cssClass.toggle('advisory', ann.priority === msfsSdk.AnnunciationType.Advisory);
                entry.cssClass.toggle('safe-op', ann.priority === msfsSdk.AnnunciationType.SafeOp);
                if (ann.priority === msfsSdk.AnnunciationType.Caution || ann.priority === msfsSdk.AnnunciationType.Warning) {
                    // If the message is a caution or warning, we need to handle the acknowledged/unacknowledged state.
                    // Typically, unacknowledged messages will need to be flashing, and we want all such messages to flash in sync
                    // with each other. To accomplish that, whenever any slot is changed, we will remove the unacknowledged CSS
                    // class from all slots, then wait one frame and add that class back to all slots displaying an unacknowledged
                    // message. This ensures that any animations that are applied using the CSS class are reset for all
                    // unacknowledged messages at the same time.
                    entry.isAcknowledged = ann.acknowledged;
                    entry.cssClass.delete('new');
                    entry.cssClass.toggle('acked', ann.acknowledged);
                    if (!ann.acknowledged && !this.refreshNewMessageCssClassTimer.isPending()) {
                        this.refreshNewMessageCssClassTimer.schedule(this.refreshNewMessageCssClassCallback, 0);
                    }
                }
                else {
                    entry.isAcknowledged = undefined;
                    entry.cssClass.delete('acked');
                    entry.cssClass.delete('new');
                }
            }
            for (let i = end; i < this.messageSlots.length; i++) {
                const entry = this.messageSlots[i];
                entry.isVisible.set(false);
                entry.isAcknowledged = undefined;
            }
        }
        /**
         * Adds the `new` CSS class to all of this display's message slots that are currently displaying an unacknowledged
         * message.
         */
        refreshNewMessageCssClass() {
            for (let i = 0; i < this.messageSlots.length; i++) {
                const entry = this.messageSlots[i];
                if (entry.isAcknowledged === false) {
                    entry.cssClass.add('new');
                }
            }
        }
        /**
         * Scroll the message window down.
         */
        scrollDown() {
            if (this.topAlertIndex < this.activeAnns.length - this.messageCount.get() && this.activeAnns.length > this.messageCount.get()) {
                this.topAlertIndex++;
                this.updateDisplayedAnnunciations();
                this.updateAlertCounts();
            }
        }
        /**
         * Scroll the message window up.
         */
        scrollUp() {
            if (this.topAlertIndex > 0) {
                this.topAlertIndex--;
                this.updateDisplayedAnnunciations();
                this.updateAlertCounts();
            }
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: 'cas-display', ref: this.casDiv }));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b;
            this.refreshNewMessageCssClassTimer.clear();
            (_a = this.messageCountSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.messageSub) === null || _b === void 0 ? void 0 : _b.destroy();
            super.destroy();
        }
    }

    /** A VNode representing a preformated rendering of an approach's name. */
    class ApproachNameDisplay extends msfsSdk.DisplayComponent {
        constructor() {
            var _a, _b, _c;
            super(...arguments);
            this.nameRef = msfsSdk.FSComponent.createRef();
            this.prefixRef = msfsSdk.FSComponent.createRef();
            this.airportRef = msfsSdk.FSComponent.createRef();
            this.subTypeRef = msfsSdk.FSComponent.createRef();
            this.suffixRef = msfsSdk.FSComponent.createRef();
            this.runwayRef = msfsSdk.FSComponent.createRef();
            this.flagsRef = msfsSdk.FSComponent.createRef();
            this.nullRef = msfsSdk.FSComponent.createRef();
            this.approach = msfsSdk.SubscribableUtils.toSubscribable(this.props.approach, true);
            this.approachNamePartsProp = msfsSdk.SubscribableUtils.toSubscribable(this.props.approachNameParts, true);
            this.airport = msfsSdk.SubscribableUtils.toSubscribable((_a = this.props.airport) !== null && _a !== void 0 ? _a : null, true);
            this.airportIdent = msfsSdk.SubscribableUtils.toSubscribable((_b = this.props.airportIdent) !== null && _b !== void 0 ? _b : '', true);
            this.prefix = msfsSdk.SubscribableUtils.toSubscribable((_c = this.props.prefix) !== null && _c !== void 0 ? _c : '', true);
            this.prefixText = this.prefix.map(prefix => prefix !== null && prefix !== void 0 ? prefix : '');
            this.airportText = this.props.airportIdent
                ? this.airportIdent.map(x => x)
                : this.airport.map(airport => airport ? msfsSdk.ICAO.getIdent(airport.icao) : '');
            this.approachNameParts = this.props.approachNameParts
                ? this.approachNamePartsProp.map(x => x)
                : this.approach.map(approach => approach ? FmsUtils.getApproachNameAsParts(approach) : null);
            this.typeText = this.approachNameParts.map(parts => { var _a; return (_a = parts === null || parts === void 0 ? void 0 : parts.type) !== null && _a !== void 0 ? _a : ''; });
            this.subTypeText = this.approachNameParts.map(parts => { var _a; return (_a = parts === null || parts === void 0 ? void 0 : parts.subtype) !== null && _a !== void 0 ? _a : ''; });
            this.suffixConnectorText = this.approachNameParts.map(parts => !parts || parts.runway ? ' ' : '–');
            this.suffixText = this.approachNameParts.map(parts => { var _a; return (_a = parts === null || parts === void 0 ? void 0 : parts.suffix) !== null && _a !== void 0 ? _a : ''; });
            this.runwayText = this.approachNameParts.map(parts => { var _a; return (_a = parts === null || parts === void 0 ? void 0 : parts.runway) !== null && _a !== void 0 ? _a : ''; });
            this.flagsText = this.approachNameParts.map(parts => { var _a; return (_a = parts === null || parts === void 0 ? void 0 : parts.flags) !== null && _a !== void 0 ? _a : ''; });
        }
        /** @inheritdoc */
        onAfterRender() {
            this.approachNameParts.sub(parts => {
                this.nameRef.instance.style.display = parts ? '' : 'none';
                this.nullRef.instance.style.display = this.props.nullText === undefined || parts ? 'none' : '';
            }, true);
            this.prefixText.sub(value => { this.prefixRef.instance.style.display = !value ? 'none' : ''; }, true);
            this.airportText.sub(value => { this.airportRef.instance.style.display = value === '' ? 'none' : ''; }, true);
            this.subTypeText.sub(value => { this.subTypeRef.instance.style.display = value === '' ? 'none' : ''; }, true);
            this.suffixText.sub(value => { this.suffixRef.instance.style.display = value === '' ? 'none' : ''; }, true);
            this.runwayText.sub(value => { this.runwayRef.instance.style.display = value === '' ? 'none' : ''; }, true);
            this.flagsText.sub(value => { this.flagsRef.instance.style.display = value === '' ? 'none' : ''; }, true);
        }
        /** @inheritdoc */
        render() {
            var _a, _b;
            return (msfsSdk.FSComponent.buildComponent("div", { class: `appr-name ${(_a = this.props.class) !== null && _a !== void 0 ? _a : ''}` },
                msfsSdk.FSComponent.buildComponent("span", { ref: this.nameRef },
                    msfsSdk.FSComponent.buildComponent("span", { ref: this.prefixRef }, this.prefixText),
                    msfsSdk.FSComponent.buildComponent("span", { ref: this.airportRef },
                        this.props.useZeroWithSlash ? this.airportText.map(msfsSdk.StringUtils.useZeroSlash) : this.airportText,
                        "\u2013"),
                    msfsSdk.FSComponent.buildComponent("span", null, this.typeText),
                    msfsSdk.FSComponent.buildComponent("span", { ref: this.subTypeRef, class: 'appr-name-subtype' }, this.subTypeText),
                    msfsSdk.FSComponent.buildComponent("span", { ref: this.suffixRef },
                        this.suffixConnectorText,
                        this.suffixText),
                    msfsSdk.FSComponent.buildComponent("span", { ref: this.runwayRef },
                        " ",
                        this.props.useZeroWithSlash ? this.runwayText.map(msfsSdk.StringUtils.useZeroSlash) : this.runwayText),
                    msfsSdk.FSComponent.buildComponent("span", { ref: this.flagsRef },
                        " ",
                        this.flagsText)),
                msfsSdk.FSComponent.buildComponent("span", { ref: this.nullRef }, (_b = this.props.nullText) !== null && _b !== void 0 ? _b : '')));
        }
        /** @inheritdoc */
        destroy() {
            this.prefixText.destroy();
            this.airportText.destroy();
            this.approachNameParts.destroy();
            super.destroy();
        }
    }

    /**
     * Displays a bearing value.
     */
    class BearingDisplay extends msfsSdk.AbstractNumberUnitDisplay {
        constructor() {
            var _a;
            super(...arguments);
            this.unitFormatter = (_a = this.props.unitFormatter) !== null && _a !== void 0 ? _a : BearingDisplay.DEFAULT_UNIT_FORMATTER;
            this.unitTextBigDisplay = msfsSdk.Subject.create('');
            this.unitTextSmallDisplay = msfsSdk.Subject.create('');
            this.numberText = msfsSdk.Subject.create('');
            this.unitTextBig = msfsSdk.Subject.create('');
            this.unitTextSmall = msfsSdk.Subject.create('');
        }
        /** @inheritdoc */
        onValueChanged(value) {
            let displayUnit = this.displayUnit.get();
            if (!displayUnit || !value.unit.canConvert(displayUnit)) {
                displayUnit = value.unit;
            }
            const numberValue = value.asUnit(displayUnit);
            this.updateNumberText(numberValue);
            this.updateUnitText(numberValue, displayUnit);
            if (this.props.hideDegreeSymbolWhenNan === true) {
                this.updateUnitTextVisibility(numberValue);
            }
        }
        /** @inheritdoc */
        onDisplayUnitChanged(displayUnit) {
            const value = this.value.get();
            if (!displayUnit || !value.unit.canConvert(displayUnit)) {
                displayUnit = value.unit;
            }
            const numberValue = value.asUnit(displayUnit);
            this.updateNumberText(numberValue);
            this.updateUnitText(numberValue, displayUnit);
            this.updateUnitTextVisibility(numberValue);
        }
        /**
         * Updates this component's displayed number text.
         * @param numberValue The numeric value to display.
         */
        updateNumberText(numberValue) {
            let numberText = this.props.formatter(numberValue);
            if (this.props.use360 !== false && parseFloat(numberText) === 0) {
                numberText = this.props.formatter(360);
            }
            this.numberText.set(numberText);
        }
        /**
         * Updates this component's displayed unit text.
         * @param numberValue The numeric value to display.
         * @param displayUnit The unit type in which to display the value.
         */
        updateUnitText(numberValue, displayUnit) {
            BearingDisplay.unitTextCache[0] = '';
            BearingDisplay.unitTextCache[1] = '';
            this.unitFormatter(BearingDisplay.unitTextCache, displayUnit, numberValue);
            this.unitTextBig.set(BearingDisplay.unitTextCache[0]);
            this.unitTextSmall.set(BearingDisplay.unitTextCache[1]);
        }
        /**
         * Updates whether this component's unit text spans are visible.
         * @param numberValue The numeric value displayed by this component.
         */
        updateUnitTextVisibility(numberValue) {
            if (this.props.hideDegreeSymbolWhenNan === true) {
                if (isNaN(numberValue)) {
                    this.unitTextBigDisplay.set('none');
                    this.unitTextSmallDisplay.set('none');
                    return;
                }
            }
            // We have to hide the unit text when empty because an empty string will get rendered as a space.
            this.unitTextBigDisplay.set(this.unitTextBig.get() === '' ? 'none' : '');
            this.unitTextSmallDisplay.set(this.unitTextSmall.get() === '' ? 'none' : '');
        }
        /** @inheritdoc */
        render() {
            var _a;
            return (msfsSdk.FSComponent.buildComponent("div", { class: (_a = this.props.class) !== null && _a !== void 0 ? _a : '', style: 'white-space: nowrap;' },
                msfsSdk.FSComponent.buildComponent("span", { class: 'bearing-num' }, this.numberText),
                msfsSdk.FSComponent.buildComponent("span", { class: 'bearing-unit', style: { 'display': this.unitTextBigDisplay } }, this.unitTextBig),
                msfsSdk.FSComponent.buildComponent("span", { class: 'bearing-unit-small', style: { 'display': this.unitTextSmallDisplay } }, this.unitTextSmall)));
        }
    }
    /**
     * A function which formats units to default text for BearingDisplay.
     * @param out The 2-tuple to which to write the formatted text, as `[bigText, smallText]`.
     * @param unit The unit to format.
     */
    BearingDisplay.DEFAULT_UNIT_FORMATTER = (out, unit) => {
        out[0] = '°';
        out[1] = unit.isMagnetic() ? '' : 'T';
    };
    BearingDisplay.unitTextCache = ['', ''];

    /**
     * A failure box.
     */
    class FailureBox extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.display = this.props.show.map(show => show ? '' : 'none');
        }
        /** @inheritdoc */
        render() {
            var _a;
            let cssClass;
            if (typeof this.props.class === 'object') {
                cssClass = msfsSdk.SetSubject.create();
                cssClass.add('failure-box');
                if (this.props.class.isSubscribableSet === true) {
                    this.cssClassSubs = [msfsSdk.FSComponent.bindCssClassSet(cssClass, this.props.class, FailureBox.RESERVED_CLASSES)];
                }
                else {
                    this.cssClassSubs = msfsSdk.FSComponent.bindCssClassSet(cssClass, this.props.class, FailureBox.RESERVED_CLASSES);
                }
            }
            else {
                cssClass = `failure-box ${(_a = this.props.class) !== null && _a !== void 0 ? _a : ''}`;
            }
            return (msfsSdk.FSComponent.buildComponent("svg", { class: cssClass, style: { 'display': this.display } },
                msfsSdk.FSComponent.buildComponent("line", { x1: '0%', y1: '0%', x2: '100%', y2: '100%', class: 'failure-box-line' }),
                msfsSdk.FSComponent.buildComponent("line", { x1: '0%', y1: '100%', x2: '100%', y2: '0%', class: 'failure-box-line' })));
        }
        /** @inheritdoc */
        destroy() {
            this.display.destroy();
            if (this.cssClassSubs) {
                for (const sub of this.cssClassSubs) {
                    sub.destroy();
                }
            }
            super.destroy();
        }
    }
    FailureBox.RESERVED_CLASSES = ['failure-box'];

    /**
     * Display formats for {@link LatLonDisplay}.
     */
    exports.LatLonDisplayFormat = void 0;
    (function (LatLonDisplayFormat) {
        /** HDDD° MM.MM' */
        LatLonDisplayFormat["HDDD_MMmm"] = "HDDD\u00B0 MM.MM'";
        /** HDDD° MM.MMM' */
        LatLonDisplayFormat["HDDD_MMmmm"] = "HDDD\u00B0 MM.MMM'";
        /** HDDD° MM' SS.S */
        LatLonDisplayFormat["HDDD_MM_SSs"] = "HDDD\u00B0 MM' SS.S";
    })(exports.LatLonDisplayFormat || (exports.LatLonDisplayFormat = {}));
    /**
     * A component which displays Garmin-style lat/lon coordinates.
     */
    class LatLonDisplay extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.value = msfsSdk.SubscribableUtils.toSubscribable(this.props.value, true);
            this.format = msfsSdk.SubscribableUtils.toSubscribable(this.props.format, true);
            this.valueState = msfsSdk.MappedSubject.create(this.value, this.format);
            this.latText = msfsSdk.Subject.create('');
            this.latPrefixText = this.props.splitPrefix ? this.latText.map((it) => it[0]) : undefined;
            this.latNumberText = this.props.splitPrefix ? this.latText.map((it) => it.substring(2)) : undefined;
            this.lonText = msfsSdk.Subject.create('');
            this.lonPrefixText = this.props.splitPrefix ? this.lonText.map((it) => it[0]) : undefined;
            this.lonNumberText = this.props.splitPrefix ? this.lonText.map((it) => it.substring(1)) : undefined;
        }
        /** @inheritdoc */
        onAfterRender() {
            const latFormatters = this.props.blankPrefixWhenNaN ? LatLonDisplay.BLANK_PREFIX_LAT_FORMATTERS : LatLonDisplay.LAT_FORMATTERS;
            const lonFormatters = this.props.blankPrefixWhenNaN ? LatLonDisplay.BLANK_PREFIX_LON_FORMATTERS : LatLonDisplay.LON_FORMATTERS;
            this.valueState.sub(([value, format]) => {
                var _a, _b;
                const lat = (_a = value === null || value === void 0 ? void 0 : value.lat) !== null && _a !== void 0 ? _a : NaN;
                const lon = (_b = value === null || value === void 0 ? void 0 : value.lon) !== null && _b !== void 0 ? _b : NaN;
                this.latText.set(latFormatters[format](lat * 3600));
                this.lonText.set(lonFormatters[format](lon * 3600));
            }, true);
        }
        /** @inheritdoc */
        render() {
            var _a;
            return (msfsSdk.FSComponent.buildComponent("div", { class: (_a = this.props.class) !== null && _a !== void 0 ? _a : '' }, this.props.splitPrefix ? (msfsSdk.FSComponent.buildComponent(msfsSdk.FSComponent.Fragment, null,
                msfsSdk.FSComponent.buildComponent("div", { class: 'g-latlon-coord g-latlon-lat' },
                    msfsSdk.FSComponent.buildComponent("span", { class: "g-latlon-prefix" }, this.latPrefixText),
                    msfsSdk.FSComponent.buildComponent("span", { class: "g-latlon-num" }, this.latNumberText)),
                msfsSdk.FSComponent.buildComponent("div", { class: 'g-latlon-coord g-latlon-lon' },
                    msfsSdk.FSComponent.buildComponent("span", { class: "g-latlon-prefix" }, this.lonPrefixText),
                    msfsSdk.FSComponent.buildComponent("span", { class: "g-latlon-num" }, this.lonNumberText)))) : (msfsSdk.FSComponent.buildComponent(msfsSdk.FSComponent.Fragment, null,
                msfsSdk.FSComponent.buildComponent("div", { class: 'g-latlon-coord g-latlon-lat' }, this.latText),
                msfsSdk.FSComponent.buildComponent("div", { class: 'g-latlon-coord g-latlon-lon' }, this.lonText)))));
        }
        /** @inheritdoc */
        destroy() {
            this.valueState.destroy();
            super.destroy();
        }
        /**
         * Creates a record of latitude formatters keyed by display format.
         * @param blankPrefixWhenNaN Whether the formatters should format prefix text as an underscore when latitude is equal
         * to `NaN`.
         * @returns A record of latitude formatters keyed by display format.
         */
        static createLatFormatters(blankPrefixWhenNaN) {
            const nanPrefix = blankPrefixWhenNaN ? '_' : 'N';
            return {
                [exports.LatLonDisplayFormat.HDDD_MMmm]: msfsSdk.DmsFormatter2.create('{+[N]-[S]} {dd}°{mm.mm}\'', msfsSdk.UnitType.ARC_SEC, 0.6, `${nanPrefix} __°__.__'`),
                [exports.LatLonDisplayFormat.HDDD_MMmmm]: msfsSdk.DmsFormatter2.create('{+[N]-[S]} {dd}°{mm.mmm}\'', msfsSdk.UnitType.ARC_SEC, 0.6, `${nanPrefix} __°__.___'`),
                [exports.LatLonDisplayFormat.HDDD_MM_SSs]: msfsSdk.DmsFormatter2.create('{+[N]-[S]} {dd}°{mm}\'{ss.s}"', msfsSdk.UnitType.ARC_SEC, 0.1, `${nanPrefix} __°__'__._"`),
            };
        }
        /**
         * Creates a record of longitude formatters keyed by display format.
         * @param blankPrefixWhenNaN Whether the formatters should format prefix text as an underscore when longitude is
         * equal to `NaN`.
         * @returns A record of longitude formatters keyed by display format.
         */
        static createLonFormatters(blankPrefixWhenNaN) {
            const nanPrefix = blankPrefixWhenNaN ? '_' : 'E';
            return {
                [exports.LatLonDisplayFormat.HDDD_MMmm]: msfsSdk.DmsFormatter2.create('{+[E]-[W]}{ddd}°{mm.mm}\'', msfsSdk.UnitType.ARC_SEC, 0.6, `${nanPrefix}___°__.__'`),
                [exports.LatLonDisplayFormat.HDDD_MMmmm]: msfsSdk.DmsFormatter2.create('{+[E]-[W]}{ddd}°{mm.mmm}\'', msfsSdk.UnitType.ARC_SEC, 0.6, `${nanPrefix}___°__.___'`),
                [exports.LatLonDisplayFormat.HDDD_MM_SSs]: msfsSdk.DmsFormatter2.create('{+[E]-[W]}{ddd}°{mm}\'{ss.s}"', msfsSdk.UnitType.ARC_SEC, 0.1, `${nanPrefix}___°__'__._"`),
            };
        }
    }
    LatLonDisplay.LAT_FORMATTERS = LatLonDisplay.createLatFormatters(false);
    LatLonDisplay.BLANK_PREFIX_LAT_FORMATTERS = LatLonDisplay.createLatFormatters(true);
    LatLonDisplay.LON_FORMATTERS = LatLonDisplay.createLonFormatters(false);
    LatLonDisplay.BLANK_PREFIX_LON_FORMATTERS = LatLonDisplay.createLonFormatters(true);

    /**
     * Text display of a magnetic variation as degrees east or west.
     */
    class MagVarDisplay extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.text = typeof this.props.magvar === 'object'
                ? (this.magvarSub = this.props.magvar.map(MagVarDisplay.FORMATTER))
                : MagVarDisplay.FORMATTER(this.props.magvar);
        }
        /** @inheritdoc */
        render() {
            var _a;
            return (msfsSdk.FSComponent.buildComponent("div", { class: (_a = this.props.class) !== null && _a !== void 0 ? _a : '' }, this.text));
        }
        /** @inheritdoc */
        destroy() {
            var _a;
            super.destroy();
            (_a = this.magvarSub) === null || _a === void 0 ? void 0 : _a.destroy();
        }
    }
    MagVarDisplay.FORMATTER = (magvar) => {
        // Force any finite magnetic variation value into the domain of [-180, 180].
        const magVarCorrected = ((magvar + 540) % 360 - 180);
        // Rendering this way means we may variously show 0W or 0E depending on the fractional
        // part of the value, but this is accurate to testing with the trainer.  Start at a
        // place where the plane's magvar on the setup page is shown as 0W, then fly west, and
        // eventually it switches to 0E before proceeding on to 1E.  A value of precisely 0
        // will be shown as east, which also seems to match how the trainer displays VORs with
        // a 0 station magvar: they, empirically, all seem to show 0E.
        return `${Math.abs(magVarCorrected).toFixed(0)}°${magVarCorrected < 0 ? 'W' : 'E'}`;
    };

    /**
     * A utility class for creating Garmin unit formatters.
     *
     * Each unit formatter is a function which generates output strings from input measurement units.
     */
    class UnitFormatter {
        /**
         * Creates a function which formats measurement units to strings representing their abbreviated names.
         * @param defaultString The string to output when the input unit cannot be formatted. Defaults to the empty string.
         * @param charCase The case to enforce on the output string. Defaults to `'normal'`.
         * @returns A function which formats measurement units to strings representing their abbreviated names.
         */
        static create(defaultString = '', charCase = 'normal') {
            var _a, _b;
            switch (charCase) {
                case 'upper':
                    (_a = UnitFormatter.UNIT_TEXT_UPPER) !== null && _a !== void 0 ? _a : (UnitFormatter.UNIT_TEXT_UPPER = UnitFormatter.createUpperCase());
                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                    return (unit) => { var _a, _b; return (_b = (_a = UnitFormatter.UNIT_TEXT_UPPER[unit.family]) === null || _a === void 0 ? void 0 : _a[unit.name]) !== null && _b !== void 0 ? _b : defaultString; };
                case 'lower':
                    (_b = UnitFormatter.UNIT_TEXT_LOWER) !== null && _b !== void 0 ? _b : (UnitFormatter.UNIT_TEXT_LOWER = UnitFormatter.createLowerCase());
                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                    return (unit) => { var _a, _b; return (_b = (_a = UnitFormatter.UNIT_TEXT_LOWER[unit.family]) === null || _a === void 0 ? void 0 : _a[unit.name]) !== null && _b !== void 0 ? _b : defaultString; };
                default:
                    return (unit) => { var _a, _b; return (_b = (_a = UnitFormatter.UNIT_TEXT[unit.family]) === null || _a === void 0 ? void 0 : _a[unit.name]) !== null && _b !== void 0 ? _b : defaultString; };
            }
        }
        /**
         * Creates a record of lowercase unit abbreviated names.
         * @returns A record of lowercase unit abbreviated names.
         */
        static createLowerCase() {
            const lower = {};
            for (const family in UnitFormatter.UNIT_TEXT) {
                const familyText = UnitFormatter.UNIT_TEXT[family];
                lower[family] = {};
                for (const unit in familyText) {
                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                    lower[family][unit] = familyText[unit].toLowerCase();
                }
            }
            return lower;
        }
        /**
         * Creates a record of uppercase unit abbreviated names.
         * @returns A record of uppercase unit abbreviated names.
         */
        static createUpperCase() {
            const upper = {};
            for (const family in UnitFormatter.UNIT_TEXT) {
                const familyText = UnitFormatter.UNIT_TEXT[family];
                upper[family] = {};
                for (const unit in familyText) {
                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                    upper[family][unit] = familyText[unit].toUpperCase();
                }
            }
            return upper;
        }
        /**
         * Gets a mapping of unit family and name to text used by UnitFormatter to format units. The returned object maps
         * unit families to objects that map unit names within each family to formatted text.
         * @returns A mapping of unit family and name to text used by UnitFormatter to format units.
         */
        static getUnitTextMap() {
            return UnitFormatter.UNIT_TEXT;
        }
    }
    UnitFormatter.UNIT_TEXT = {
        [msfsSdk.UnitFamily.Distance]: {
            [msfsSdk.UnitType.METER.name]: 'M',
            [msfsSdk.UnitType.FOOT.name]: 'FT',
            [msfsSdk.UnitType.KILOMETER.name]: 'KM',
            [msfsSdk.UnitType.NMILE.name]: 'NM',
            [msfsSdk.UnitType.MILE.name]: 'SM'
        },
        [msfsSdk.UnitFamily.Angle]: {
            [msfsSdk.UnitType.DEGREE.name]: '°',
            [msfsSdk.UnitType.RADIAN.name]: 'rad'
        },
        [msfsSdk.UnitFamily.Duration]: {
            [msfsSdk.UnitType.SECOND.name]: 'SEC',
            [msfsSdk.UnitType.MINUTE.name]: 'MIN',
            [msfsSdk.UnitType.HOUR.name]: 'HR'
        },
        [msfsSdk.UnitFamily.Weight]: {
            [msfsSdk.UnitType.KILOGRAM.name]: 'KG',
            [msfsSdk.UnitType.POUND.name]: 'LB',
            [msfsSdk.UnitType.LITER_FUEL.name]: 'LT',
            [msfsSdk.UnitType.GALLON_FUEL.name]: 'GAL',
            [msfsSdk.UnitType.IMP_GALLON_FUEL.name]: 'IG'
        },
        [msfsSdk.UnitFamily.Volume]: {
            [msfsSdk.UnitType.LITER.name]: 'L',
            [msfsSdk.UnitType.GALLON.name]: 'GAL'
        },
        [msfsSdk.UnitFamily.Pressure]: {
            [msfsSdk.UnitType.HPA.name]: 'HPA',
            [msfsSdk.UnitType.IN_HG.name]: 'IN'
        },
        [msfsSdk.UnitFamily.Temperature]: {
            [msfsSdk.UnitType.CELSIUS.name]: '°C',
            [msfsSdk.UnitType.FAHRENHEIT.name]: '°F'
        },
        [msfsSdk.UnitFamily.TemperatureDelta]: {
            [msfsSdk.UnitType.DELTA_CELSIUS.name]: '°C',
            [msfsSdk.UnitType.DELTA_FAHRENHEIT.name]: '°F'
        },
        [msfsSdk.UnitFamily.Speed]: {
            [msfsSdk.UnitType.KNOT.name]: 'KT',
            [msfsSdk.UnitType.KPH.name]: 'KH',
            [msfsSdk.UnitType.MPM.name]: 'MPM',
            [msfsSdk.UnitType.FPM.name]: 'FPM'
        },
        [msfsSdk.UnitFamily.WeightFlux]: {
            [msfsSdk.UnitType.KGH.name]: 'KG/HR',
            [msfsSdk.UnitType.PPH.name]: 'LB/HR',
            [msfsSdk.UnitType.LPH_FUEL.name]: 'LT/HR',
            [msfsSdk.UnitType.GPH_FUEL.name]: 'GAL/HR',
            [msfsSdk.UnitType.IGPH_FUEL.name]: 'IG/HR'
        }
    };

    /**
     * A component which displays a number with units.
     */
    class NumberUnitDisplay extends msfsSdk.AbstractNumberUnitDisplay {
        constructor() {
            var _a;
            super(...arguments);
            this.unitFormatter = (_a = this.props.unitFormatter) !== null && _a !== void 0 ? _a : NumberUnitDisplay.DEFAULT_UNIT_FORMATTER;
            this.unitTextBigDisplay = msfsSdk.Subject.create('');
            this.unitTextSmallDisplay = msfsSdk.Subject.create('');
            this.numberText = msfsSdk.Subject.create('');
            this.unitTextBig = msfsSdk.Subject.create('');
            this.unitTextSmall = msfsSdk.Subject.create('');
        }
        /** @inheritdoc */
        onValueChanged(value) {
            this.updateDisplay(value, this.displayUnit.get());
        }
        /** @inheritdoc */
        onDisplayUnitChanged(displayUnit) {
            this.updateDisplay(this.value.get(), displayUnit);
        }
        /**
         * Updates this component's displayed number and unit text.
         * @param value The value to display.
         * @param displayUnit The unit type in which to display the value, or `null` if the value should be displayed in its
         * native unit type.
         */
        updateDisplay(value, displayUnit) {
            if (!displayUnit || !value.unit.canConvert(displayUnit)) {
                displayUnit = value.unit;
            }
            const numberValue = value.asUnit(displayUnit);
            const numberText = this.props.formatter(numberValue);
            this.numberText.set(numberText);
            NumberUnitDisplay.unitTextCache[0] = '';
            NumberUnitDisplay.unitTextCache[1] = '';
            this.unitFormatter(NumberUnitDisplay.unitTextCache, displayUnit, numberValue);
            this.unitTextBig.set(NumberUnitDisplay.unitTextCache[0]);
            this.unitTextSmall.set(NumberUnitDisplay.unitTextCache[1]);
            this.updateUnitTextVisibility(numberValue, NumberUnitDisplay.unitTextCache[0], NumberUnitDisplay.unitTextCache[1]);
        }
        /**
         * Updates whether this component's unit text spans are visible.
         * @param numberValue The numeric value displayed by this component.
         * @param unitTextBig The text to display in the big text span.
         * @param unitTextSmall The text to display in the small text span.
         */
        updateUnitTextVisibility(numberValue, unitTextBig, unitTextSmall) {
            if (this.props.hideUnitWhenNaN === true && isNaN(numberValue)) {
                this.unitTextBigDisplay.set('none');
                this.unitTextSmallDisplay.set('none');
                return;
            }
            // We have to hide the unit text when empty because an empty string will get rendered as a space.
            this.unitTextBigDisplay.set(unitTextBig === '' ? 'none' : '');
            this.unitTextSmallDisplay.set(unitTextSmall === '' ? 'none' : '');
        }
        /** @inheritdoc */
        render() {
            var _a;
            return (msfsSdk.FSComponent.buildComponent("div", { class: (_a = this.props.class) !== null && _a !== void 0 ? _a : '', style: 'white-space: nowrap;' },
                msfsSdk.FSComponent.buildComponent("span", { class: 'numberunit-num' }, this.numberText),
                msfsSdk.FSComponent.buildComponent("span", { class: 'numberunit-unit-big', style: { 'display': this.unitTextBigDisplay } }, this.unitTextBig),
                msfsSdk.FSComponent.buildComponent("span", { class: 'numberunit-unit-small', style: { 'display': this.unitTextSmallDisplay } }, this.unitTextSmall)));
        }
        /**
         * Creates the default mapping from unit to displayed text.
         * @returns The default mapping from unit to displayed text.
         */
        static createDefaultUnitTextMap() {
            const originalMap = UnitFormatter.getUnitTextMap();
            const map = {};
            for (const family in originalMap) {
                const nameMap = map[family] = {};
                const originalNameMap = originalMap[family];
                for (const name in originalNameMap) {
                    const text = nameMap[name] = ['', ''];
                    const originalText = originalNameMap[name];
                    if (originalText[0] === '°') {
                        text[0] = '°';
                        text[1] = originalText.substring(1);
                    }
                    else {
                        text[1] = originalText;
                    }
                }
            }
            return map;
        }
    }
    // We create our own map instead of using UnitFormatter.create() so that we don't have to generate new big and small
    // text substrings with every call to the default unit formatter function.
    NumberUnitDisplay.DEFAULT_UNIT_TEXT_MAP = NumberUnitDisplay.createDefaultUnitTextMap();
    /**
     * A function which formats units to default text for NumberUnitDisplay.
     * @param out The 2-tuple to which to write the formatted text, as `[bigText, smallText]`.
     * @param unit The unit to format.
     */
    NumberUnitDisplay.DEFAULT_UNIT_FORMATTER = (out, unit) => {
        var _a;
        const text = (_a = NumberUnitDisplay.DEFAULT_UNIT_TEXT_MAP[unit.family]) === null || _a === void 0 ? void 0 : _a[unit.name];
        if (text) {
            out[0] = text[0];
            out[1] = text[1];
        }
    };
    NumberUnitDisplay.unitTextCache = ['', ''];

    /**
     * A component which displays a map range value.
     */
    class MapRangeValueDisplay extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.numberUnitRef = msfsSdk.FSComponent.createRef();
            this.range = ('range' in this.props)
                ? msfsSdk.SubscribableUtils.toSubscribable(this.props.range, true)
                : msfsSdk.MappedSubject.create(([rangeArray, rangeIndex]) => {
                    var _a;
                    return (_a = rangeArray[rangeIndex]) !== null && _a !== void 0 ? _a : MapRangeValueDisplay.DEFAULT_RANGE;
                }, this.props.rangeArray, msfsSdk.SubscribableUtils.toSubscribable(this.props.rangeIndex, true));
            this.displayUnit = msfsSdk.Subject.create(null);
            this.displayUnitState = msfsSdk.CombinedSubject.create(this.range, this.props.displayUnit);
        }
        /** @inheritdoc */
        onAfterRender() {
            this.displayUnitState.sub(this.updateDisplayUnit.bind(this), true);
        }
        /**
         * Updates this component's display unit.
         */
        updateDisplayUnit() {
            const nominalDisplayUnit = this.props.displayUnit.get();
            const range = this.range.get();
            let displayUnit;
            if (nominalDisplayUnit && nominalDisplayUnit.equals(msfsSdk.UnitType.NMILE)) {
                if (range.asUnit(msfsSdk.UnitType.FOOT) <= 2501) {
                    displayUnit = msfsSdk.UnitType.FOOT;
                }
                else {
                    displayUnit = msfsSdk.UnitType.NMILE;
                }
            }
            else if (nominalDisplayUnit && nominalDisplayUnit.equals(msfsSdk.UnitType.KILOMETER)) {
                if (range.asUnit(msfsSdk.UnitType.METER) < 999) {
                    displayUnit = msfsSdk.UnitType.METER;
                }
                else {
                    displayUnit = msfsSdk.UnitType.KILOMETER;
                }
            }
            else {
                displayUnit = nominalDisplayUnit;
            }
            this.displayUnit.set(displayUnit);
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent(NumberUnitDisplay, { ref: this.numberUnitRef, value: this.range, displayUnit: this.displayUnit, formatter: msfsSdk.NumberFormatter.create({ precision: 0.01, forceDecimalZeroes: false, maxDigits: 3 }), class: this.props.class }));
        }
        /** @inheritdoc */
        destroy() {
            var _a;
            (_a = this.numberUnitRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
            this.displayUnitState.destroy();
            if (this.range instanceof msfsSdk.MappedSubject) {
                this.range.destroy();
            }
            super.destroy();
        }
    }
    MapRangeValueDisplay.DEFAULT_RANGE = msfsSdk.UnitType.NMILE.createNumber(NaN);

    /**
     * Time display formats.
     */
    exports.TimeDisplayFormat = void 0;
    (function (TimeDisplayFormat) {
        /** UTC time. */
        TimeDisplayFormat[TimeDisplayFormat["UTC"] = 0] = "UTC";
        /** Local time in 24-hour format. */
        TimeDisplayFormat[TimeDisplayFormat["Local24"] = 1] = "Local24";
        /** Local time in 12-hour format. */
        TimeDisplayFormat[TimeDisplayFormat["Local12"] = 2] = "Local12";
    })(exports.TimeDisplayFormat || (exports.TimeDisplayFormat = {}));
    /**
     * Displays time in HH:MM:SS format.
     */
    class TimeDisplay extends msfsSdk.DisplayComponent {
        constructor() {
            var _a, _b;
            super(...arguments);
            this.timeSeconds = typeof this.props.time === 'object'
                ? (this.timeSub = this.props.time.map(TimeDisplay.SECOND_PRECISION_MAP))
                : msfsSdk.Subject.create(TimeDisplay.SECOND_PRECISION_MAP(this.props.time));
            this.format = msfsSdk.SubscribableUtils.toSubscribable(this.props.format, true);
            this.localOffset = msfsSdk.SubscribableUtils.toSubscribable(this.props.localOffset, true);
            this.padHour = msfsSdk.SubscribableUtils.toSubscribable((_a = this.props.padHour) !== null && _a !== void 0 ? _a : true, true);
            this.suffixFormatter = (_b = this.props.suffixFormatter) !== null && _b !== void 0 ? _b : TimeDisplay.DEFAULT_SUFFIX_FORMATTER;
            this.suffixDisplay = msfsSdk.Subject.create('');
            this.date = new Date();
            this.hourText = msfsSdk.Subject.create('');
            this.minText = msfsSdk.Subject.create('');
            this.secText = msfsSdk.Subject.create('');
            this.suffixText = msfsSdk.Subject.create('');
            this.updateHandler = this.updateDisplayedTime.bind(this);
        }
        /** @inheritDoc */
        onAfterRender() {
            this.formatSub = this.format.sub(this.updateHandler);
            this.localOffsetSub = this.localOffset.sub(this.updateHandler);
            this.padHourSub = this.padHour.sub(this.updateHandler);
            this.timeSeconds.sub(this.updateHandler, true);
            // We have to hide the suffix text when empty because an empty string will get rendered as a space.
            this.suffixText.pipe(this.suffixDisplay, TimeDisplay.HIDE_UNIT_TEXT_PIPE);
        }
        /**
         * Updates the displayed time.
         */
        updateDisplayedTime() {
            const utcTime = this.timeSeconds.get();
            const format = this.format.get();
            let isAm = true;
            if (isNaN(utcTime)) {
                this.hourText.set('__');
                this.minText.set('__');
                this.secText.set('__');
                this.suffixText.set(this.props.hideSuffixWhenNaN ? '' : this.getSuffix(format, isAm));
            }
            else {
                const offset = format === exports.TimeDisplayFormat.UTC ? 0 : this.localOffset.get();
                const displayTime = utcTime + offset;
                this.date.setTime(displayTime);
                const hour = this.date.getUTCHours();
                isAm = hour < 12;
                const displayHour = format === exports.TimeDisplayFormat.Local12
                    ? 12 - (12 - (hour % 12)) % 12 // Need to display hours 0 and 12 as '12'
                    : hour % 24;
                const hourText = displayHour.toString();
                this.hourText.set(this.padHour.get() ? hourText.padStart(2, '0') : hourText);
                this.minText.set(this.date.getUTCMinutes().toString().padStart(2, '0'));
                this.secText.set(this.date.getUTCSeconds().toString().padStart(2, '0'));
                this.suffixText.set(this.getSuffix(format, isAm));
            }
        }
        /**
         * Gets the suffix to append to the time display.
         * @param format The format of the time display.
         * @param isAm Whether or not the current time is AM.
         * @returns The time display suffix.
         */
        getSuffix(format, isAm) {
            return this.suffixFormatter(format, isAm);
        }
        /** @inheritDoc */
        render() {
            var _a;
            return (msfsSdk.FSComponent.buildComponent("div", { class: (_a = this.props.class) !== null && _a !== void 0 ? _a : '', style: 'white-space: nowrap;' },
                msfsSdk.FSComponent.buildComponent("span", { class: 'time-hour' }, this.hourText),
                msfsSdk.FSComponent.buildComponent("span", { class: 'time-min' },
                    ":",
                    this.minText),
                msfsSdk.FSComponent.buildComponent("span", { class: 'time-sec' },
                    ":",
                    this.secText),
                msfsSdk.FSComponent.buildComponent("span", { class: 'time-suffix', style: { 'display': this.suffixDisplay } }, this.suffixText)));
        }
        /** @inheritDoc */
        destroy() {
            var _a, _b, _c, _d;
            (_a = this.timeSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.formatSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.localOffsetSub) === null || _c === void 0 ? void 0 : _c.destroy();
            (_d = this.padHourSub) === null || _d === void 0 ? void 0 : _d.destroy();
            super.destroy();
        }
    }
    TimeDisplay.SECOND_PRECISION_MAP = msfsSdk.SubscribableMapFunctions.withPrecision(1000);
    TimeDisplay.HIDE_UNIT_TEXT_PIPE = (text) => text === '' ? 'none' : '';
    /**
     * A function which formats suffixes for TimeDisplay.
     * @param format The current format used to display the time.
     * @param isAm Whether or not the current time is AM.
     * @returns The suffix to append to the displayed time.
     */
    TimeDisplay.DEFAULT_SUFFIX_FORMATTER = (format, isAm) => {
        if (format === exports.TimeDisplayFormat.UTC) {
            return 'UTC';
        }
        else if (format === exports.TimeDisplayFormat.Local24) {
            return 'LCL';
        }
        else {
            return isAm ? 'AM' : 'PM';
        }
    };

    /**
     * A status bar which depicts an on/off state.
     *
     * The root element of the status bar contains the `toggle-status-bar` CSS class by default. The root element also
     * conditionally contains the `toggle-status-bar-on` class when the status bar's bound state is on.
     */
    class ToggleStatusBar extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.cssClassSet = msfsSdk.SetSubject.create(['toggle-status-bar']);
        }
        /** @inheritdoc */
        onAfterRender() {
            this.stateSub = this.props.state.sub(state => {
                if (state) {
                    this.cssClassSet.add('toggle-status-bar-on');
                }
                else {
                    this.cssClassSet.delete('toggle-status-bar-on');
                }
            }, true);
        }
        /** @inheritdoc */
        render() {
            var _a;
            const reservedClasses = ['toggle-status-bar', 'toggle-status-bar-on'];
            if (typeof this.props.class === 'object') {
                this.cssClassSub = msfsSdk.FSComponent.bindCssClassSet(this.cssClassSet, this.props.class, reservedClasses);
            }
            else {
                for (const cssClassToAdd of msfsSdk.FSComponent.parseCssClassesFromString((_a = this.props.class) !== null && _a !== void 0 ? _a : '').filter(cssClass => !reservedClasses.includes(cssClass))) {
                    this.cssClassSet.add(cssClassToAdd);
                }
            }
            return (msfsSdk.FSComponent.buildComponent("div", { class: this.cssClassSet }));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b;
            super.destroy();
            (_a = this.stateSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.cssClassSub) === null || _b === void 0 ? void 0 : _b.destroy();
        }
    }

    /**
     * A list that handles dynamically adding and removing list items from an HTML element.
     */
    class DynamicList {
        /**
         * Creates a new instance of DynamicList.
         * @param data An array of data items to display in this list.
         * @param itemsContainer The Element to which this list's items will be rendered as children.
         * @param renderItem A function that used to render this list's items. The function is called for every list item
         * that is added to this list. If the root node returned by the function is a {@link DisplayComponent}, then its
         * `destroy()` method will be called when the item is removed from this list.
         * @param sortItems A function to sort data items before rendering them. The function should return a negative
         * number if the first item should be rendered before the second, a positive number if the first item should be
         * rendered after the second, or zero if the two items' relative order does not matter. If not defined, items will
         * be rendered in the order in which they appear in the data item array.
         */
        constructor(data, itemsContainer, renderItem, sortItems) {
            this.data = data;
            this.itemsContainer = itemsContainer;
            this.renderItem = renderItem;
            this.sortItems = sortItems;
            this.listItemCount = msfsSdk.Subject.create(0);
            this._visibleItemCount = msfsSdk.Subject.create(0);
            /** The number of visible items in this list.  */
            this.visibleItemCount = this._visibleItemCount;
            // Key everything on index instead of data item since data items are not guaranteed to be unique in the data array
            this.renderedNodes = [];
            this.visibilitySubscriptions = [];
            this.elements = [];
            this.sortIndexes = this.sortItems === undefined
                ? undefined
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                : (a, b) => this.sortItems(this.data.get(a), this.data.get(b));
            this.sortedIndexes = [];
            this.indexToSortedIndex = [];
            /**
             * Adjust the visible item count when a data item's visiblity changes.
             * @param isVisible The data item's new visibility.
             */
            this.dataItemVisibilityChanged = (isVisible) => {
                if (isVisible) {
                    this.incrementVisibleCount();
                }
                else {
                    this.decrementVisibleCount();
                }
            };
            this.renderList();
            this.dataSub = this.data.sub(this.onDataChanged.bind(this));
        }
        /**
         * Renders all of this list's items to the item container element.
         */
        renderList() {
            this.onDataAdded(0, this.data.getArray());
        }
        /**
         * Gets the sorted index of a data item index.
         * @param index A data item index.
         * @returns The index to which the specified data item index is sorted, or `-1` if the data index is out of bounds.
         */
        sortedIndexOfIndex(index) {
            var _a;
            return (_a = this.indexToSortedIndex[index]) !== null && _a !== void 0 ? _a : -1;
        }
        /**
         * Gets the sorted index of a data item.
         * @param data A data item.
         * @returns The index to which the specified data item is sorted, or `-1` if the item is not in this list.
         */
        sortedIndexOfData(data) {
            return this.sortedIndexOfIndex(this.data.getArray().indexOf(data));
        }
        /**
         * Gets the sorted index of a data item index after hidden items have been excluded.
         * @param index A data item index.
         * @returns The index to which the specified data item index is sorted after hidden items have been excluded, or `-1`
         * if the data index is out of bounds or the data item whose index was given is itself hidden.
         */
        sortedVisibleIndexOfIndex(index) {
            var _a, _b;
            let sortedIndex = this.sortedIndexOfIndex(index);
            if (sortedIndex < 0) {
                return -1;
            }
            if (((_a = this.data.get(sortedIndex).isVisible) === null || _a === void 0 ? void 0 : _a.get()) === false) {
                return -1;
            }
            for (let i = sortedIndex - 1; i >= 0; i--) {
                if (((_b = this.data.get(this.sortedIndexes[i]).isVisible) === null || _b === void 0 ? void 0 : _b.get()) === false) {
                    sortedIndex--;
                }
            }
            return sortedIndex;
        }
        /**
         * Gets the sorted index of a data item after hidden items have been excluded.
         * @param data A data item.
         * @returns The index to which the specified data item is sorted after hidden items have been excluded, or `-1` if
         * the item is not in this list or is itself hidden.
         */
        sortedVisibleIndexOfData(data) {
            var _a;
            if (((_a = data.isVisible) === null || _a === void 0 ? void 0 : _a.get()) === false) {
                return -1;
            }
            return this.sortedVisibleIndexOfIndex(this.data.getArray().indexOf(data));
        }
        /**
         * Gets the data item index of a sorted index.
         * @param sortedIndex A sorted index.
         * @returns The index of the data item that is sorted to the specified index, or `-1` if the sorted index is out of bounds.
         */
        indexOfSortedIndex(sortedIndex) {
            var _a;
            return (_a = this.sortedIndexes[sortedIndex]) !== null && _a !== void 0 ? _a : -1;
        }
        /**
         * Gets the data item index of a sorted index after hidden items have been excluded.
         * @param sortedVisibleIndex A sorted index after hidden items have been excluded.
         * @returns The index of the data item that is sorted to the specified index after hidden items have been excluded,
         * or `-1` if the sorted index is out of bounds.
         */
        indexOfSortedVisibleIndex(sortedVisibleIndex) {
            var _a;
            if (sortedVisibleIndex < 0 || sortedVisibleIndex >= this.sortedIndexes.length) {
                return -1;
            }
            for (let i = 0; i < this.sortedIndexes.length; i++) {
                if (((_a = this.data.get(this.sortedIndexes[i]).isVisible) === null || _a === void 0 ? void 0 : _a.get()) === false) {
                    continue;
                }
                if (sortedVisibleIndex > 0) {
                    sortedVisibleIndex--;
                }
                else {
                    return this.sortedIndexes[i];
                }
            }
            return -1;
        }
        /**
         * Gets the rendered instance of a data item in this list.
         * @param index The index of the data item for which to get the rendered instance.
         * @returns The rendered instance of the specified data item, or `undefined` if {@linkcode index} is out of bounds.
         */
        getRenderedItem(index) {
            var _a;
            return (_a = this.renderedNodes[index]) === null || _a === void 0 ? void 0 : _a.instance;
        }
        /**
         * Iterates over each rendered component and executes a callback function.
         * @param fn The callback function to execute for each component. The function should take two arguments: the first
         * argument is the iterated component, and the second argument is the index of the component _in the iteration_.
         * @param visibleOnly Whether to only iterate over components whose associated data items have their visibility flags
         * set to `true`. Defaults to `false`.
         * @param sortedOrder Whether to iterate over components in sorted order instead of the order in which their
         * associated data items appear in the data array. Defaults to `false`.
         */
        forEachComponent(fn, visibleOnly = false, sortedOrder = false) {
            var _a, _b;
            let iteratorIndex = 0;
            if (sortedOrder) {
                for (let i = 0; i < this.renderedNodes.length; i++) {
                    const index = this.sortedIndexes[i];
                    if (!visibleOnly || ((_a = this.data.get(index).isVisible) === null || _a === void 0 ? void 0 : _a.get()) !== false) {
                        fn(this.renderedNodes[index].instance, iteratorIndex);
                    }
                    iteratorIndex++;
                }
            }
            else {
                for (let i = 0; i < this.renderedNodes.length; i++) {
                    if (!visibleOnly || ((_b = this.data.get(i).isVisible) === null || _b === void 0 ? void 0 : _b.get()) !== false) {
                        fn(this.renderedNodes[i].instance, iteratorIndex);
                    }
                    iteratorIndex++;
                }
            }
        }
        /**
         * Responds to when this list's data item array changes.
         * @param index The index of the change.
         * @param type The type of change.
         * @param data The item that was changed.
         */
        onDataChanged(index, type, data) {
            switch (type) {
                case msfsSdk.SubscribableArrayEventType.Added:
                    this.onDataAdded(index, data);
                    break;
                case msfsSdk.SubscribableArrayEventType.Removed:
                    data !== undefined && this.onDataRemoved(index, data);
                    break;
                case msfsSdk.SubscribableArrayEventType.Cleared:
                    this.onDataCleared();
                    break;
            }
            this.listItemCount.set(this.data.length);
        }
        /**
         * Responds to when data items are added to this list's data item array.
         * @param index The index at which the data items were added.
         * @param data The data items that were added.
         */
        onDataAdded(index, data) {
            if (data !== undefined) {
                let numAdded = 0;
                if (Array.isArray(data)) {
                    for (let i = 0; i < data.length; i++) {
                        const dataItem = data[i];
                        const indexToAdd = index + i;
                        this.addDataItem(dataItem, indexToAdd);
                    }
                    numAdded = data.length;
                }
                else {
                    this.addDataItem(data, index);
                    numAdded = 1;
                }
                if (numAdded > 0) {
                    // Update the indexes in the sorted index array to account for shifting caused by the insertion of the new items.
                    for (let i = 0; i < this.sortedIndexes.length; i++) {
                        if (this.sortedIndexes[i] >= index) {
                            this.sortedIndexes[i] += numAdded;
                        }
                    }
                    // Insert the indexes of the new items at the positions where they were rendered into the DOM.
                    this.sortedIndexes.splice(index, 0, ...msfsSdk.ArrayUtils.create(numAdded, i => index + i));
                    this.reconcileSortedIndexArrays();
                    this.updateOrder();
                }
            }
        }
        /**
         * Adds a data item to the list and performs the required rendering and ordering operations.
         * @param dataItem The data item to add to the list.
         * @param indexToAdd The index to add the item at.
         */
        addDataItem(dataItem, indexToAdd) {
            // Create list item and store a reference to the instance if it is a DisplayComponent so we can destroy it later
            const listItemVNode = this.renderItem(dataItem, indexToAdd);
            this.renderedNodes.splice(indexToAdd, 0, listItemVNode);
            // Render the list item into the DOM and store a reference to the root element of the rendered item.
            // By default, we will render the item to the same index at which it appears in the data array. Therefore, if
            // this list does not support sorting, it will be in the correct position. If this list does support sorting, it
            // will be moved if necessary when the list is resorted immediately after the insertion operation.
            const elementAtIndexToInsert = this.itemsContainer.children.item(indexToAdd);
            const element = this.renderToDom(listItemVNode, elementAtIndexToInsert);
            this.elements.splice(indexToAdd, 0, element !== null && element !== void 0 ? element : undefined);
            // Update our visible items count.
            if (dataItem.isVisible === undefined || dataItem.isVisible.get() === true) {
                this.incrementVisibleCount();
            }
            // Subscribe to the item's visibility state if one is provided.
            if (dataItem.isVisible !== undefined) {
                this.visibilitySubscriptions.splice(indexToAdd, 0, dataItem.isVisible.sub(this.dataItemVisibilityChanged));
            }
            else {
                this.visibilitySubscriptions.splice(indexToAdd, 0, undefined);
            }
        }
        /**
         * Adds a list rendered DOM node to the collection.
         * @param node Item to render and add.
         * @param elementAtIndexToInsert The existing element, if any, located at the index to which to render the node.
         * @returns The created DOM element.
         */
        renderToDom(node, elementAtIndexToInsert) {
            if (elementAtIndexToInsert !== null) {
                node && elementAtIndexToInsert && msfsSdk.FSComponent.renderBefore(node, elementAtIndexToInsert);
                return elementAtIndexToInsert.previousElementSibling;
            }
            else {
                elementAtIndexToInsert = this.itemsContainer;
                node && elementAtIndexToInsert && msfsSdk.FSComponent.render(node, elementAtIndexToInsert);
                return this.itemsContainer.lastElementChild;
            }
        }
        /**
         * Responds to when data items are removed from this list's data item array.
         * @param index The index at which the data items were removed.
         * @param data The data items that were removed.
         */
        onDataRemoved(index, data) {
            let numRemoved = 0;
            if (Array.isArray(data)) {
                for (let i = 0; i < data.length; i++) {
                    const dataItem = data[i];
                    this.removeDataItem(dataItem, index + i);
                }
                numRemoved = data.length;
            }
            else {
                this.removeDataItem(data, index);
                numRemoved = 1;
            }
            if (numRemoved > 0) {
                this.renderedNodes.splice(index, numRemoved);
                this.visibilitySubscriptions.splice(index, numRemoved);
                this.elements.splice(index, numRemoved);
                // Update the indexes in the sorted index array to account for shifting caused by the removal of items.
                for (let i = 0; i < this.sortedIndexes.length; i++) {
                    const diff = this.sortedIndexes[i] - index;
                    if (diff < 0) {
                        // Index is less than the range of removed indexes -> do nothing.
                        continue;
                    }
                    else if (diff >= numRemoved) {
                        // Index is greater than the range of removed indexes -> decrement the index by the number of removed indexes.
                        this.sortedIndexes[i] -= numRemoved;
                    }
                    else {
                        // Index is within the range of removed indexes -> remove the index from the array.
                        this.sortedIndexes.splice(i--, 1);
                    }
                }
                this.reconcileSortedIndexArrays();
            }
        }
        /**
         * Removes a data item from the list.
         * @param data The data item to remove.
         * @param index The index of the data that was removed.
         */
        removeDataItem(data, index) {
            var _a;
            this.removeDomNode(data, index);
            msfsSdk.FSComponent.shallowDestroy(this.renderedNodes[index]);
            if (data.isVisible === undefined || data.isVisible.get() === true) {
                this.decrementVisibleCount();
            }
            (_a = this.visibilitySubscriptions[index]) === null || _a === void 0 ? void 0 : _a.destroy();
        }
        /**
         * Removes a rendered DOM node corresponding to a data item from this list's item container.
         * @param data The data item to remove.
         * @param index The index of the data item to remove.
         */
        removeDomNode(data, index) {
            const toRemove = this.elements[index];
            if (toRemove !== undefined) {
                this.itemsContainer.removeChild(toRemove);
            }
            else {
                console.warn('DynamicList: could not find DOM node to remove');
            }
        }
        /**
         * Responds to when this list's data item array is cleared.
         */
        onDataCleared() {
            this.itemsContainer.innerHTML = '';
            this.sortedIndexes.length = 0;
            this.indexToSortedIndex.length = 0;
            for (let i = 0; i < this.renderedNodes.length; i++) {
                msfsSdk.FSComponent.shallowDestroy(this.renderedNodes[i]);
            }
            this.renderedNodes.length = 0;
            this.visibilitySubscriptions.forEach(x => x === null || x === void 0 ? void 0 : x.destroy());
            this.visibilitySubscriptions.length = 0;
            this._visibleItemCount.set(0);
            this.elements.length = 0;
        }
        /**
         * Increments this list's visible item count by 1.
         */
        incrementVisibleCount() {
            this._visibleItemCount.set(this._visibleItemCount.get() + 1);
        }
        /**
         * Decrements this list's visible item count by 1.
         */
        decrementVisibleCount() {
            this._visibleItemCount.set(this._visibleItemCount.get() - 1);
        }
        /**
         * Updates the array which maps data indexes to sorted indexes to match the sorting order provided by the
         * sorted index array.
         */
        reconcileSortedIndexArrays() {
            for (let i = 0; i < this.sortedIndexes.length; i++) {
                this.indexToSortedIndex[this.sortedIndexes[i]] = i;
            }
        }
        /**
         * Updates the order of the rendered items in this list.
         */
        updateOrder() {
            if (this.sortIndexes === undefined) {
                return;
            }
            const oldOrder = Array.from(this.sortedIndexes);
            this.sortedIndexes.sort(this.sortIndexes);
            if (msfsSdk.ArrayUtils.equals(this.sortedIndexes, oldOrder)) {
                return;
            }
            this.reconcileSortedIndexArrays();
            for (let i = 0; i < this.sortedIndexes.length; i++) {
                const element = this.elements[this.sortedIndexes[i]];
                if (element !== undefined) {
                    this.itemsContainer.appendChild(element);
                }
            }
        }
        /**
         * Destroys this list.
         */
        destroy() {
            this.dataSub.destroy();
            this.onDataCleared();
        }
    }

    /**
     * A scrollable list.
     */
    class ScrollList extends msfsSdk.DisplayComponent {
        constructor() {
            var _a, _b, _c, _d;
            super(...arguments);
            this.rootRef = msfsSdk.FSComponent.createRef();
            this.translatableRef = msfsSdk.FSComponent.createRef();
            this.itemsContainerRef = msfsSdk.FSComponent.createRef();
            this.listItemLengthPxProp = msfsSdk.SubscribableUtils.toSubscribable(this.props.listItemLengthPx, true);
            this.listItemSpacingPxProp = msfsSdk.SubscribableUtils.toSubscribable((_a = this.props.listItemSpacingPx) !== null && _a !== void 0 ? _a : 0, true);
            this.itemsPerPageProp = this.props.itemsPerPage === undefined ? undefined : msfsSdk.SubscribableUtils.toSubscribable(this.props.itemsPerPage, true);
            this.listItemLengthPx = msfsSdk.Subject.create(this.listItemLengthPxProp.get());
            this.listItemSpacingPx = msfsSdk.Subject.create(this.listItemSpacingPxProp.get());
            this.itemCount = msfsSdk.SubscribableUtils.toSubscribable(this.props.itemCount, true);
            /** The axis along which this list scrolls. */
            this.scrollAxis = (_b = this.props.scrollAxis) !== null && _b !== void 0 ? _b : 'y';
            this._itemsPerPage = this.itemsPerPageProp === undefined ? undefined : msfsSdk.Subject.create(this.itemsPerPageProp.get());
            /**
             * The number of visible list items per page displayed by this list, or `undefined` if the number of items per page
             * is not prescribed.
             */
            this.itemsPerPage = this._itemsPerPage;
            this.snapToItem = this.itemsPerPage !== undefined;
            this._listItemLengthWithMarginPx = msfsSdk.MappedSubject.create(([listItemLengthPx, listItemSpacingPx]) => listItemLengthPx + listItemSpacingPx, this.listItemLengthPx, this.listItemSpacingPx);
            /** The length, in pixels, of one item in this list plus its margin along this list's scroll axis. */
            this.listItemLengthWithMarginPx = this._listItemLengthWithMarginPx;
            this._totalLengthPx = msfsSdk.MappedSubject.create(([listItemLengthPx, listItemSpacingPx, itemCount]) => {
                return listItemLengthPx * Math.max(itemCount, 0) + listItemSpacingPx * Math.max(itemCount - 1, 0);
            }, this.listItemLengthPx, this.listItemSpacingPx, this.itemCount);
            /** The total length, in pixels, of all items in this list plus their margins along this list's scroll axis. */
            this.totalLengthPx = this._totalLengthPx;
            this._lengthPx = this.props.lengthPx === undefined
                ? this.itemsPerPage === undefined
                    ? msfsSdk.Subject.create(100)
                    : msfsSdk.MappedSubject.create(([listItemLengthPx, listItemSpacingPx, itemsPerPage]) => {
                        return listItemLengthPx * Math.max(itemsPerPage, 0) + listItemSpacingPx * Math.max(itemsPerPage - 1, 0);
                    }, this.listItemLengthPx, this.listItemSpacingPx, this.itemsPerPage)
                : msfsSdk.SubscribableUtils.toSubscribable(this.props.lengthPx, true);
            /** The visible length of this list, in pixels, along its scroll axis. */
            this.lengthPx = this._lengthPx;
            /** The length of one page, in pixels, along this list's scroll axis. */
            this.pageLength = this.itemsPerPage === undefined
                ? this._lengthPx.map(msfsSdk.SubscribableMapFunctions.identity())
                : msfsSdk.MappedSubject.create(([itemLengthWithMarginPx, itemsPerPage]) => {
                    return itemLengthWithMarginPx * itemsPerPage;
                }, this._listItemLengthWithMarginPx, this.itemsPerPage);
            this._maxScrollPos = msfsSdk.MappedSubject.create(([totalLengthPx, lengthPx]) => {
                return Math.max(totalLengthPx - lengthPx, 0);
            }, this._totalLengthPx, this._lengthPx);
            /** This list's maximum allowed scroll position, in pixels. Does not include overscroll. */
            this.maxScrollPos = this._maxScrollPos;
            /** How many pixels we will allow overscrolling before stopping. */
            this.maxOverscrollPx = msfsSdk.SubscribableUtils.toSubscribable((_c = this.props.maxOverscrollPx) !== null && _c !== void 0 ? _c : this.listItemLengthPx, true);
            this._scrollPos = msfsSdk.Subject.create(0);
            /**
             * This list's current scroll position, in pixels. The scroll position is zero when the list is scrolled to the
             * beginning (without overscroll) and increases as the list is scrolled toward the end.
             */
            this.scrollPos = this._scrollPos;
            /**
             * This list's current scroll position, normalized such that 0 represents when the list is scrolled to the beginning
             * (without overscroll) and 1 represents when the list is scrolled to the end (without overscroll).
             */
            this.scrollPosFraction = msfsSdk.MappedSubject.create(([scrollPos, maxScrollPos, pageLength]) => {
                if (maxScrollPos > 0) {
                    return scrollPos / maxScrollPos;
                }
                else {
                    // This is used when itemCount <= itemsPerPage
                    if (scrollPos > 0) {
                        return (scrollPos / pageLength) + 1;
                    }
                    else if (scrollPos < 0) {
                        return scrollPos / pageLength;
                    }
                    else {
                        return 0;
                    }
                }
            }, this._scrollPos, this._maxScrollPos, this.pageLength);
            /**
             * The fraction of this list's visible length compared to the total length of all items in this list plus their
             * margins along this list's scroll axis.
             */
            this.scrollBarLengthFraction = msfsSdk.MappedSubject.create(([totalLengthPx, lengthPx]) => {
                return Math.min(1, lengthPx / totalLengthPx);
            }, this._totalLengthPx, this._lengthPx);
            this._animationTargetScrollPos = msfsSdk.Subject.create(undefined);
            /**
             * The scroll position targeted by this list's current scrolling animation, in pixels, or `undefined` if scrolling is
             * not currently animated or the animation has no defined target scroll position.
             */
            this.animationTargetScrollPos = this._animationTargetScrollPos;
            /**
             * This list's current target scroll position, in pixels. The target scroll position is equal to the current
             * animation target if it is defined; otherwise it is equal to the current scroll position.
             */
            this.targetScrollPos = msfsSdk.MappedSubject.create(([animatedPos, scrollPos]) => animatedPos !== null && animatedPos !== void 0 ? animatedPos : scrollPos, this._animationTargetScrollPos, this._scrollPos);
            this._firstVisibleIndex = msfsSdk.MappedSubject.create(([scrollPos, listItemLengthWithMarginPx]) => {
                return Math.max(0, Math.round(scrollPos / listItemLengthWithMarginPx));
            }, this._scrollPos, this._listItemLengthWithMarginPx);
            // The index of the first item in this list that is visible with the list's current scroll position.
            this.firstVisibleIndex = this._firstVisibleIndex;
            this.maxRenderedItemCount = this.itemsPerPage === undefined
                ? undefined
                : msfsSdk.MappedSubject.create(([itemsPerPage, desiredMax]) => Math.max(3, itemsPerPage * 3, desiredMax), this.itemsPerPage, msfsSdk.SubscribableUtils.toSubscribable((_d = this.props.maxRenderedItemCount) !== null && _d !== void 0 ? _d : Infinity, true));
            this.renderWindowStartIndex = msfsSdk.Subject.create(0);
            this.renderWindowStartPos = msfsSdk.MappedSubject.create(([index, itemLength]) => index * itemLength, this.renderWindowStartIndex, this._listItemLengthWithMarginPx);
            /** This list's current scroll position adjusted for the render window. */
            this.trueScrollPos = msfsSdk.MappedSubject.create(([scrollPos, windowStartPos]) => scrollPos - windowStartPos, this._scrollPos, this.renderWindowStartPos);
            this._renderWindow = msfsSdk.Vec2Subject.create(msfsSdk.Vec2Math.create(0, Infinity));
            /**
             * The window of rendered list items, as `[startIndex, endIndex]`, where `startIndex` is the index of the first
             * rendered item, inclusive, and `endIndex` is the index of the last rendered item, exclusive.
             */
            this.renderWindow = this._renderWindow;
            this.animateFunc = this.animate.bind(this);
            this.isAnimating = false;
            this.velocity = 0;
            this.lastTimeSeconds = 0;
            this.deltaTimeSeconds = 0;
            this.timeInOverscrollSeconds = 0;
            /** How long to wait while overscrolled before snapping back. */
            this.maxTimeInOverscrollSeconds = 0.5;
            /** Once at or below this velocity, we pick that target Y to snap to. */
            this.snappingTransitionSpeed = 200;
            this.goToAnimationTargetPos = false;
            this.listItemParamSubs = [];
        }
        /** @inheritDoc */
        onAfterRender() {
            if (this._itemsPerPage && this.itemsPerPageProp) {
                this.listItemLengthPx.set(this.listItemLengthPxProp.get());
                this.listItemSpacingPx.set(this.listItemSpacingPxProp.get());
                this._itemsPerPage.set(this.itemsPerPageProp.get());
                this.listItemParamSubs.push(this.listItemLengthPxProp.sub(this.onListItemParamChanged.bind(this, this.listItemLengthPx)), this.listItemSpacingPxProp.sub(this.onListItemParamChanged.bind(this, this.listItemSpacingPx)), this.itemsPerPageProp.sub(this.onListItemParamChanged.bind(this, this._itemsPerPage)));
            }
            else {
                this.listItemParamSubs.push(this.listItemLengthPxProp.pipe(this.listItemLengthPx), this.listItemSpacingPxProp.pipe(this.listItemSpacingPx));
            }
            this._lengthPx.sub(lengthPx => {
                this.rootRef.instance.style.setProperty('--scroll-list-length', lengthPx + 'px');
            }, true);
            this.listItemLengthPx.sub(listItemLengthPx => {
                this.rootRef.instance.style.setProperty('--scroll-list-item-length', listItemLengthPx + 'px');
            }, true);
            this.listItemSpacingPx.sub(listItemSpacingPx => {
                this.rootRef.instance.style.setProperty('--scroll-list-item-margin', listItemSpacingPx + 'px');
            }, true);
            if (this.itemsPerPage && this.maxRenderedItemCount) {
                const updateRenderWindow = this.updateRenderWindow.bind(this);
                this._listItemLengthWithMarginPx.sub(updateRenderWindow);
                this.itemsPerPage.sub(updateRenderWindow);
                this._scrollPos.sub(updateRenderWindow);
                this.maxRenderedItemCount.sub(updateRenderWindow);
            }
            this.trueScrollPos.sub((this.scrollAxis === 'x' ? this.updateTransformX : this.updateTransformY).bind(this), true);
            this._maxScrollPos.sub(this.ensureScrollIsInBounds.bind(this));
        }
        /**
         * Returns a reference to the element where the list items should be added.
         * @returns A reference to the element where the list items should be added.
         */
        getContainerRef() {
            return this.itemsContainerRef.instance;
        }
        /**
         * Scrolls backward by one full page length.
         */
        pageBack() {
            const startingPoint = this.targetScrollPos.get();
            // If scrolled to beginning already, do nothing
            if (startingPoint <= 0) {
                return;
            }
            const desired = startingPoint - this.pageLength.get();
            this.executeScrollTo(this.snapToItem ? this.pickNearestSnapToPos(desired) : desired, true);
        }
        /**
         * Scrolls forward by one full page length.
         */
        pageForward() {
            const startingPoint = this.targetScrollPos.get();
            // If scrolled to end already, do nothing
            if (startingPoint >= this._maxScrollPos.get()) {
                return;
            }
            const desired = startingPoint + this.pageLength.get();
            this.executeScrollTo(this.snapToItem ? this.pickNearestSnapToPos(desired) : desired, true);
        }
        /**
         * Scrolls until the item at a specified index is in view.
         * @param index The index of the item to which to scroll.
         * @param position The position to place the target item at the end of the scroll. Position `0` is the top/left-most
         * visible slot, position `1` is the next slot, and so on. Values greater than or equal to the number of visible
         * items per page will be clamped. Negative values will be interpreted as counting backwards from the
         * bottom/right-most visible slot starting with `-1`. Ignored if this list does not support snapping to list items.
         * @param animate Whether to animate the scroll.
         */
        scrollToIndex(index, position, animate) {
            var _a, _b;
            if (index < 0 || index >= this.itemCount.get()) {
                return;
            }
            const itemLength = this.listItemLengthPx.get();
            const itemLengthWithMargin = this._listItemLengthWithMarginPx.get();
            const targetItemStartPos = index * itemLengthWithMargin;
            const targetItemEndPos = targetItemStartPos + itemLength;
            const currentPageStartPos = this.targetScrollPos.get();
            const currentPageEndPos = currentPageStartPos + this._lengthPx.get();
            const itemsPerPage = (_b = (_a = this.itemsPerPage) === null || _a === void 0 ? void 0 : _a.get()) !== null && _b !== void 0 ? _b : 1;
            if (position < 0) {
                position += itemsPerPage;
                position = Math.max(position, 0);
            }
            else {
                position = Math.min(position, itemsPerPage - 1);
            }
            const itemCountBefore = position;
            const itemCountAfter = itemsPerPage - position - 1;
            const targetPageStartPos = targetItemStartPos - itemCountBefore * itemLengthWithMargin;
            const targetPageEndPos = targetItemEndPos + itemCountAfter * itemLengthWithMargin;
            let scrollTarget = undefined;
            if (currentPageStartPos < targetPageStartPos || (!this.snapToItem && itemLength > this._lengthPx.get())) {
                scrollTarget = targetPageStartPos;
            }
            else if (currentPageEndPos > targetPageEndPos) {
                scrollTarget = targetPageEndPos - this._lengthPx.get();
            }
            if (scrollTarget === undefined) {
                return;
            }
            this.executeScrollTo(scrollTarget, animate);
        }
        /**
         * Scrolls the minimum possible distance until the item at a specified index is in view with a given margin from the
         * edges of the visible list.
         * @param index The index of the item to which to scroll.
         * @param margin The margin from the edges of the visible list to respect when scrolling to the target item. In other
         * words, the scrolling operation will attempt to place the target item at least as far from the edges of the visible
         * list as the specified margin. If this list supports snapping to items, then the margin should be expressed as an
         * item count. If this list does not support snapping to items, then the margin should be expressed as pixels. The
         * margin will be clamped between zero and the largest possible value such that an item can be placed within the
         * visible list while respecting the margin value on both sides.
         * @param animate Whether to animate the scroll.
         */
        scrollToIndexWithMargin(index, margin, animate) {
            if (index < 0 || index >= this.itemCount.get()) {
                return;
            }
            const itemLength = this.listItemLengthPx.get();
            const itemLengthWithMargin = this._listItemLengthWithMarginPx.get();
            const targetItemStartPos = index * itemLengthWithMargin;
            const targetItemEndPos = targetItemStartPos + itemLength;
            const currentPageStartPos = this.targetScrollPos.get();
            const currentPageEndPos = currentPageStartPos + this._lengthPx.get();
            if (this.itemsPerPage) {
                margin = Math.max(0, Math.min(margin, Math.ceil(this.itemsPerPage.get() / 2) - 1));
                margin *= itemLengthWithMargin;
            }
            else {
                margin = Math.max(0, Math.min(margin, (this._lengthPx.get() - itemLength) / 2));
            }
            const targetPageStartPos = targetItemStartPos - margin;
            const targetPageEndPos = targetItemEndPos + margin;
            let scrollTarget = undefined;
            if (currentPageStartPos > targetPageStartPos || (itemLength > this._lengthPx.get())) {
                scrollTarget = targetPageStartPos;
            }
            else if (currentPageEndPos < targetPageEndPos) {
                scrollTarget = targetPageEndPos - this._lengthPx.get();
            }
            if (scrollTarget === undefined) {
                return;
            }
            this.executeScrollTo(scrollTarget, animate);
        }
        /**
         * Executes a scroll to a specifed position.
         * @param pos The position to which to scroll.
         * @param animate Whether to animate the scroll.
         */
        executeScrollTo(pos, animate) {
            pos = msfsSdk.MathUtils.clamp(pos, 0, this._maxScrollPos.get());
            this.stopAnimating();
            if (animate) {
                this.goToAnimationTargetPos = true;
                this._animationTargetScrollPos.set(pos);
                this.startAnimating();
            }
            else {
                this._scrollPos.set(pos);
            }
        }
        /**
         * Reset the animation vars and start the animation, if not already started.
         */
        startAnimating() {
            if (this.isAnimating) {
                return;
            }
            this.isAnimating = true;
            this.lastTimeSeconds = Date.now() / 1000;
            this.timeInOverscrollSeconds = 0;
            this.interval = window.setInterval(this.animateFunc, 0);
        }
        /** Stop the animation. */
        stopAnimating() {
            window.clearInterval(this.interval);
            this.isAnimating = false;
            this.goToAnimationTargetPos = false;
            this._animationTargetScrollPos.set(undefined);
        }
        /**
         * Called once per animation frame while we are animating.
         */
        animate() {
            if (!this.isAnimating) {
                return;
            }
            const timeSeconds = Date.now() / 1000;
            /** Seconds since last animation frame. */
            let deltaTimeSeconds = timeSeconds - this.lastTimeSeconds;
            if (deltaTimeSeconds === 0) {
                deltaTimeSeconds = this.deltaTimeSeconds;
            }
            /** Whether we have been in an overscrolled state for too long, and now it's time to snapback. */
            const overstayedOverscroll = this.timeInOverscrollSeconds > this.maxTimeInOverscrollSeconds;
            const isAboveMinVelocity = Math.abs(this.velocity) > (this.snapToItem ? this.snappingTransitionSpeed : 0);
            if (this.goToAnimationTargetPos) {
                this.animateSnapping(deltaTimeSeconds);
            }
            else if ((this.isOverscrolled() || isAboveMinVelocity) && this.velocity !== 0 && !overstayedOverscroll) {
                // Animate velocity until we slow down enough to where we can pick what list item to snap to,
                // or until we have been overscrolling for too long.
                this.animateVelocity(deltaTimeSeconds);
            }
            else if (this.scrollPosFraction.get() < 0 || this.scrollPosFraction.get() > 1) {
                this.animateSnapback();
            }
            else {
                this.animateSnapping(deltaTimeSeconds);
            }
            this.lastTimeSeconds = timeSeconds;
            this.deltaTimeSeconds = deltaTimeSeconds;
            // If we are overscrolled, keep track of how long we are overscrolled.
            if (this.getOverscrollPx() !== 0) {
                this.timeInOverscrollSeconds += deltaTimeSeconds;
            }
        }
        /**
         * Applies the velocity to the scroll position, which gives it the "flick" effect.
         * Also slows the velocity down overtime.
         * @param deltaTimeSeconds Seconds since last animation frame.
         */
        animateVelocity(deltaTimeSeconds) {
            const decel = this.getOverscrollPx() === 0 ? 1 : 10;
            // Slow the velocity down over time
            this.velocity *= Math.max(0, 1 - decel * deltaTimeSeconds);
            const maxOverscrollPx = this.maxOverscrollPx.get();
            // Apply velocity to the scroll position
            this._scrollPos.set(msfsSdk.MathUtils.clamp(this._scrollPos.get() + ((this.velocity * this.getDampening(Math.sign(this.velocity))) * deltaTimeSeconds), -maxOverscrollPx, this._maxScrollPos.get() + maxOverscrollPx));
            // If it has slowed down enough, or hit max overscroll, kill the velocity,
            // so that it can stop animating, or start snapping back
            if (Math.abs(this.velocity) < 10 || this.getDampening(Math.sign(this.velocity)) === 0) {
                this.velocity = 0;
            }
        }
        /**
         * Animates scrolling back when overscrolled.
         */
        animateSnapback() {
            const currentScrollPos = this._scrollPos.get();
            if (this.scrollPosFraction.get() < 0) {
                const newScrollPos = currentScrollPos / 2;
                if (newScrollPos >= -1) {
                    this._scrollPos.set(0);
                    this.stopAnimating();
                }
                else {
                    this._scrollPos.set(newScrollPos);
                }
            }
            else {
                const maxScrollPos = this._maxScrollPos.get();
                const diff = currentScrollPos - maxScrollPos;
                const delta = diff / 2;
                const newScrollPos = currentScrollPos - delta;
                if (newScrollPos <= maxScrollPos + 1) {
                    this._scrollPos.set(maxScrollPos);
                    this.stopAnimating();
                }
                else {
                    this._scrollPos.set(newScrollPos);
                }
            }
        }
        /**
         * If we have a target Y position to stop scrolling at, animate towards a smooth stop right at that point.
         * @param deltaTimeSeconds  Seconds since last animation frame.
         */
        animateSnapping(deltaTimeSeconds) {
            let targetScrollPos = this._animationTargetScrollPos.get();
            // Need to pick a target value to scroll to
            if (targetScrollPos === undefined) {
                if (this.velocity === 0) {
                    // Velocity could be 0 when user lets go of mouse. In this case if snapping is supported, we need
                    // to pick the closest item to snap to. If snapping to item is not supported, we will just set the target
                    // to the current scroll in order to end the animation immediately.
                    targetScrollPos = this.snapToItem ? this.pickNearestSnapToPos(this._scrollPos.get()) : this._scrollPos.get();
                    this.velocity = targetScrollPos > this._scrollPos.get()
                        ? this.snappingTransitionSpeed
                        : -this.snappingTransitionSpeed;
                }
                else {
                    // This is when the user flick scrolled, and it has slowed down to a reasonable speed
                    // to where we can now pick a stopping point in the direction that we are already moving.
                    // Note that if we are in this case snapping to item must be supported.
                    if (Math.sign(this.velocity) < 0) {
                        const adjustment = this._scrollPos.get() % this._listItemLengthWithMarginPx.get();
                        targetScrollPos = this._scrollPos.get() - adjustment;
                    }
                    else {
                        const adjustment = this._listItemLengthWithMarginPx.get() - (this._scrollPos.get() % this._listItemLengthWithMarginPx.get());
                        targetScrollPos = this._scrollPos.get() + adjustment;
                    }
                }
            }
            // If we have reached our snapping destination, stop
            if (this._scrollPos.get() === targetScrollPos) {
                this.stopAnimating();
                return;
            }
            this._animationTargetScrollPos.set(targetScrollPos);
            let direction;
            const absDistanceToTargetPos = Math.abs(this._scrollPos.get() - targetScrollPos);
            if (this.goToAnimationTargetPos) {
                // This is currently used for arrow button scrolling
                direction = targetScrollPos > this._scrollPos.get() ? 1 : -1;
                const speed = Math.sqrt(absDistanceToTargetPos) * 80;
                this.velocity = speed * direction;
            }
            else {
                // This is used when snapping at the end of a flick,
                // or snapping after letting go in the middle of an item
                direction = Math.sign(this.velocity);
                // 0 when at max distance, approaches 1 as it gets closer.
                const snapCloseness = msfsSdk.MathUtils.clamp((this._listItemLengthWithMarginPx.get() - absDistanceToTargetPos) / this._listItemLengthWithMarginPx.get(), 0, 1);
                // Approaches snappingTransitionSpeed as snapCloseness approaches 0.
                // It's curved so that it rapidly slows down when it gets really close to the stopping position.
                const minVelocity = Math.max(10, this.snappingTransitionSpeed * (-(snapCloseness ** 5) + 1));
                // Gradually slow down the velocity
                this.velocity = this.velocity - (this.velocity * (10 * deltaTimeSeconds));
                // Apply the min velocity curve thing
                if (direction > 0) {
                    this.velocity = Math.max(minVelocity, this.velocity);
                }
                else {
                    this.velocity = Math.min(-minVelocity, this.velocity);
                }
            }
            const maxOverscrollPx = this.maxOverscrollPx.get();
            // Apply velocity to the scroll position
            this._scrollPos.set(msfsSdk.MathUtils.clamp(this._scrollPos.get() + (this.velocity * deltaTimeSeconds), -maxOverscrollPx, this._maxScrollPos.get() + maxOverscrollPx));
            // If we have scrolled past our target, set scroll position to the target and stop
            if (direction > 0) {
                if (this._scrollPos.get() > targetScrollPos) {
                    this._scrollPos.set(targetScrollPos);
                    this.stopAnimating();
                }
            }
            else {
                if (this._scrollPos.get() < targetScrollPos) {
                    this._scrollPos.set(targetScrollPos);
                    this.stopAnimating();
                }
            }
        }
        /**
         * Picks the scroll position, in pixels, of the snap-to target that is nearest to a given scroll position.
         * @param pos The scroll position, in pixels, for which to find the nearest snap-to target.
         * @returns The scroll position, in pixels, of the snap-to target that is nearest to the specified scroll
         * position.
         */
        pickNearestSnapToPos(pos) {
            return msfsSdk.MathUtils.clamp(msfsSdk.MathUtils.round(pos, this._listItemLengthWithMarginPx.get()), 0, this._maxScrollPos.get());
        }
        /**
         * Responds to when one of this list's item parameters changes when the list supports snapping to items.
         * @param pipeTo The mutable subscribable to which to pipe the new parameter value.
         * @param value The new parameter value.
         */
        onListItemParamChanged(pipeTo, value) {
            // If a list item parameter changes, then after the change the list's scroll position may be misaligned with
            // respect to item snapping. Therefore, we will store the first visible item in the list before the parameter
            // change and force the list to snap to the stored item after the parameter change.
            const firstVisibleIndex = this._firstVisibleIndex.get();
            pipeTo.set(value);
            this.scrollToIndex(firstVisibleIndex, 0, false);
        }
        /**
         * Updates this list's item render window.
         */
        updateRenderWindow() {
            if (!this.itemsPerPage || !this.maxRenderedItemCount) {
                return;
            }
            const scrollPos = msfsSdk.MathUtils.clamp(this._scrollPos.get(), 0, this._maxScrollPos.get());
            const itemLength = this._listItemLengthWithMarginPx.get();
            const itemsPerPage = this.itemsPerPage.get();
            const renderCount = this.maxRenderedItemCount.get();
            const windowStartPos = this.renderWindowStartPos.get();
            const windowLength = (renderCount - itemsPerPage) * itemLength;
            const windowEndLength = windowStartPos + windowLength;
            if (scrollPos >= windowStartPos && scrollPos < windowEndLength) {
                // We are still within the rendered window, so nothing to do.
                return;
            }
            this.changeRenderWindow(scrollPos, renderCount, itemLength, windowLength);
        }
        /**
         * Changes this list's item render window.
         * @param scrollPos The scroll position on which to center the new render window, in pixels.
         * @param renderCount The number of items to render in the new window.
         * @param itemLength The length of each item to render, including margin, in pixels.
         * @param windowLength The length of the render window, in pixels.
         */
        changeRenderWindow(scrollPos, renderCount, itemLength, windowLength) {
            const newWindowStartLength = scrollPos - windowLength / 2;
            const newWindowStartIndex = Math.max(0, Math.round(newWindowStartLength / itemLength));
            this.renderWindowStartIndex.set(newWindowStartIndex);
            this._renderWindow.set(newWindowStartIndex, newWindowStartIndex + renderCount);
        }
        /**
         * Updates this list's item container's x-transform based on the current true scroll position.
         * @param scrollPos The current true scroll position, in pixels.
         */
        updateTransformX(scrollPos) {
            this.translatableRef.instance.style.transform = `translate3d(${-scrollPos}px, 0px, 0)`;
        }
        /**
         * Updates this list's item container's y-transform based on the current true scroll position.
         * @param scrollPos The current true scroll position, in pixels.
         */
        updateTransformY(scrollPos) {
            this.translatableRef.instance.style.transform = `translate3d(0px, ${-scrollPos}px, 0)`;
        }
        /**
         * Checks if the list is scrolled past the maximum limit, and if so, snaps the list back to the limit.
         */
        ensureScrollIsInBounds() {
            const max = this._maxScrollPos.get();
            const current = this._scrollPos.get();
            if (current <= max) {
                return;
            }
            this.executeScrollTo(max, false);
        }
        /**
         * Returns a number used to dampen the mouse movement when overscrolled.
         * @param direction What direction os the mouse moving in.
         * @returns a number used to dampen the mouse movement when overscrolled.
         */
        getDampening(direction) {
            const maxScrollPos = this.maxScrollPos.get();
            const maxOverscrollPx = this.maxOverscrollPx.get();
            // If we can't scroll at all, always dampen velocity to zero.
            if (maxScrollPos <= 0 && maxOverscrollPx <= 0) {
                return 0;
            }
            const scrollPosFraction = this.scrollPosFraction.get();
            const overscrollDirection = scrollPosFraction >= 1 ? 1 : scrollPosFraction <= 0 ? -1 : 0;
            // If we are not trying to increase overscroll, then do not dampen velocity.
            if (overscrollDirection !== direction) {
                return 1;
            }
            if (maxOverscrollPx > 0) {
                // If we can overscroll, then dampen velocity to zero if we are at the overscroll limit or by half if we are not
                // at the limit.
                const overscrollPercentage = Math.min(1, this.getOverscrollPx() / maxOverscrollPx);
                return overscrollPercentage === 1 ? 0 : 0.5;
            }
            else {
                // If we can't overscroll, then always dampen velocity to zero.
                return 0;
            }
        }
        /**
         * Gets the distance by which this list is currently overscrolled, in pixels along the scroll axis.
         * @returns The distance by which this list is currently overscrolled, in pixels along the scroll axis.
         */
        getOverscrollPx() {
            if (this.scrollPosFraction.get() < 0) {
                return -this._scrollPos.get();
            }
            else if (this.scrollPosFraction.get() > 1) {
                return this._scrollPos.get() - this._maxScrollPos.get();
            }
            else {
                return 0;
            }
        }
        /**
         * Returns whether this list is currently overscrolled.
         * @returns Whether this list is currently overscrolled.
         */
        isOverscrolled() {
            return this.getOverscrollPx() !== 0;
        }
        /** @inheritDoc */
        render() {
            let cssClass;
            if (typeof this.props.class === 'object') {
                cssClass = msfsSdk.SetSubject.create();
                cssClass.add('scroll-list');
                cssClass.add(`scroll-list-${this.scrollAxis}`);
                this.cssClassSub = msfsSdk.FSComponent.bindCssClassSet(cssClass, this.props.class, this.getReservedCssClasses());
            }
            else {
                cssClass = `scroll-list scroll-list-${this.scrollAxis}`;
                if (this.props.class) {
                    const reserved = this.getReservedCssClasses();
                    cssClass += ` ${msfsSdk.FSComponent.parseCssClassesFromString(this.props.class, classToFilter => !reserved.includes(classToFilter)).join(' ')}`;
                }
            }
            this.childrenNode = this.props.children === undefined ? undefined : msfsSdk.FSComponent.buildComponent(msfsSdk.FSComponent.Fragment, null, this.props.children);
            return (msfsSdk.FSComponent.buildComponent("div", { ref: this.rootRef, class: cssClass },
                msfsSdk.FSComponent.buildComponent("div", { ref: this.translatableRef, class: 'scroll-list-translatable' },
                    msfsSdk.FSComponent.buildComponent("div", { ref: this.itemsContainerRef, class: 'items-container' }),
                    this.childrenNode)));
        }
        /**
         * Gets an array of this list's reserved CSS classes.
         * @returns An array of this list's reserved CSS classes.
         */
        getReservedCssClasses() {
            return ScrollList.RESERVED_CLASSES;
        }
        /** @inheritDoc */
        destroy() {
            var _a;
            this.childrenNode && msfsSdk.FSComponent.shallowDestroy(this.childrenNode);
            for (const sub of this.listItemParamSubs) {
                sub.destroy();
            }
            this._listItemLengthWithMarginPx.destroy();
            'destroy' in this._lengthPx && this._lengthPx.destroy();
            this.pageLength.destroy();
            (_a = this.maxRenderedItemCount) === null || _a === void 0 ? void 0 : _a.destroy();
            this._firstVisibleIndex.destroy();
            super.destroy();
        }
    }
    ScrollList.RESERVED_CLASSES = ['scroll-list', 'scroll-list-x', 'scroll-list-y'];

    /**
     * A touchscreen list which can be scrolled by clicking and dragging the mouse.
     */
    class TouchList extends ScrollList {
        constructor() {
            var _a;
            super(...arguments);
            this.isMouseDown = msfsSdk.Subject.create(false);
            this.instrumentMouseLeaveSub = (_a = this.props.bus) === null || _a === void 0 ? void 0 : _a.getSubscriber().on('vc_mouse_leave').handle(() => this.onMouseUp(), true);
            this.lastMousePosition = undefined;
            this.onMouseDownCaptureFunc = this.onMouseDownCapture.bind(this);
            this.onMouseDownFunc = this.onMouseDown.bind(this);
            this.onMouseUpFunc = this.onMouseUp.bind(this);
            this.onMouseMoveFunc = this.onMouseMove.bind(this);
        }
        /** @inheritDoc */
        onAfterRender() {
            this.rootRef.instance.classList.add('touch-list', `touch-list-${this.scrollAxis}`);
            this.translatableRef.instance.classList.add('touch-list-translatable');
            this._lengthPx.sub(lengthPx => {
                this.rootRef.instance.style.setProperty('--touch-list-length', lengthPx + 'px');
            }, true);
            this.listItemLengthPx.sub(listItemLengthPx => {
                this.rootRef.instance.style.setProperty('--touch-list-item-length', listItemLengthPx + 'px');
            }, true);
            this.listItemSpacingPx.sub(listItemSpacingPx => {
                this.rootRef.instance.style.setProperty('--touch-list-item-margin', listItemSpacingPx + 'px');
            }, true);
            super.onAfterRender();
            this.rootRef.instance.addEventListener('mousedown', this.onMouseDownCaptureFunc, {
                capture: true,
            });
            this.rootRef.instance.addEventListener('mousedown', this.onMouseDownFunc, {
                capture: false,
            });
        }
        /**
         * Scrolls until the item at a specified index is in view. The operation will be aborted if the list is currently
         * being dragged by the user.
         * @param index The index of the item to which to scroll.
         * @param position The position to place the target item at the end of the scroll. Position `0` is the top/left-most
         * visible slot, position `1` is the next slot, and so on. Values greater than or equal to the number of visible
         * items per page will be clamped. Negative values will be interpreted as counting backwards from the
         * bottom/right-most visible slot starting with `-1`. Ignored if this list does not support snapping to list items.
         * @param animate Whether to animate the scroll.
         */
        scrollToIndex(index, position, animate) {
            if (this.isMouseDown.get() === true) {
                // We don't want to take control from the user if they are manually scrolling the list
                return;
            }
            super.scrollToIndex(index, position, animate);
        }
        /**
         * Scrolls the minimum possible distance until the item at a specified index is in view with a given margin from the
         * edges of the visible list. The operation will be aborted if the list is currently being dragged by the user.
         * @param index The index of the item to which to scroll.
         * @param margin The margin from the edges of the visible list to respect when scrolling to the target item. In other
         * words, the scrolling operation will attempt to place the target item at least as far from the edges of the visible
         * list as the specified margin. If this list supports snapping to items, then the margin should be expressed as an
         * item count. If this list does not support snapping to items, then the margin should be expressed as pixels. The
         * margin will be clamped between zero and the largest possible value such that an item can be placed within the
         * visible list while respecting the margin value on both sides.
         * @param animate Whether to animate the scroll.
         */
        scrollToIndexWithMargin(index, margin, animate) {
            if (this.isMouseDown.get() === true) {
                // We don't want to take control from the user if they are manually scrolling the list
                return;
            }
            super.scrollToIndexWithMargin(index, margin, animate);
        }
        /** @inheritdoc */
        changeRenderWindow(scrollPos, renderCount, itemLength, windowLength) {
            // Reset last mouse position so that we don't trigger any weird behaviors if the user is dragging when the
            // container translation is changed to accommodate the new render window.
            this.lastMousePosition = undefined;
            super.changeRenderWindow(scrollPos, renderCount, itemLength, windowLength);
        }
        /**
         * User has clicked on the list, so now we want to listen for
         * the mouse moving so we can scroll the list with the mouse.
         * @param e The mouse event.
         */
        onMouseDownCapture(e) {
            if (e.eventPhase === e.CAPTURING_PHASE && this.isAnimating) {
                // If list is drifting after a flick, and user clicks,
                // capture the event before it can get to any buttons in the list, and stop it
                e.stopPropagation();
                this.onMouseDown(e);
            }
            else {
                // Do nothing, it will fire again if it bubbles back up.
                return;
            }
        }
        /**
         * User has clicked on the list, so now we want to listen for
         * the mouse moving so we can scroll the list with the mouse.
         * @param e The mouse event.
         */
        onMouseDown(e) {
            var _a;
            this.lastMousePosition = this.scrollAxis === 'x' ? e.clientX : e.clientY;
            this.lastTimeSeconds = Date.now() / 1000;
            this.isMouseDown.set(true);
            window.addEventListener('mousemove', this.onMouseMoveFunc);
            window.addEventListener('mouseup', this.onMouseUpFunc);
            (_a = this.instrumentMouseLeaveSub) === null || _a === void 0 ? void 0 : _a.resume();
            this.stopAnimating();
        }
        /**
         * Responds to `mouseup` events on this list after the user has started dragging.
         */
        onMouseUp() {
            var _a;
            this.isMouseDown.set(false);
            window.removeEventListener('mousemove', this.onMouseMoveFunc);
            window.removeEventListener('mouseup', this.onMouseUpFunc);
            (_a = this.instrumentMouseLeaveSub) === null || _a === void 0 ? void 0 : _a.pause();
            if (this.isOverscrolled()) {
                // If we mouseup when overscrolled, we want it to immediately snap back,
                // so we kill the velocity.
                this.velocity = 0;
            }
            this.startAnimating();
        }
        /**
         * Responds to `mousemove` events on this list while the user is dragging.
         * @param e The mouse event.
         */
        onMouseMove(e) {
            if (this.lastMousePosition === undefined) {
                return;
            }
            const timeSeconds = Date.now() / 1000;
            let deltaTimeSeconds = timeSeconds - this.lastTimeSeconds;
            if (deltaTimeSeconds === 0) {
                deltaTimeSeconds = this.deltaTimeSeconds;
            }
            const mousePos = this.scrollAxis === 'x' ? e.clientX : e.clientY;
            const delta = mousePos - this.lastMousePosition;
            const maxOverscrollPx = this.maxOverscrollPx.get();
            this._scrollPos.set(msfsSdk.MathUtils.clamp(this._scrollPos.get() - delta * this.getDampening(Math.sign(-delta)), -maxOverscrollPx, this._maxScrollPos.get() + maxOverscrollPx));
            this.lastMousePosition = mousePos;
            this.velocity = -delta / deltaTimeSeconds;
            this.lastTimeSeconds = timeSeconds;
            this.deltaTimeSeconds = deltaTimeSeconds;
        }
        /** @inheritDoc */
        getReservedCssClasses() {
            return TouchList.RESERVED_CLASSES;
        }
        /** @inheritDoc */
        destroy() {
            var _a, _b, _c;
            (_a = this.rootRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.removeEventListener('mousedown', this.onMouseDownCaptureFunc);
            (_b = this.rootRef.getOrDefault()) === null || _b === void 0 ? void 0 : _b.removeEventListener('mousedown', this.onMouseDownFunc);
            (_c = this.instrumentMouseLeaveSub) === null || _c === void 0 ? void 0 : _c.destroy();
            super.destroy();
        }
    }
    TouchList.RESERVED_CLASSES = [...ScrollList.RESERVED_CLASSES, 'touch-list', 'touch-list-x', 'touch-list-y'];

    /**
     * Setting modes for nav angle units.
     */
    exports.UnitsNavAngleSettingMode = void 0;
    (function (UnitsNavAngleSettingMode) {
        UnitsNavAngleSettingMode["Magnetic"] = "magnetic";
        UnitsNavAngleSettingMode["True"] = "true";
    })(exports.UnitsNavAngleSettingMode || (exports.UnitsNavAngleSettingMode = {}));
    /**
     * Setting modes for distance/speed units.
     */
    exports.UnitsDistanceSettingMode = void 0;
    (function (UnitsDistanceSettingMode) {
        UnitsDistanceSettingMode["Metric"] = "metric";
        UnitsDistanceSettingMode["Nautical"] = "nautical";
        UnitsDistanceSettingMode["Statute"] = "statute";
    })(exports.UnitsDistanceSettingMode || (exports.UnitsDistanceSettingMode = {}));
    /**
     * Setting modes for altitude units.
     */
    exports.UnitsAltitudeSettingMode = void 0;
    (function (UnitsAltitudeSettingMode) {
        UnitsAltitudeSettingMode["Feet"] = "feet";
        UnitsAltitudeSettingMode["Meters"] = "meters";
        UnitsAltitudeSettingMode["MetersMps"] = "metersMps";
    })(exports.UnitsAltitudeSettingMode || (exports.UnitsAltitudeSettingMode = {}));
    /**
     * Setting modes for temperature units.
     */
    exports.UnitsTemperatureSettingMode = void 0;
    (function (UnitsTemperatureSettingMode) {
        UnitsTemperatureSettingMode["Celsius"] = "celsius";
        UnitsTemperatureSettingMode["Fahrenheit"] = "fahrenheit";
    })(exports.UnitsTemperatureSettingMode || (exports.UnitsTemperatureSettingMode = {}));
    /**
     * Setting modes for weight units.
     */
    exports.UnitsWeightSettingMode = void 0;
    (function (UnitsWeightSettingMode) {
        UnitsWeightSettingMode["Kilograms"] = "kilograms";
        UnitsWeightSettingMode["Pounds"] = "pounds";
    })(exports.UnitsWeightSettingMode || (exports.UnitsWeightSettingMode = {}));
    /**
     * Setting modes for fuel units.
     */
    exports.UnitsFuelSettingMode = void 0;
    (function (UnitsFuelSettingMode) {
        UnitsFuelSettingMode["Gallons"] = "gallons";
        UnitsFuelSettingMode["ImpGal"] = "imp gals";
        UnitsFuelSettingMode["Kilograms"] = "kilograms";
        UnitsFuelSettingMode["Liters"] = "liters";
        UnitsFuelSettingMode["Pounds"] = "pounds";
    })(exports.UnitsFuelSettingMode || (exports.UnitsFuelSettingMode = {}));
    /**
     * A default implementation of {@link UnitsUserSettingManager} which sources setting values from another setting
     * manager.
     */
    class DefaultUnitsUserSettingManager {
        /**
         * Creates a new instance of DefaultUnitsUserSettingManager.
         * @param sourceSettingManager The manager from which to source setting values.
         */
        constructor(sourceSettingManager) {
            this.sourceSettingManager = sourceSettingManager;
            this._navAngleUnits = msfsSdk.Subject.create(DefaultUnitsUserSettingManager.MAGNETIC_BEARING);
            /** @inheritDoc */
            this.navAngleUnits = this._navAngleUnits;
            this._distanceUnitsLarge = msfsSdk.Subject.create(msfsSdk.UnitType.NMILE);
            /** @inheritDoc */
            this.distanceUnitsLarge = this._distanceUnitsLarge;
            this._distanceUnitsSmall = msfsSdk.Subject.create(msfsSdk.UnitType.FOOT);
            /** @inheritDoc */
            this.distanceUnitsSmall = this._distanceUnitsSmall;
            this._speedUnits = msfsSdk.Subject.create(msfsSdk.UnitType.KNOT);
            /** @inheritDoc */
            this.speedUnits = this._speedUnits;
            this._altitudeUnits = msfsSdk.Subject.create(msfsSdk.UnitType.FOOT);
            /** @inheritDoc */
            this.altitudeUnits = this._altitudeUnits;
            this._verticalSpeedUnits = msfsSdk.Subject.create(msfsSdk.UnitType.FPM);
            /** @inheritDoc */
            this.verticalSpeedUnits = this._verticalSpeedUnits;
            this._temperatureUnits = msfsSdk.Subject.create(msfsSdk.UnitType.CELSIUS);
            /** @inheritDoc */
            this.temperatureUnits = this._temperatureUnits;
            this._temperatureDeltaUnits = msfsSdk.Subject.create(msfsSdk.UnitType.DELTA_CELSIUS);
            /** @inheritDoc */
            this.temperatureDeltaUnits = this._temperatureDeltaUnits;
            this._weightUnits = msfsSdk.Subject.create(msfsSdk.UnitType.POUND);
            /** @inheritDoc */
            this.weightUnits = this._weightUnits;
            this._fuelUnits = msfsSdk.Subject.create(msfsSdk.UnitType.GALLON_FUEL);
            /** @inheritDoc */
            this.fuelUnits = this._fuelUnits;
            this._fuelFlowUnits = msfsSdk.Subject.create(msfsSdk.UnitType.GPH_FUEL);
            /** @inheritDoc */
            this.fuelFlowUnits = this._fuelFlowUnits;
            sourceSettingManager.getSetting('unitsNavAngle').pipe(this._navAngleUnits, value => {
                return value === exports.UnitsNavAngleSettingMode.True ? DefaultUnitsUserSettingManager.TRUE_BEARING : DefaultUnitsUserSettingManager.MAGNETIC_BEARING;
            });
            sourceSettingManager.getSetting('unitsDistance').sub(value => {
                switch (value) {
                    case exports.UnitsDistanceSettingMode.Metric:
                        this._distanceUnitsLarge.set(msfsSdk.UnitType.KILOMETER);
                        this._distanceUnitsSmall.set(msfsSdk.UnitType.METER);
                        this._speedUnits.set(msfsSdk.UnitType.KPH);
                        break;
                    case exports.UnitsDistanceSettingMode.Statute:
                        this._distanceUnitsLarge.set(msfsSdk.UnitType.MILE);
                        this._distanceUnitsSmall.set(msfsSdk.UnitType.FOOT);
                        this._speedUnits.set(msfsSdk.UnitType.MPH);
                        break;
                    default:
                        this._distanceUnitsLarge.set(msfsSdk.UnitType.NMILE);
                        this._distanceUnitsSmall.set(msfsSdk.UnitType.FOOT);
                        this._speedUnits.set(msfsSdk.UnitType.KNOT);
                }
            }, true);
            sourceSettingManager.getSetting('unitsAltitude').sub(value => {
                switch (value) {
                    case exports.UnitsAltitudeSettingMode.Meters:
                        this._altitudeUnits.set(msfsSdk.UnitType.METER);
                        this._verticalSpeedUnits.set(msfsSdk.UnitType.MPM);
                        break;
                    case exports.UnitsAltitudeSettingMode.MetersMps:
                        this._altitudeUnits.set(msfsSdk.UnitType.METER);
                        this._verticalSpeedUnits.set(msfsSdk.UnitType.MPS);
                        break;
                    default:
                        this._altitudeUnits.set(msfsSdk.UnitType.FOOT);
                        this._verticalSpeedUnits.set(msfsSdk.UnitType.FPM);
                }
            }, true);
            sourceSettingManager.getSetting('unitsTemperature').sub(value => {
                if (value === exports.UnitsTemperatureSettingMode.Fahrenheit) {
                    this._temperatureUnits.set(msfsSdk.UnitType.FAHRENHEIT);
                    this._temperatureDeltaUnits.set(msfsSdk.UnitType.DELTA_FAHRENHEIT);
                }
                else {
                    this._temperatureUnits.set(msfsSdk.UnitType.CELSIUS);
                    this._temperatureDeltaUnits.set(msfsSdk.UnitType.DELTA_CELSIUS);
                }
            }, true);
            sourceSettingManager.getSetting('unitsWeight').pipe(this._weightUnits, value => {
                return value === exports.UnitsWeightSettingMode.Kilograms ? msfsSdk.UnitType.KILOGRAM : msfsSdk.UnitType.POUND;
            });
            sourceSettingManager.getSetting('unitsFuel').sub(value => {
                switch (value) {
                    case exports.UnitsFuelSettingMode.ImpGal:
                        this._fuelUnits.set(msfsSdk.UnitType.IMP_GALLON_FUEL);
                        this._fuelFlowUnits.set(msfsSdk.UnitType.IGPH_FUEL);
                        break;
                    case exports.UnitsFuelSettingMode.Liters:
                        this._fuelUnits.set(msfsSdk.UnitType.LITER_FUEL);
                        this._fuelFlowUnits.set(msfsSdk.UnitType.LPH_FUEL);
                        break;
                    case exports.UnitsFuelSettingMode.Kilograms:
                        this._fuelUnits.set(msfsSdk.UnitType.KILOGRAM);
                        this._fuelFlowUnits.set(msfsSdk.UnitType.KGH);
                        break;
                    case exports.UnitsFuelSettingMode.Pounds:
                        this._fuelUnits.set(msfsSdk.UnitType.POUND);
                        this._fuelFlowUnits.set(msfsSdk.UnitType.PPH);
                        break;
                    default:
                        this._fuelUnits.set(msfsSdk.UnitType.GALLON_FUEL);
                        this._fuelFlowUnits.set(msfsSdk.UnitType.GPH_FUEL);
                }
            }, true);
        }
        /** @inheritDoc */
        tryGetSetting(name) {
            return this.sourceSettingManager.tryGetSetting(name);
        }
        /** @inheritDoc */
        getSetting(name) {
            return this.sourceSettingManager.getSetting(name);
        }
        /** @inheritDoc */
        whenSettingChanged(name) {
            return this.sourceSettingManager.whenSettingChanged(name);
        }
        /** @inheritDoc */
        getAllSettings() {
            return this.sourceSettingManager.getAllSettings();
        }
        /** @inheritDoc */
        mapTo(map) {
            return this.sourceSettingManager.mapTo(map);
        }
    }
    DefaultUnitsUserSettingManager.TRUE_BEARING = msfsSdk.BasicNavAngleUnit.create(false);
    DefaultUnitsUserSettingManager.MAGNETIC_BEARING = msfsSdk.BasicNavAngleUnit.create(true);
    /**
     * Utility class for retrieving display units user setting managers.
     */
    class UnitsUserSettings {
        /**
         * Retrieves a manager for display units user settings.
         * @param bus The event bus.
         * @returns A manager for display units user settings.
         */
        static getManager(bus) {
            var _a;
            return (_a = UnitsUserSettings.INSTANCE) !== null && _a !== void 0 ? _a : (UnitsUserSettings.INSTANCE = new DefaultUnitsUserSettingManager(new msfsSdk.DefaultUserSettingManager(bus, Object.entries(UnitsUserSettings.getDefaultValues()).map(([name, defaultValue]) => {
                return {
                    name,
                    defaultValue
                };
            }))));
        }
        /**
         * Retrieves a manager for instrument-local display units user settings.
         * @param bus The event bus.
         * @returns A manager for instrument-local display units user settings.
         */
        static getLocalManager(bus) {
            if (UnitsUserSettings.LOCAL_INSTANCE) {
                return UnitsUserSettings.LOCAL_INSTANCE;
            }
            const defaultValues = Object.entries(UnitsUserSettings.getDefaultValues());
            const defs = defaultValues.map(([name, defaultValue]) => {
                return {
                    name: `${name}_local`,
                    defaultValue
                };
            });
            const map = {};
            for (const [name] of defaultValues) {
                map[name] = `${name}_local`;
            }
            return UnitsUserSettings.LOCAL_INSTANCE = new DefaultUnitsUserSettingManager(new msfsSdk.DefaultUserSettingManager(bus, defs, true).mapTo(map));
        }
        /**
         * Gets the default values for a full set of standard display units user settings.
         * @returns The default values for a full set of standard display units user settings.
         */
        static getDefaultValues() {
            return {
                unitsNavAngle: exports.UnitsNavAngleSettingMode.Magnetic,
                unitsDistance: exports.UnitsDistanceSettingMode.Nautical,
                unitsAltitude: exports.UnitsAltitudeSettingMode.Feet,
                unitsTemperature: exports.UnitsTemperatureSettingMode.Celsius,
                unitsWeight: exports.UnitsWeightSettingMode.Pounds,
                unitsFuel: exports.UnitsFuelSettingMode.Gallons,
            };
        }
    }

    /**
     * A map flight plan layer data provider that provides a displayed flight plan from a flight planner.
     */
    class MapFlightPlannerPlanDataProvider {
        // eslint-disable-next-line jsdoc/require-jsdoc
        constructor(bus, arg2) {
            var _a, _b;
            this.bus = bus;
            this._plan = msfsSdk.Subject.create(null);
            /** @inheritDoc */
            this.plan = this._plan;
            /** @inheritDoc */
            this.planModified = new msfsSdk.SubEvent();
            /** @inheritDoc */
            this.planCalculated = new msfsSdk.SubEvent();
            this._activeLateralLegIndex = msfsSdk.Subject.create(0);
            /** @inheritDoc */
            this.activeLateralLegIndex = this._activeLateralLegIndex;
            this._lnavData = msfsSdk.Subject.create(undefined, (a, b) => {
                if (!a && !b) {
                    return true;
                }
                if (a && b) {
                    return msfsSdk.LNavUtils.lnavTrackingStateEquals(a, b);
                }
                return false;
            });
            /** @inheritDoc */
            this.lnavData = this._lnavData;
            this._vnavState = msfsSdk.Subject.create(msfsSdk.VNavState.Disabled);
            /** @inheritDoc */
            this.vnavState = this._vnavState;
            this._vnavPathMode = msfsSdk.Subject.create(msfsSdk.VNavPathMode.None);
            /** @inheritDoc */
            this.vnavPathMode = this._vnavPathMode;
            this._vnavTodLegIndex = msfsSdk.Subject.create(-1);
            /** @inheritDoc */
            this.vnavTodLegIndex = this._vnavTodLegIndex;
            this._vnavBodLegIndex = msfsSdk.Subject.create(-1);
            /** @inheritDoc */
            this.vnavBodLegIndex = this._vnavBodLegIndex;
            this._vnavTodLegDistance = msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.METER.createNumber(0));
            /** @inheritDoc */
            this.vnavTodLegDistance = this._vnavTodLegDistance;
            this._vnavDistanceToTod = msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.METER.createNumber(0));
            /** @inheritDoc */
            this.vnavDistanceToTod = this._vnavDistanceToTod;
            this._vnavTocLegIndex = msfsSdk.Subject.create(-1);
            /** @inheritDoc */
            this.vnavTocLegIndex = this._vnavTocLegIndex;
            this._vnavBocLegIndex = msfsSdk.Subject.create(-1);
            /** @inheritDoc */
            this.vnavBocLegIndex = this._vnavBocLegIndex;
            this._vnavTocLegDistance = msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.METER.createNumber(0));
            /** @inheritDoc */
            this.vnavTocLegDistance = this._vnavTocLegDistance;
            this._vnavDistanceToToc = msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.METER.createNumber(0));
            /** @inheritDoc */
            this.vnavDistanceToToc = this._vnavDistanceToToc;
            this._obsCourse = msfsSdk.Subject.create(undefined);
            /** @inheritDoc */
            this.obsCourse = this._obsCourse;
            this.lnavDataSource = msfsSdk.Subject.create(undefined);
            this.obsActiveSource = msfsSdk.Subject.create(false);
            this.obsCourseSource = msfsSdk.Subject.create(0);
            this.lnavSourceSubs = [];
            this.vnavTodLegIndexSource = msfsSdk.Subject.create(-1);
            this.vnavBodLegIndexSource = msfsSdk.Subject.create(-1);
            this.vnavTodLegDistanceSource = msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.METER.createNumber(0));
            this.vnavDistanceToTodSource = msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.METER.createNumber(0));
            this.vnavTocLegIndexSource = msfsSdk.Subject.create(-1);
            this.vnavBocLegIndexSource = msfsSdk.Subject.create(-1);
            this.vnavTocLegDistanceSource = msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.METER.createNumber(0));
            this.vnavDistanceToTocSource = msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.METER.createNumber(0));
            this.vnavSourceSubs = [];
            this.planIndex = -1;
            this.isObsActive = false;
            this.obsCourseValue = 0;
            this.fplSubs = [];
            this.isAlive = true;
            this.subscriptions = [];
            let flightPlanner;
            let options;
            if (arg2 instanceof msfsSdk.FlightPlanner) {
                flightPlanner = arg2;
            }
            else {
                flightPlanner = arg2.flightPlanner;
                options = arg2;
            }
            this.planner = msfsSdk.SubscribableUtils.toSubscribable(flightPlanner, true);
            this.lnavIndex = msfsSdk.SubscribableUtils.toSubscribable((_a = options === null || options === void 0 ? void 0 : options.lnavIndex) !== null && _a !== void 0 ? _a : 0, true);
            this.vnavIndex = msfsSdk.SubscribableUtils.toSubscribable((_b = options === null || options === void 0 ? void 0 : options.vnavIndex) !== null && _b !== void 0 ? _b : 0, true);
            const lnavEvents = bus.getSubscriber();
            const vnavEvents = bus.getSubscriber();
            this.activePlanSubs = [
                this.lnavDataSource.pipe(this._lnavData, true),
                this.vnavTodLegIndexSource.pipe(this._vnavTodLegIndex, true),
                this.vnavBodLegIndexSource.pipe(this._vnavBodLegIndex, true),
                this.vnavTodLegDistanceSource.pipe(this._vnavTodLegDistance, true),
                this.vnavDistanceToTodSource.pipe(this._vnavDistanceToTod, true),
                this.vnavTocLegIndexSource.pipe(this._vnavTocLegIndex, true),
                this.vnavBocLegIndexSource.pipe(this._vnavBocLegIndex, true),
                this.vnavTocLegDistanceSource.pipe(this._vnavTocLegDistance, true),
                this.vnavDistanceToTocSource.pipe(this._vnavDistanceToToc, true),
                this.obsActiveSource.sub(isActive => {
                    this.isObsActive = isActive;
                    this.updateObsCourse();
                }, false, true),
                this.obsCourseSource.sub(course => {
                    this.obsCourseValue = course;
                    this.updateObsCourse();
                }, false, true)
            ];
            this.subscriptions.push(this.planner.sub(this.onFlightPlannerChanged.bind(this), true), this.lnavIndex.sub(this.onLNavIndexChanged.bind(this, lnavEvents), true), this.vnavIndex.sub(this.onVNavIndexChanged.bind(this, vnavEvents), true));
        }
        /**
         * Responds to when this provider's flight planner changes.
         * @param planner The new flight planner.
         */
        onFlightPlannerChanged(planner) {
            for (const sub of this.fplSubs) {
                sub.destroy();
            }
            this.fplSubs.length = 0;
            this.fplSubs.push(planner.onEvent('fplCreated').handle(data => { data.planIndex === this.planIndex && this.updatePlan(); }), planner.onEvent('fplDeleted').handle(data => { data.planIndex === this.planIndex && this.updatePlan(); }), planner.onEvent('fplLoaded').handle(data => { data.planIndex === this.planIndex && this.updatePlan(); }), planner.onEvent('fplIndexChanged').handle(() => { this.updateActivePlanRelatedSubs(); }), planner.onEvent('fplLegChange').handle(data => { data.planIndex === this.planIndex && this.planModified.notify(this); }), planner.onEvent('fplSegmentChange').handle(data => { data.planIndex === this.planIndex && this.planModified.notify(this); }), planner.onEvent('fplOriginDestChanged').handle(data => { data.planIndex === this.planIndex && this.planModified.notify(this); }), planner.onEvent('fplActiveLegChange').handle(data => { data.planIndex === this.planIndex && data.type === msfsSdk.ActiveLegType.Lateral && this.updateActiveLegIndex(); }), planner.onEvent('fplCalculated').handle(data => { data.planIndex === this.planIndex && this.planCalculated.notify(this); }));
            this.updatePlan();
            this.updateActivePlanRelatedSubs();
        }
        /**
         * Responds to when this provider's LNAV index changes.
         * @param lnavEvents An event subscriber for LNAV events.
         * @param index The new LNAV index.
         */
        onLNavIndexChanged(lnavEvents, index) {
            for (const sub of this.lnavSourceSubs) {
                sub.destroy();
            }
            this.lnavSourceSubs.length = 0;
            this.lnavDataSource.set(undefined);
            this.obsActiveSource.set(false);
            this.obsCourseSource.set(0);
            if (msfsSdk.LNavUtils.isValidLNavIndex(index)) {
                const lnavTopicSuffix = msfsSdk.LNavUtils.getEventBusTopicSuffix(index);
                this.lnavSourceSubs.push(lnavEvents.on(`lnav_tracking_state${lnavTopicSuffix}`).handle(state => {
                    this.lnavDataSource.set(state);
                }), lnavEvents.on(`lnav_obs_active${lnavTopicSuffix}`).whenChanged().handle(isActive => {
                    this.obsActiveSource.set(isActive);
                }), lnavEvents.on(`lnav_obs_course${lnavTopicSuffix}`).whenChanged().handle(course => {
                    this.obsCourseSource.set(course);
                }));
            }
        }
        /**
         * Responds to when this provider's VNAV index changes.
         * @param vnavEvents An event subscriber for VNAV events.
         * @param index The new VNAV index.
         */
        onVNavIndexChanged(vnavEvents, index) {
            for (const sub of this.vnavSourceSubs) {
                sub.destroy();
            }
            this.vnavSourceSubs.length = 0;
            this._vnavState.set(msfsSdk.VNavState.Disabled);
            this._vnavPathMode.set(msfsSdk.VNavPathMode.None);
            this.vnavTodLegIndexSource.set(-1);
            this.vnavBodLegIndexSource.set(-1);
            this.vnavTodLegDistanceSource.set(0);
            this.vnavDistanceToTodSource.set(0);
            this.vnavTocLegIndexSource.set(-1);
            this.vnavBocLegIndexSource.set(-1);
            this.vnavTocLegDistanceSource.set(0);
            this.vnavDistanceToTocSource.set(0);
            if (msfsSdk.VNavUtils.isValidVNavIndex(index)) {
                const vnavTopicSuffix = msfsSdk.VNavUtils.getEventBusTopicSuffix(index);
                this.vnavSourceSubs.push(vnavEvents.on(`vnav_state${vnavTopicSuffix}`).handle(state => {
                    this._vnavState.set(state);
                }), vnavEvents.on(`vnav_path_mode${vnavTopicSuffix}`).handle(mode => {
                    this._vnavPathMode.set(mode);
                }), vnavEvents.on(`vnav_tod_global_leg_index${vnavTopicSuffix}`).whenChanged().handle(legIndex => {
                    this.vnavTodLegIndexSource.set(legIndex);
                }), vnavEvents.on(`vnav_bod_global_leg_index${vnavTopicSuffix}`).whenChanged().handle(legIndex => {
                    this.vnavBodLegIndexSource.set(legIndex);
                }), vnavEvents.on(`vnav_tod_leg_distance${vnavTopicSuffix}`).withPrecision(0).handle(distance => {
                    this.vnavTodLegDistanceSource.set(distance, msfsSdk.UnitType.METER);
                }), vnavEvents.on(`vnav_tod_distance${vnavTopicSuffix}`).withPrecision(0).handle(distance => {
                    this.vnavDistanceToTodSource.set(distance, msfsSdk.UnitType.METER);
                }), vnavEvents.on(`vnav_toc_global_leg_index${vnavTopicSuffix}`).whenChanged().handle(legIndex => {
                    this.vnavTocLegIndexSource.set(legIndex);
                }), vnavEvents.on(`vnav_boc_global_leg_index${vnavTopicSuffix}`).whenChanged().handle(legIndex => {
                    this.vnavBocLegIndexSource.set(legIndex);
                }), vnavEvents.on(`vnav_toc_leg_distance${vnavTopicSuffix}`).withPrecision(0).handle(distance => {
                    this.vnavTocLegDistanceSource.set(distance, msfsSdk.UnitType.METER);
                }), vnavEvents.on(`vnav_toc_distance${vnavTopicSuffix}`).withPrecision(0).handle(distance => {
                    this.vnavDistanceToTocSource.set(distance, msfsSdk.UnitType.METER);
                }));
            }
        }
        /**
         * Sets the index of the displayed plan.
         * @param index The index of the displayed plan.
         * @throws Error if this data provider has been destroyed.
         */
        setPlanIndex(index) {
            if (!this.isAlive) {
                throw new Error('MapFlightPlannerPlanDataProvider: cannot modify a dead provider');
            }
            if (index === this.planIndex) {
                return;
            }
            this.planIndex = index;
            this.updatePlan();
            this.updateActivePlanRelatedSubs();
        }
        /**
         * Updates the displayed plan.
         */
        updatePlan() {
            const planner = this.planner.get();
            if (planner.hasFlightPlan(this.planIndex)) {
                this._plan.set(planner.getFlightPlan(this.planIndex));
            }
            else {
                this._plan.set(null);
            }
        }
        /**
         * Updates subjects related to the active plan.
         */
        updateActivePlanRelatedSubs() {
            this.updateActiveLegIndex();
            const planner = this.planner.get();
            if (this.planIndex === planner.activePlanIndex) {
                for (const sub of this.activePlanSubs) {
                    sub.resume(true);
                }
            }
            else {
                for (const sub of this.activePlanSubs) {
                    sub.pause();
                }
                this._lnavData.set(undefined);
                this._vnavTodLegIndex.set(-1);
                this._vnavBodLegIndex.set(-1);
                this._vnavTodLegDistance.set(0);
                this._vnavDistanceToTod.set(0);
                this._vnavTocLegIndex.set(-1);
                this._vnavBocLegIndex.set(-1);
                this._vnavTocLegDistance.set(0);
                this._vnavDistanceToToc.set(0);
                this._obsCourse.set(undefined);
            }
        }
        /**
         * Updates the active leg index.
         */
        updateActiveLegIndex() {
            const planner = this.planner.get();
            const plan = this.plan.get();
            this._activeLateralLegIndex.set(plan && this.planIndex === planner.activePlanIndex ? plan.activeLateralLeg : -1);
        }
        /**
         * Updates the OBS course.
         */
        updateObsCourse() {
            this._obsCourse.set(this.isObsActive ? this.obsCourseValue : undefined);
        }
        /**
         * Destroys this data provider.
         */
        destroy() {
            this.isAlive = false;
            for (const sub of this.subscriptions) {
                sub.destroy();
            }
            for (const sub of this.fplSubs) {
                sub.destroy();
            }
            for (const sub of this.lnavSourceSubs) {
                sub.destroy();
            }
            for (const sub of this.vnavSourceSubs) {
                sub.destroy();
            }
        }
    }

    /**
     * A map flight plan layer data provider that provides the active flight plan from a flight planner to be displayed.
     */
    class MapActiveFlightPlanDataProvider {
        // eslint-disable-next-line jsdoc/require-jsdoc
        constructor(bus, arg2) {
            this.bus = bus;
            let flightPlanner;
            let options;
            if (arg2 instanceof msfsSdk.FlightPlanner) {
                flightPlanner = arg2;
            }
            else {
                flightPlanner = arg2.flightPlanner;
                options = arg2;
            }
            const plannerSubscribable = msfsSdk.SubscribableUtils.toSubscribable(flightPlanner, true);
            this.planner = msfsSdk.Subject.create(plannerSubscribable.get());
            this.provider = new MapFlightPlannerPlanDataProvider(this.bus, Object.assign(Object.assign({}, options), { flightPlanner: this.planner }));
            this.plan = this.provider.plan;
            this.planModified = this.provider.planModified;
            this.planCalculated = this.provider.planCalculated;
            this.activeLateralLegIndex = this.provider.activeLateralLegIndex;
            this.lnavData = this.provider.lnavData;
            this.vnavState = this.provider.vnavState;
            this.vnavPathMode = this.provider.vnavPathMode;
            this.vnavTodLegIndex = this.provider.vnavTodLegIndex;
            this.vnavBodLegIndex = this.provider.vnavBodLegIndex;
            this.vnavTodLegDistance = this.provider.vnavTodLegDistance;
            this.vnavDistanceToTod = this.provider.vnavDistanceToTod;
            this.vnavTocLegIndex = this.provider.vnavTocLegIndex;
            this.vnavBocLegIndex = this.provider.vnavBocLegIndex;
            this.vnavTocLegDistance = this.provider.vnavTocLegDistance;
            this.vnavDistanceToToc = this.provider.vnavDistanceToToc;
            this.obsCourse = this.provider.obsCourse;
            this.plannerSub = plannerSubscribable.sub(this.onFlightPlannerChanged.bind(this), true);
        }
        /**
         * Responds to when this provider's flight planner changes.
         * @param planner The new flight planner.
         */
        onFlightPlannerChanged(planner) {
            var _a;
            (_a = this.fplIndexSub) === null || _a === void 0 ? void 0 : _a.destroy();
            this.fplIndexSub = planner.onEvent('fplIndexChanged').handle(data => { this.provider.setPlanIndex(data.planIndex); });
            this.planner.set(planner);
            this.provider.setPlanIndex(planner.activePlanIndex);
        }
        /**
         * Destroys this data provider.
         */
        destroy() {
            var _a;
            this.plannerSub.destroy();
            (_a = this.fplIndexSub) === null || _a === void 0 ? void 0 : _a.destroy();
            this.provider.destroy();
        }
    }

    /**
     * A repeating pattern of triangular arrows pointing in the direction of the input path.
     */
    class FlightPathArrowPattern {
        constructor() {
            /** @inheritdoc */
            this.length = 12;
            /** @inheritdoc */
            this.anchor = 0.5;
            /** The canvas 2D rendering context to which to render. */
            this.context = null;
            /** The color of the arrows to render. */
            this.color = 'white';
        }
        /** @inheritdoc */
        draw(stream) {
            if (!this.context) {
                return;
            }
            stream.moveTo(5, 0);
            stream.lineTo(-5, -3);
            stream.lineTo(-5, 3);
            stream.closePath();
            this.context.fillStyle = this.color;
            this.context.fill();
        }
    }

    /**
     * Bitflags for flight path rendering styles.
     */
    exports.MapFlightPathStyleFlags = void 0;
    (function (MapFlightPathStyleFlags) {
        MapFlightPathStyleFlags[MapFlightPathStyleFlags["Prior"] = 1] = "Prior";
        MapFlightPathStyleFlags[MapFlightPathStyleFlags["Upcoming"] = 2] = "Upcoming";
        MapFlightPathStyleFlags[MapFlightPathStyleFlags["Active"] = 4] = "Active";
        MapFlightPathStyleFlags[MapFlightPathStyleFlags["Next"] = 8] = "Next";
        MapFlightPathStyleFlags[MapFlightPathStyleFlags["MissedApproach"] = 16] = "MissedApproach";
        MapFlightPathStyleFlags[MapFlightPathStyleFlags["ActiveSegment"] = 32] = "ActiveSegment";
        MapFlightPathStyleFlags[MapFlightPathStyleFlags["RollHeading"] = 64] = "RollHeading";
        MapFlightPathStyleFlags[MapFlightPathStyleFlags["Heading"] = 128] = "Heading";
    })(exports.MapFlightPathStyleFlags || (exports.MapFlightPathStyleFlags = {}));
    /**
     * A utility class defining common Garmin styles for rendering flight paths.
     */
    class MapFlightPathStyles {
    }
    /** The stroke width, in pixels, for flight plan legs positioned after the active leg in the same segment as the active leg. */
    MapFlightPathStyles.STROKE_WIDTH = 4;
    /** The stroke color for flight plan legs positioned after the active leg in the same segment as the active leg. */
    MapFlightPathStyles.STROKE_COLOR = 'white';
    /** The stroke width, in pixels, for the active flight plan leg. */
    MapFlightPathStyles.ACTIVE_STROKE_WIDTH = 4;
    /** The stroke color for the active flight plan leg. */
    MapFlightPathStyles.ACTIVE_STROKE_COLOR = 'magenta';
    /** The stroke width, in pixels, for flight plan legs positioned before the active leg. */
    MapFlightPathStyles.PRIOR_STROKE_WIDTH = 2;
    /** The stroke color for flight plan legs positioned before the active leg. */
    MapFlightPathStyles.PRIOR_STROKE_COLOR = '#cccccc';
    /** The stroke width, in pixels, for flight plan legs in the missed approach segment while the missed approach is not active. */
    MapFlightPathStyles.MISSED_APPROACH_STROKE_WIDTH = 1;
    /** The stroke color for flight plan legs in the missed approach segment while the missed approach is not active. */
    MapFlightPathStyles.MISSED_APPROACH_STROKE_COLOR = 'white';
    /** The base stroke width, in pixels, for flight plan legs. */
    MapFlightPathStyles.BASE_STROKE_WIDTH = 2;
    /** The base stroke color for flight plan legs. */
    MapFlightPathStyles.BASE_STROKE_COLOR = 'rgba(204, 204, 204, 0.5)';
    /** The stroke width, in pixels, for procedure transition preview legs. */
    MapFlightPathStyles.TRANSITION_STROKE_WIDTH = 2;
    /** The stroke color for procedure transition preview legs. */
    MapFlightPathStyles.TRANSITION_STROKE_COLOR = '#666666';
    /** The stroke width, in pixels, for roll-heading vectors rendered as dashes. */
    MapFlightPathStyles.ROLL_HEADING_DASH_STROKE_WIDTH = 2;
    /** The dash array for roll-heading vectors rendered as dashes. */
    MapFlightPathStyles.ROLL_HEADING_DASH_ARRAY = [4, 4];
    /** The width of the background for magenta arrows. */
    MapFlightPathStyles.MAGENTA_ARROW_BG_WIDTH = 6;
    /** The background color for magenta arrows. */
    MapFlightPathStyles.MAGENTA_ARROW_BG_COLOR = 'rgba(40, 40, 40, 0.5)';

    /**
     * Renders flight plan legs as a continuous line.
     */
    class FlightPathLegContinuousLineRenderer extends msfsSdk.FlightPathLegLineRenderer {
        /**
         * Constructor.
         */
        constructor() {
            super((vector, isIngress, isEgress, leg, projection, out, width, style, dash) => {
                out.strokeWidth = width;
                out.strokeStyle = style;
                out.strokeDash = dash !== null && dash !== void 0 ? dash : null;
                out.outlineWidth = 0;
                out.isContinuous = true;
                return out;
            });
        }
    }
    /**
     * Renders flight plan legs using default styles. Normally, all vectors in the leg are rendered as solid lines of
     * varying width and color based on the leg's parent flight plan segment and relation to the active leg. When the leg's
     * ingress or egress transition is joined to a vector styled as a roll-heading vector, the transition will also be
     * styled as a roll-heading vector.
     */
    class FlightPathDefaultLegRenderer {
        constructor() {
            this.arrowPattern = new FlightPathArrowPattern();
            this.legLineRenderer = new msfsSdk.FlightPathLegLineRenderer(this.selectLineStyle.bind(this));
            this.legPatternRenderer = new msfsSdk.FlightPathLegPatternRenderer(this.selectPatternStyle.bind(this));
        }
        /**
         * Renders a default leg to a canvas.
         * @param leg The leg to render.
         * @param context The canvas 2D rendering context to which to render.
         * @param streamStack The path stream stack to which to render.
         * @param legIndex The global index of the leg to render.
         * @param activeLegIndex The global index of the active flight plan leg.
         * @param prevLeg The leg prior to the leg to render.
         * @param nextLeg The leg after to the leg to render.
         * @param isMissedApproachActive Whether the missed approach is active.
         */
        render(leg, context, streamStack, legIndex, activeLegIndex, prevLeg, nextLeg, isMissedApproachActive) {
            var _a;
            const isMissedApproachLeg = msfsSdk.BitFlags.isAny(leg.flags, msfsSdk.LegDefinitionFlags.MissedApproach);
            const useMissedApproachStyle = !isMissedApproachActive && isMissedApproachLeg;
            const isFirstMissedApproachLeg = isMissedApproachLeg
                && !msfsSdk.BitFlags.isAny((_a = prevLeg === null || prevLeg === void 0 ? void 0 : prevLeg.flags) !== null && _a !== void 0 ? _a : 0, msfsSdk.LegDefinitionFlags.MissedApproach);
            let styleFlags = 0;
            if (legIndex < activeLegIndex) {
                styleFlags |= exports.MapFlightPathStyleFlags.Prior;
            }
            else if (useMissedApproachStyle) {
                styleFlags |= exports.MapFlightPathStyleFlags.MissedApproach;
            }
            else if (legIndex > activeLegIndex) {
                styleFlags |= exports.MapFlightPathStyleFlags.Upcoming;
                if (legIndex === activeLegIndex + 1) {
                    styleFlags |= exports.MapFlightPathStyleFlags.Next;
                }
            }
            else { // legIndex === activeLegIndex
                styleFlags |= exports.MapFlightPathStyleFlags.Active;
            }
            const isIngressRollHeading = (!useMissedApproachStyle || isFirstMissedApproachLeg) && this.isIngressRollHeading(leg, prevLeg);
            const isEgressRollHeading = !useMissedApproachStyle && this.isEgressRollHeading(leg, nextLeg);
            this.arrowPattern.context = context;
            this.legLineRenderer.render(leg, context, streamStack, msfsSdk.FlightPathLegRenderPart.All, styleFlags, isIngressRollHeading, isEgressRollHeading, useMissedApproachStyle);
            this.legPatternRenderer.render(leg, context, streamStack, msfsSdk.FlightPathLegRenderPart.All, styleFlags, isIngressRollHeading, isEgressRollHeading, useMissedApproachStyle);
        }
        /**
         * Checks if an ingress transition following a specified leg should be rendered as roll-heading vectors.
         * @param leg The leg to which the ingress transition belongs.
         * @param prevLeg The leg prior to the leg to which the ingress transition belongs, or `undefined` if there is no
         * such leg.
         * @returns If the ingress transition following the specified leg should be rendered as roll-heading vectors.
         */
        isIngressRollHeading(leg, prevLeg) {
            var _a, _b;
            const firstIngressVector = (_a = leg.calculated) === null || _a === void 0 ? void 0 : _a.ingress[0];
            if (firstIngressVector === undefined) {
                return false;
            }
            if (this.isRollHeadingVector(firstIngressVector, leg)) {
                return true;
            }
            if (msfsSdk.BitFlags.isAny(firstIngressVector.flags, msfsSdk.FlightPathVectorFlags.LegToLegTurn)) {
                const ingressJoinVector = (_b = leg.calculated) === null || _b === void 0 ? void 0 : _b.flightPath[leg.calculated.ingressJoinIndex];
                if (ingressJoinVector !== undefined && this.isRollHeadingVector(ingressJoinVector, leg)) {
                    return true;
                }
                return prevLeg !== undefined && this.isEgressRollHeading(prevLeg, undefined);
            }
            return false;
        }
        /**
         * Checks if an egress transition prior to a specified leg should be rendered as roll-heading vectors.
         * @param leg The leg to which the egress transition belongs.
         * @param nextLeg The leg after the leg to which the egress transition belongs, or `undefined` if there is no such
         * leg.
         * @returns If the egress transition prior to the specified leg should be rendered as roll-heading vectors.
         */
        isEgressRollHeading(leg, nextLeg) {
            var _a, _b;
            const firstEgressVector = (_a = leg.calculated) === null || _a === void 0 ? void 0 : _a.egress[0];
            if (firstEgressVector === undefined) {
                return false;
            }
            if (this.isRollHeadingVector(firstEgressVector, leg)) {
                return true;
            }
            if (msfsSdk.BitFlags.isAny(firstEgressVector.flags, msfsSdk.FlightPathVectorFlags.LegToLegTurn)) {
                const egressJoinVector = (_b = leg.calculated) === null || _b === void 0 ? void 0 : _b.flightPath[leg.calculated.egressJoinIndex];
                if (egressJoinVector !== undefined && this.isRollHeadingVector(egressJoinVector, leg)) {
                    return true;
                }
                return nextLeg !== undefined && this.isIngressRollHeading(nextLeg, undefined);
            }
            return false;
        }
        /**
         * Selects a line style to render for a vector.
         * @param vector The vector for which to select a style.
         * @param isIngress Whether the vector is part of the ingress transition.
         * @param isEgress Whether the vector is part of the egress transition.
         * @param leg The flight plan leg containing the vector to render.
         * @param projection The map projection to use when rendering.
         * @param out The line style object to which to write the selected style.
         * @param styleFlags Bit flags describing the style with which to render the line.
         * @param isIngressRollHeading Whether the ingress transition should be rendered as roll-heading vectors.
         * @param isEgressRollHeading Whether the egress transition should be rendered as roll-heading vectors.
         * @param useMissedApproachStyle Whether the missed approach style should be applied to the vector.
         * @returns The selected line style for the vector.
         */
        selectLineStyle(vector, isIngress, isEgress, leg, projection, out, styleFlags, isIngressRollHeading, isEgressRollHeading, useMissedApproachStyle) {
            if ((isIngress && isIngressRollHeading)
                || (isEgress && isEgressRollHeading)
                || (!isIngress && !isEgress && !useMissedApproachStyle && this.isRollHeadingVector(vector, leg))) {
                return this.selectRollHeadingLineStyle(styleFlags, out);
            }
            else {
                return this.selectNormalLineStyle(styleFlags, out);
            }
        }
        /**
         * Selects a line style to render for a normal vector.
         * @param styleFlags Bit flags describing the style with which to render the line.
         * @param out The line style object to which to write the selected style.
         * @returns The selected line style for the vector.
         */
        selectNormalLineStyle(styleFlags, out) {
            let width, style;
            if (msfsSdk.BitFlags.isAny(styleFlags, exports.MapFlightPathStyleFlags.Active)) {
                width = MapFlightPathStyles.ACTIVE_STROKE_WIDTH;
                style = MapFlightPathStyles.ACTIVE_STROKE_COLOR;
            }
            else if (msfsSdk.BitFlags.isAny(styleFlags, exports.MapFlightPathStyleFlags.Prior)) {
                width = MapFlightPathStyles.PRIOR_STROKE_WIDTH;
                style = MapFlightPathStyles.PRIOR_STROKE_COLOR;
            }
            else if (msfsSdk.BitFlags.isAny(styleFlags, exports.MapFlightPathStyleFlags.MissedApproach)) {
                width = MapFlightPathStyles.MISSED_APPROACH_STROKE_WIDTH;
                style = MapFlightPathStyles.MISSED_APPROACH_STROKE_COLOR;
            }
            else {
                width = MapFlightPathStyles.STROKE_WIDTH;
                style = MapFlightPathStyles.STROKE_COLOR;
            }
            out.strokeWidth = width;
            out.strokeStyle = style;
            out.strokeDash = null;
            out.outlineWidth = 0;
            out.isContinuous = true;
            return out;
        }
        /**
         * Selects a line style to render for a roll-heading vector.
         * @param styleFlags Bit flags describing the style with which to render the line.
         * @param out The line style object to which to write the selected style.
         * @returns The selected line style for the vector.
         */
        selectRollHeadingLineStyle(styleFlags, out) {
            let width, style, dash;
            if (msfsSdk.BitFlags.isAny(styleFlags, exports.MapFlightPathStyleFlags.Active)) {
                width = MapFlightPathStyles.MAGENTA_ARROW_BG_WIDTH;
                style = MapFlightPathStyles.MAGENTA_ARROW_BG_COLOR;
                dash = null;
            }
            else if (msfsSdk.BitFlags.isAny(styleFlags, exports.MapFlightPathStyleFlags.Next)) {
                width = 0;
                style = '';
                dash = null;
            }
            else {
                width = MapFlightPathStyles.ROLL_HEADING_DASH_STROKE_WIDTH;
                style = MapFlightPathStyles.STROKE_COLOR;
                dash = MapFlightPathStyles.ROLL_HEADING_DASH_ARRAY;
            }
            out.strokeWidth = width;
            out.strokeStyle = style;
            out.strokeDash = dash;
            out.outlineWidth = 0;
            out.isContinuous = true;
            return out;
        }
        /**
         * Selects a pattern style to render a vector.
         * @param vector The vector for which to select a style.
         * @param isIngress Whether the vector is part of the ingress transition.
         * @param isEgress Whether the vector is part of the egress transition.
         * @param leg The flight plan leg containing the vector to render.
         * @param projection The map projection to use when rendering.
         * @param out The line style object to which to write the selected style.
         * @param styleFlags Bit flags describing the style with which to render the pattern.
         * @param isIngressRollHeading Whether the ingress transition should be rendered as roll-heading vectors.
         * @param isEgressRollHeading Whether the egress transition should be rendered as roll-heading vectors.
         * @param useMissedApproachStyle Whether the missed approach style should be applied to the vector.
         * @returns The selected pattern style for the vector.
         */
        selectPatternStyle(vector, isIngress, isEgress, leg, projection, out, styleFlags, isIngressRollHeading, isEgressRollHeading, useMissedApproachStyle) {
            if ((isIngress && isIngressRollHeading)
                || (isEgress && isEgressRollHeading)
                || (!isIngress && !isEgress && !useMissedApproachStyle && this.isRollHeadingVector(vector, leg))) {
                if (msfsSdk.BitFlags.isAny(styleFlags, exports.MapFlightPathStyleFlags.Active)) {
                    this.arrowPattern.color = MapFlightPathStyles.ACTIVE_STROKE_COLOR;
                    out.pattern = this.arrowPattern;
                }
                else if (msfsSdk.BitFlags.isAny(styleFlags, exports.MapFlightPathStyleFlags.Next)) {
                    this.arrowPattern.color = MapFlightPathStyles.STROKE_COLOR;
                    out.pattern = this.arrowPattern;
                }
                else {
                    out.pattern = null;
                }
            }
            else {
                out.pattern = null;
            }
            out.isContinuous = true;
            return out;
        }
        /**
         * Checks whether a vector should be rendered as a roll-heading vector.
         * @param vector A flight path vector.
         * @param leg The flight plan leg containing the vector.
         * @returns Whether the vector should be rendered as a roll-heading vector.
         */
        isRollHeadingVector(vector, leg) {
            if (msfsSdk.BitFlags.isAny(vector.flags, msfsSdk.FlightPathVectorFlags.Fallback
                | msfsSdk.FlightPathVectorFlags.HoldDirectEntry
                | msfsSdk.FlightPathVectorFlags.HoldTeardropEntry
                | msfsSdk.FlightPathVectorFlags.HoldParallelEntry)) {
                return true;
            }
            switch (leg.leg.type) {
                case msfsSdk.LegType.CF:
                    return msfsSdk.BitFlags.isAny(vector.flags, msfsSdk.FlightPathVectorFlags.InterceptCourse | msfsSdk.FlightPathVectorFlags.Direct);
                default:
                    return false;
            }
        }
    }
    /**
     * Renders hold legs.
     */
    class FlightPathHoldLegRenderer {
        constructor() {
            this.arrowPattern = new FlightPathArrowPattern();
            this.legContinuousLineRenderer = new FlightPathLegContinuousLineRenderer();
            this.legLineRenderer = new msfsSdk.FlightPathLegLineRenderer(this.selectLineStyle.bind(this));
            this.legPatternRenderer = new msfsSdk.FlightPathLegPatternRenderer(this.selectPatternStyle.bind(this));
        }
        /**
         * Renders a hold leg to a canvas.
         * @param leg The leg to render.
         * @param context The canvas 2D rendering context to which to render.
         * @param streamStack The path stream stack to which to render.
         * @param plan The flight plan containing the leg to render.
         * @param activeLeg The active flight plan leg.
         * @param legIndex The global index of the leg to render.
         * @param activeLegIndex The global index of the active flight plan leg.
         * @param lnavData LNAV tracking data for the flight plan containing the leg to render, or undefined if LNAV is not
         * tracking the flight plan.
         */
        render(leg, context, streamStack, plan, activeLeg, legIndex, activeLegIndex, lnavData) {
            const isMissedApproachActive = !!activeLeg && msfsSdk.BitFlags.isAny(activeLeg.flags, msfsSdk.LegDefinitionFlags.MissedApproach);
            if (msfsSdk.BitFlags.isAll(leg.flags, msfsSdk.LegDefinitionFlags.MissedApproach) && !isMissedApproachActive) {
                this.legContinuousLineRenderer.render(leg, context, streamStack, msfsSdk.FlightPathLegRenderPart.Base, MapFlightPathStyles.MISSED_APPROACH_STROKE_WIDTH, MapFlightPathStyles.MISSED_APPROACH_STROKE_COLOR);
            }
            else if (legIndex < activeLegIndex || legIndex > activeLegIndex + 1) {
                const partsToRender = leg.leg.type === msfsSdk.LegType.HF ? msfsSdk.FlightPathLegRenderPart.All : msfsSdk.FlightPathLegRenderPart.Base | msfsSdk.FlightPathLegRenderPart.Egress;
                this.legLineRenderer.render(leg, context, streamStack, partsToRender, legIndex, activeLegIndex);
            }
            else if (legIndex === activeLegIndex + 1) {
                this.arrowPattern.color = MapFlightPathStyles.STROKE_COLOR;
                this.arrowPattern.context = context;
                if (leg.leg.type === msfsSdk.LegType.HF) {
                    // Draw the entire hold as arrows
                    this.legPatternRenderer.render(leg, context, streamStack, msfsSdk.FlightPathLegRenderPart.All, legIndex, activeLegIndex);
                }
                else {
                    // Draw the entire hold circuit as lines + ingress as arrows on top.
                    this.legLineRenderer.render(leg, context, streamStack, msfsSdk.FlightPathLegRenderPart.Base, legIndex, activeLegIndex);
                    this.legPatternRenderer.render(leg, context, streamStack, msfsSdk.FlightPathLegRenderPart.Ingress, legIndex, activeLegIndex);
                }
            }
            else { // legIndex === activeLegIndex
                let partsToRender = 0;
                if (!lnavData || lnavData.globalLegIndex !== legIndex) {
                    partsToRender = leg.leg.type === msfsSdk.LegType.HF ? msfsSdk.FlightPathLegRenderPart.All : msfsSdk.FlightPathLegRenderPart.Ingress | msfsSdk.FlightPathLegRenderPart.Base;
                }
                else {
                    partsToRender = msfsSdk.FlightPathLegRenderPart.Base;
                    if (lnavData.transitionMode === msfsSdk.LNavTransitionMode.Ingress) {
                        partsToRender |= msfsSdk.FlightPathLegRenderPart.Ingress;
                    }
                    if (!lnavData.isSuspended) {
                        partsToRender |= msfsSdk.FlightPathLegRenderPart.Egress;
                    }
                }
                // Draw the entire hold as arrows, except the inbound leg and egress.
                this.legLineRenderer.render(leg, context, streamStack, partsToRender, legIndex, activeLegIndex);
                this.arrowPattern.color = MapFlightPathStyles.ACTIVE_STROKE_COLOR;
                this.arrowPattern.context = context;
                this.legPatternRenderer.render(leg, context, streamStack, (msfsSdk.FlightPathLegRenderPart.Ingress | msfsSdk.FlightPathLegRenderPart.Base) & partsToRender, legIndex, activeLegIndex);
            }
        }
        /**
         * Selects a line style to render for a hold vector.
         * @param vector The vector for which to select a style.
         * @param isIngress Whether the vector is part of the ingress transition.
         * @param isEgress Whether the vector is part of the egress transition.
         * @param leg The flight plan leg containing the vector to render.
         * @param projection The map projection to use when rendering.
         * @param out The line style object to which to write the selected style.
         * @param legIndex The global index of the flight plan leg to which the vector belongs.
         * @param activeLegIndex The global index of the active flight plan leg.
         * @returns The selected line style for the vector.
         */
        selectLineStyle(vector, isIngress, isEgress, leg, projection, out, legIndex, activeLegIndex) {
            let color, width, dash;
            const isInboundOrEgress = isEgress || msfsSdk.BitFlags.isAll(vector.flags, msfsSdk.FlightPathVectorFlags.HoldInboundLeg);
            if (legIndex < activeLegIndex || legIndex > activeLegIndex + 1) {
                // Draw all vectors as a line with the inbound leg and egress solid and rest dashed.
                width = MapFlightPathStyles.ROLL_HEADING_DASH_STROKE_WIDTH;
                color = legIndex < activeLegIndex ? MapFlightPathStyles.PRIOR_STROKE_COLOR : MapFlightPathStyles.STROKE_COLOR;
                dash = isInboundOrEgress ? null : MapFlightPathStyles.ROLL_HEADING_DASH_ARRAY;
            }
            else if (legIndex === activeLegIndex + 1) {
                // Draw the ingress with arrows, and the hold circuit as a line with the inbound leg solid and rest dashed.
                width = MapFlightPathStyles.ROLL_HEADING_DASH_STROKE_WIDTH;
                color = MapFlightPathStyles.STROKE_COLOR;
                dash = isInboundOrEgress ? null : MapFlightPathStyles.ROLL_HEADING_DASH_ARRAY;
            }
            else { // legIndex === activeLegIndex
                // Draw the inbound leg and egress as solid lines, and the rest as magenta arrow background.
                if (isInboundOrEgress) {
                    width = MapFlightPathStyles.ACTIVE_STROKE_WIDTH;
                    color = MapFlightPathStyles.ACTIVE_STROKE_COLOR;
                }
                else {
                    width = MapFlightPathStyles.MAGENTA_ARROW_BG_WIDTH;
                    color = MapFlightPathStyles.MAGENTA_ARROW_BG_COLOR;
                }
                dash = null;
            }
            out.strokeWidth = width;
            out.strokeStyle = color;
            out.strokeDash = dash;
            out.outlineWidth = 0;
            out.isContinuous = true;
            return out;
        }
        /**
         * Selects a pattern style to render for a hold vector.
         * @param vector The vector for which to select a style.
         * @param isIngress Whether the vector is part of the ingress transition.
         * @param isEgress Whether the vector is part of the egress transition.
         * @param leg The flight plan leg containing the vector to render.
         * @param projection The map projection to use when rendering.
         * @param out The line style object to which to write the selected style.
         * @param legIndex The global index of the flight plan leg to which the vector belongs.
         * @param activeLegIndex The global index of the active flight plan leg.
         * @returns The selected pattern style for the vector.
         */
        selectPatternStyle(vector, isIngress, isEgress, leg, projection, out, legIndex, activeLegIndex) {
            if (legIndex !== activeLegIndex || !(isEgress || msfsSdk.BitFlags.isAll(vector.flags, msfsSdk.FlightPathVectorFlags.HoldInboundLeg))) {
                out.pattern = this.arrowPattern;
            }
            else {
                out.pattern = null;
            }
            out.isContinuous = true;
            return out;
        }
    }
    /**
     * Renders procedure turn legs.
     */
    class FlightPathProcTurnLegRenderer {
        /**
         * Constructor.
         */
        constructor() {
            this.arrowPattern = new FlightPathArrowPattern();
            this.arrowPattern.color = MapFlightPathStyles.ACTIVE_STROKE_COLOR;
            this.legLineRenderer = new FlightPathLegContinuousLineRenderer();
            this.legPatternRenderer = new msfsSdk.FlightPathLegPatternRenderer((vector, isIngress, isEgress, leg, projection, out) => {
                out.pattern = this.arrowPattern;
                out.isContinuous = true;
                return out;
            });
        }
        /**
         * Renders a procedure turn leg to a canvas.
         * @param leg The leg to render.
         * @param context The canvas 2D rendering context to which to render.
         * @param streamStack The path stream stack to which to render.
         * @param plan The flight plan containing the leg to render.
         * @param activeLeg The active flight plan leg.
         * @param legIndex The global index of the leg to render.
         * @param activeLegIndex The global index of the active flight plan leg.
         */
        render(leg, context, streamStack, plan, activeLeg, legIndex, activeLegIndex) {
            const isMissedApproachActive = !!activeLeg && msfsSdk.BitFlags.isAny(activeLeg.flags, msfsSdk.LegDefinitionFlags.MissedApproach);
            if (legIndex === activeLegIndex) {
                this.legLineRenderer.render(leg, context, streamStack, msfsSdk.FlightPathLegRenderPart.All, MapFlightPathStyles.MAGENTA_ARROW_BG_WIDTH, MapFlightPathStyles.MAGENTA_ARROW_BG_COLOR);
                this.arrowPattern.context = context;
                this.legPatternRenderer.render(leg, context, streamStack, msfsSdk.FlightPathLegRenderPart.All);
            }
            else {
                let width, style;
                if (msfsSdk.BitFlags.isAll(leg.flags, msfsSdk.LegDefinitionFlags.MissedApproach) && !isMissedApproachActive) {
                    width = MapFlightPathStyles.MISSED_APPROACH_STROKE_WIDTH;
                    style = MapFlightPathStyles.MISSED_APPROACH_STROKE_COLOR;
                }
                else if (legIndex < activeLegIndex) {
                    width = MapFlightPathStyles.ROLL_HEADING_DASH_STROKE_WIDTH;
                    style = MapFlightPathStyles.PRIOR_STROKE_COLOR;
                }
                else {
                    width = MapFlightPathStyles.ROLL_HEADING_DASH_STROKE_WIDTH;
                    style = MapFlightPathStyles.STROKE_COLOR;
                }
                this.legLineRenderer.render(leg, context, streamStack, msfsSdk.FlightPathLegRenderPart.All, width, style, MapFlightPathStyles.ROLL_HEADING_DASH_ARRAY);
            }
        }
    }
    /**
     * Renders Direct-To legs with user-defined courses. Each leg is rendered as a single line following a 500-nautical
     * mile great-circle path along the Direct-To course terminating at the end of the leg.
     */
    class FlightPathDirectToCourseLegRenderer {
        constructor() {
            this.lineRenderer = new msfsSdk.GeoCircleLineRenderer();
        }
        /**
         * Renders a Direct-To leg with user-defined course to a canvas.
         * @param leg The leg to render.
         * @param context The canvas 2D rendering context to which to render.
         * @param streamStack The path stream stack to which to render.
         * @param width The width of the rendered line.
         * @param style The style of the rendered line.
         * @param dash The dash array of the rendered line. Defaults to no dash.
         */
        render(leg, context, streamStack, width, style, dash) {
            var _a;
            // The leg should have a single base great-circle flight path vector ending at the direct-to fix
            const vector = (_a = leg.calculated) === null || _a === void 0 ? void 0 : _a.flightPath[0];
            if (!vector) {
                return;
            }
            const dtoFix = FlightPathDirectToCourseLegRenderer.geoPointCache[0].set(vector.endLat, vector.endLon);
            const dtoPath = msfsSdk.FlightPathUtils.setGeoCircleFromVector(vector, FlightPathDirectToCourseLegRenderer.geoCircleCache[0]);
            const start = dtoPath.offsetDistanceAlong(dtoFix, msfsSdk.UnitType.NMILE.convertTo(-500, msfsSdk.UnitType.GA_RADIAN), FlightPathDirectToCourseLegRenderer.geoPointCache[1], Math.PI);
            this.lineRenderer.render(dtoPath, start.lat, start.lon, dtoFix.lat, dtoFix.lon, context, streamStack, width, style, dash);
        }
    }
    FlightPathDirectToCourseLegRenderer.geoPointCache = [new msfsSdk.GeoPoint(0, 0), new msfsSdk.GeoPoint(0, 0)];
    FlightPathDirectToCourseLegRenderer.geoCircleCache = [new msfsSdk.GeoCircle(new Float64Array(3), 0)];
    /**
     * Renders OBS legs. Each leg is rendered as two lines: a magenta 500-nautical mile great-circle path along the OBS
     * course terminating at the end of the leg, and a white 500-nautical mile great-circle path along the OBS course
     * starting at the end of the leg.
     */
    class FlightPathObsLegRenderer {
        constructor() {
            this.lineRenderer = new msfsSdk.GeoCircleLineRenderer();
        }
        /**
         * Renders an OBS leg to a canvas.
         * @param leg The leg to render.
         * @param context The canvas 2D rendering context to which to render.
         * @param streamStack The path stream stack to which to render.
         * @param course The OBS course, in degrees magnetic.
         */
        render(leg, context, streamStack, course) {
            var _a, _b;
            if (((_a = leg.calculated) === null || _a === void 0 ? void 0 : _a.endLat) === undefined || ((_b = leg.calculated) === null || _b === void 0 ? void 0 : _b.endLon) === undefined) {
                return;
            }
            const obsFix = FlightPathObsLegRenderer.geoPointCache[0].set(leg.calculated.endLat, leg.calculated.endLon);
            const obsLat = obsFix.lat;
            const obsLon = obsFix.lon;
            const obsCourseTrue = msfsSdk.MagVar.magneticToTrue(course, obsLat, obsLon);
            const obsPath = FlightPathObsLegRenderer.geoCircleCache[0].setAsGreatCircle(obsFix, obsCourseTrue);
            const start = obsPath.offsetDistanceAlong(obsFix, msfsSdk.UnitType.NMILE.convertTo(-500, msfsSdk.UnitType.GA_RADIAN), FlightPathObsLegRenderer.geoPointCache[1]);
            const startLat = start.lat;
            const startLon = start.lon;
            const end = obsPath.offsetDistanceAlong(obsFix, msfsSdk.UnitType.NMILE.convertTo(500, msfsSdk.UnitType.GA_RADIAN), FlightPathObsLegRenderer.geoPointCache[1]);
            const endLat = end.lat;
            const endLon = end.lon;
            this.lineRenderer.render(obsPath, startLat, startLon, obsLat, obsLon, context, streamStack, 4, 'magenta');
            this.lineRenderer.render(obsPath, obsLat, obsLon, endLat, endLon, context, streamStack, 4, 'white');
        }
    }
    FlightPathObsLegRenderer.geoPointCache = [new msfsSdk.GeoPoint(0, 0), new msfsSdk.GeoPoint(0, 0)];
    FlightPathObsLegRenderer.geoCircleCache = [new msfsSdk.GeoCircle(new Float64Array(3), 0)];
    /**
     * Renders vectors-to-final legs. Each leg is rendered as a line representing a 30-nautical mile great-circle path
     * along the VTF course terminating at the end of the leg.
     */
    class FlightPathVtfLegRenderer {
        constructor() {
            this.lineRenderer = new msfsSdk.GeoCircleLineRenderer();
        }
        /**
         * Renders a vectors-to-final leg to a canvas.
         * @param leg The leg to render.
         * @param context The canvas 2D rendering context to which to render.
         * @param streamStack The path stream stack to which to render.
         * @param width The width of the rendered line.
         * @param style The style of the rendered line.
         * @param dash The dash array of the rendered line. Defaults to no dash.
         */
        render(leg, context, streamStack, width, style, dash) {
            // VTF legs have their terminator coordinates and course written to the lat/lon and course properties, respectively.
            if (leg.calculated === undefined || leg.leg.lat === undefined || leg.leg.lon === undefined) {
                return;
            }
            const course = leg.leg.trueDegrees ? leg.leg.course : msfsSdk.MagVar.magneticToTrue(leg.leg.course, leg.calculated.courseMagVar);
            const end = FlightPathVtfLegRenderer.geoPointCache[0].set(leg.leg.lat, leg.leg.lon);
            const path = FlightPathVtfLegRenderer.geoCircleCache[0].setAsGreatCircle(end, course);
            const start = path.offsetDistanceAlong(end, msfsSdk.UnitType.NMILE.convertTo(-30, msfsSdk.UnitType.GA_RADIAN), FlightPathVtfLegRenderer.geoPointCache[1]);
            this.lineRenderer.render(path, start.lat, start.lon, end.lat, end.lon, context, streamStack, width, style, dash);
        }
    }
    FlightPathVtfLegRenderer.geoPointCache = [new msfsSdk.GeoPoint(0, 0), new msfsSdk.GeoPoint(0, 0)];
    FlightPathVtfLegRenderer.geoCircleCache = [new msfsSdk.GeoCircle(new Float64Array(3), 0)];

    /**
     * The default base-route flight plan renderer for Garmin maps. Only renders non-transition flight path vectors within
     * flight plan legs.
     */
    class DefaultBaseFlightPathPlanRenderer extends msfsSdk.AbstractFlightPathPlanRenderer {
        constructor() {
            super(...arguments);
            this.lineRenderer = new msfsSdk.FlightPathLegLineRenderer(this.selectLineStyle.bind(this));
            this.dtoCourseRenderer = new FlightPathDirectToCourseLegRenderer();
            this.vtfRenderer = new FlightPathVtfLegRenderer();
        }
        /** @inheritdoc */
        renderLeg(leg, plan, activeLeg, legIndex, activeLegIndex, context, streamStack) {
            if (!msfsSdk.BitFlags.isAll(leg.flags, msfsSdk.LegDefinitionFlags.DirectTo) || legIndex === activeLegIndex) {
                switch (leg.leg.type) {
                    case msfsSdk.LegType.HF:
                    case msfsSdk.LegType.HM:
                    case msfsSdk.LegType.HA:
                    case msfsSdk.LegType.PI:
                        break;
                    case msfsSdk.LegType.CF:
                        if (msfsSdk.BitFlags.isAll(leg.flags, msfsSdk.LegDefinitionFlags.DirectTo)) {
                            this.dtoCourseRenderer.render(leg, context, streamStack, MapFlightPathStyles.BASE_STROKE_WIDTH, MapFlightPathStyles.BASE_STROKE_COLOR);
                            break;
                        }
                        else if (msfsSdk.BitFlags.isAll(leg.flags, msfsSdk.LegDefinitionFlags.VectorsToFinalFaf)) {
                            this.vtfRenderer.render(leg, context, streamStack, MapFlightPathStyles.BASE_STROKE_WIDTH, MapFlightPathStyles.BASE_STROKE_COLOR);
                            break;
                        }
                    // TODO: draw correct base for fallback CF legs.
                    // eslint-disable-next-line no-fallthrough
                    default:
                        this.lineRenderer.render(leg, context, streamStack, msfsSdk.FlightPathLegRenderPart.Base);
                }
            }
        }
        /**
         * Selects a line style to render for a vector.
         * @param vector The vector for which to select a style.
         * @param isIngress Whether the vector is part of the ingress transition.
         * @param isEgress Whether the vector is part of the egress transition.
         * @param leg The flight plan leg containing the vector to render.
         * @param projection The map projection to use when rendering.
         * @param out The line style object to which to write the selected style.
         * @returns The selected line style for the vector.
         */
        selectLineStyle(vector, isIngress, isEgress, leg, projection, out) {
            if (this.isRollHeadingVector(vector, leg)) {
                out.strokeWidth = 0;
            }
            else {
                out.strokeWidth = MapFlightPathStyles.BASE_STROKE_WIDTH;
                out.strokeStyle = MapFlightPathStyles.BASE_STROKE_COLOR;
            }
            out.outlineWidth = 0;
            out.isContinuous = true;
            return out;
        }
        /**
         * Checks if a flight path vector should be styled as a roll-heading vector.
         * @param vector A flight path vector.
         * @param leg The flight plan leg to which the vector belongs.
         * @returns Whether the flight path vector should be styled as a roll-heading vector.
         */
        isRollHeadingVector(vector, leg) {
            if (msfsSdk.BitFlags.isAny(vector.flags, msfsSdk.FlightPathVectorFlags.Fallback)) {
                return true;
            }
            switch (leg.leg.type) {
                case msfsSdk.LegType.CF:
                    return msfsSdk.BitFlags.isAny(vector.flags, msfsSdk.FlightPathVectorFlags.InterceptCourse | msfsSdk.FlightPathVectorFlags.Direct);
                default:
                    return false;
            }
        }
    }
    /**
     * The default full-route flight plan renderer for Garmin maps. Renders all flight path vectors within flight plan legs,
     * including transition vectors.
     */
    class DefaultFullFlightPathPlanRenderer extends msfsSdk.AbstractFlightPathPlanRenderer {
        constructor() {
            super(...arguments);
            this.defaultRenderer = new FlightPathDefaultLegRenderer();
            this.holdRenderer = new FlightPathHoldLegRenderer();
            this.procTurnRenderer = new FlightPathProcTurnLegRenderer();
            this.dtoCourseRenderer = new FlightPathDirectToCourseLegRenderer();
            this.vtfRenderer = new FlightPathVtfLegRenderer();
        }
        /** @inheritdoc */
        renderLeg(leg, plan, activeLeg, legIndex, activeLegIndex, context, streamStack, lnavData, isMissedApproachActive) {
            var _a, _b;
            let width, style;
            if (legIndex === activeLegIndex) {
                width = MapFlightPathStyles.ACTIVE_STROKE_WIDTH;
                style = MapFlightPathStyles.ACTIVE_STROKE_COLOR;
            }
            else if (legIndex < activeLegIndex) {
                width = MapFlightPathStyles.PRIOR_STROKE_WIDTH;
                style = MapFlightPathStyles.PRIOR_STROKE_COLOR;
            }
            else if (!isMissedApproachActive && msfsSdk.BitFlags.isAny(leg.flags, msfsSdk.LegDefinitionFlags.MissedApproach)) {
                width = MapFlightPathStyles.MISSED_APPROACH_STROKE_WIDTH;
                style = MapFlightPathStyles.MISSED_APPROACH_STROKE_COLOR;
            }
            else {
                width = MapFlightPathStyles.STROKE_WIDTH;
                style = MapFlightPathStyles.STROKE_COLOR;
            }
            switch (leg.leg.type) {
                case msfsSdk.LegType.HF:
                case msfsSdk.LegType.HM:
                case msfsSdk.LegType.HA:
                    this.holdRenderer.render(leg, context, streamStack, plan, activeLeg, legIndex, activeLegIndex, lnavData);
                    break;
                case msfsSdk.LegType.PI:
                    this.procTurnRenderer.render(leg, context, streamStack, plan, activeLeg, legIndex, activeLegIndex);
                    break;
                case msfsSdk.LegType.CF: {
                    if (msfsSdk.BitFlags.isAll(leg.flags, msfsSdk.LegDefinitionFlags.DirectTo)) {
                        this.dtoCourseRenderer.render(leg, context, streamStack, width, style);
                        break;
                    }
                    else if (msfsSdk.BitFlags.isAll(leg.flags, msfsSdk.LegDefinitionFlags.VectorsToFinalFaf)) {
                        this.vtfRenderer.render(leg, context, streamStack, width, style);
                        break;
                    }
                }
                // eslint-disable-next-line no-fallthrough
                default:
                    this.defaultRenderer.render(leg, context, streamStack, legIndex, activeLegIndex, (_a = plan.tryGetLeg(legIndex - 1)) !== null && _a !== void 0 ? _a : undefined, (_b = plan.tryGetLeg(legIndex + 1)) !== null && _b !== void 0 ? _b : undefined, isMissedApproachActive);
            }
        }
    }
    /**
     * The default flight plan renderer for Garmin maps.
     */
    class DefaultFlightPathPlanRenderer {
        constructor() {
            this.baseRouteRenderer = new DefaultBaseFlightPathPlanRenderer();
            this.fullRouteRenderer = new DefaultFullFlightPathPlanRenderer();
            this.obsRenderer = new FlightPathObsLegRenderer();
        }
        /** @inheritdoc */
        render(plan, context, streamStack, renderEntirePlan, activeLegIndex, lnavData, obsCourse) {
            const isObsActive = obsCourse !== undefined;
            const baseRouteStartIndex = this.getBaseRouteStartIndex(plan, renderEntirePlan, activeLegIndex, isObsActive);
            this.baseRouteRenderer.render(plan, baseRouteStartIndex, undefined, context, streamStack);
            if (isObsActive) {
                this.obsRenderer.render(plan.getLeg(activeLegIndex), context, streamStack, obsCourse);
            }
            else {
                const fullRouteStartIndex = this.getFullRouteStartIndex(plan, renderEntirePlan, activeLegIndex);
                const isMissedApproachActive = activeLegIndex >= 0
                    && activeLegIndex < plan.length
                    && msfsSdk.BitFlags.isAny(plan.getLeg(activeLegIndex).flags, msfsSdk.LegDefinitionFlags.MissedApproach);
                this.fullRouteRenderer.render(plan, fullRouteStartIndex, undefined, context, streamStack, lnavData, isMissedApproachActive);
            }
        }
        /**
         * Gets the global index of the first leg for which to render the base route.
         * @param plan The flight plan to render.
         * @param renderEntirePlan Whether to render the entire plan.
         * @param activeLegIndex The global index of the active flight plan leg, or -1 if there is no active leg.
         * @param isObsActive Whether OBS is active.
         * @returns The global index of the first leg for which to render the base route.
         */
        getBaseRouteStartIndex(plan, renderEntirePlan, activeLegIndex, isObsActive) {
            if (renderEntirePlan) {
                return 0;
            }
            if (activeLegIndex < 0) {
                return plan.length;
            }
            if (isObsActive) {
                return activeLegIndex;
            }
            return Math.max(0, this.getActiveFromLegIndex(plan, activeLegIndex));
        }
        /**
         * Gets the global index of the first leg for which to render the full route.
         * @param plan The flight plan to render.
         * @param renderEntirePlan Whether to render the entire plan.
         * @param activeLegIndex The global index of the active flight plan leg, or -1 if there is no active leg.
         * @returns The global index of the first leg for which to render the full route.
         */
        getFullRouteStartIndex(plan, renderEntirePlan, activeLegIndex) {
            if (renderEntirePlan) {
                return 0;
            }
            if (activeLegIndex < 0) {
                return plan.length;
            }
            return Math.max(0, this.getActiveFromLegIndex(plan, activeLegIndex));
        }
        /**
         * Gets the global index of the leg from which the active leg of a flight plan originates.
         * @param plan A flight plan.
         * @param activeLegIndex The global index of the active flight plan leg.
         * @returns The global index of the leg from which the active leg originates, or -1 if one could not be found.
         */
        getActiveFromLegIndex(plan, activeLegIndex) {
            const activeLeg = plan.tryGetLeg(activeLegIndex);
            if (!activeLeg) {
                return -1;
            }
            const segmentIndex = plan.getSegmentIndex(activeLegIndex);
            const segmentLegIndex = activeLegIndex - plan.getSegment(segmentIndex).offset;
            return FmsUtils.getNominalFromLegIndex(plan, segmentIndex, segmentLegIndex);
        }
    }

    /**
     * Calculates map projection parameters to fit flight plan foci.
     */
    class MapFlightPlanFocusCalculator {
        /**
         * Constructor.
         * @param mapProjection This calculator's map projection.
         */
        constructor(mapProjection) {
            this.mapProjection = mapProjection;
            this.fovCalculator = new msfsSdk.MapFieldOfViewCalculator();
            this.pointHeap = new msfsSdk.ResourceHeap(() => new msfsSdk.GeoPoint(0, 0), () => { });
        }
        /**
         * Calculates a maximum range and target center for a given flight plan focus such that the terminators of all legs
         * in the focus are visible in this calculator's map projection. If there is only one leg terminator in the specified
         * focus, the calculated range will be equal to 0. If a range and target could not be calculated, NaN will be written
         * to the results.
         * @param focus The array of legs on which to focus.
         * @param margins The margins around the projected map boundaries to respect. Expressed as [left, top, right, bottom].
         * @param ppos The current position of the airplane.
         * @param out The object to which to write the results.
         * @returns The calculated range and target for the specified focus.
         */
        calculateRangeTarget(focus, margins, ppos, out) {
            out.range = NaN;
            out.target.set(NaN, NaN);
            if (!focus) {
                return out;
            }
            if (!(focus instanceof Array)) {
                out.range = 0;
                out.target.set(focus);
                return out;
            }
            focus = focus;
            const targetWidth = this.mapProjection.getProjectedSize()[0] - margins[0] - margins[2];
            const targetHeight = this.mapProjection.getProjectedSize()[1] - margins[1] - margins[3];
            if (targetWidth * targetHeight <= 0) {
                return out;
            }
            const points = [];
            let currentLat = undefined;
            let currentLon = undefined;
            const len = focus.length;
            for (let i = 0; i < len; i++) {
                const leg = focus[i];
                if (msfsSdk.BitFlags.isAll(leg.flags, msfsSdk.LegDefinitionFlags.DirectTo) && leg.leg.type === msfsSdk.LegType.CF) {
                    // Special case for Direct-To legs with user-defined course: PPOS needs to be in focus instead of the start
                    // of the leg (which is arbitrary)
                    if (!isNaN(ppos.lat) && !isNaN(ppos.lon)) {
                        currentLat = ppos.lat;
                        currentLon = ppos.lon;
                        points.push(ppos);
                    }
                }
                else if (leg.calculated !== undefined) {
                    if (leg.calculated.startLat !== undefined && leg.calculated.startLon !== undefined
                        && leg.calculated.startLat !== currentLat && leg.calculated.startLon !== currentLon) {
                        currentLat = leg.calculated.startLat;
                        currentLon = leg.calculated.startLon;
                        points.push(this.pointHeap.allocate().set(currentLat, currentLon));
                    }
                }
                if (leg.calculated !== undefined
                    && leg.calculated.endLat !== undefined && leg.calculated.endLon !== undefined
                    && leg.calculated.endLat !== currentLat && leg.calculated.endLon !== currentLon) {
                    currentLat = leg.calculated.endLat;
                    currentLon = leg.calculated.endLon;
                    points.push(this.pointHeap.allocate().set(currentLat, currentLon));
                }
            }
            this.fovCalculator.calculateFov(this.mapProjection, points, margins, out);
            for (let i = 0; i < points.length; i++) {
                const point = points[i];
                if (point instanceof msfsSdk.GeoPoint) {
                    this.pointHeap.free(point);
                }
            }
            return out;
        }
    }

    /**
     * An abstract implementation of FlightPlanLegWaypointsRecord.
     */
    class AbstractFlightPlanLegWaypointsRecord {
        /**
         * Constructor.
         * @param leg The flight plan leg associated with this record.
         * @param waypointRenderer The renderer used to render this record's waypoints.
         * @param facLoader The facility loader used by this waypoint.
         * @param inactiveRenderRole The role(s) under which the waypoint should be registered when it is part of an inactive
         * leg.
         * @param activeRenderRole The role(s) under which the waypoint should be registered when it is part of an active
         * leg.
         */
        constructor(leg, waypointRenderer, facLoader, inactiveRenderRole, activeRenderRole) {
            this.leg = leg;
            this.waypointRenderer = waypointRenderer;
            this.facLoader = facLoader;
            this.inactiveRenderRole = inactiveRenderRole;
            this.activeRenderRole = activeRenderRole;
            this.uid = `flightplan-wptrecord-${AbstractFlightPlanLegWaypointsRecord.uidSource++}`;
            this.isActive = false;
        }
        /**
         * Registers a waypoint with this record's waypoint renderer.
         * @param waypoint A waypoint.
         * @param role The role(s) under which the waypoint should be registered.
         */
        registerWaypoint(waypoint, role) {
            this.waypointRenderer.register(waypoint, role, this.uid);
        }
        /**
         * Removes a registration for a waypoint from this record's waypoint renderer.
         * @param waypoint A waypoint.
         * @param role The role(s) from which the waypoint should be deregistered.
         */
        deregisterWaypoint(waypoint, role) {
            this.waypointRenderer.deregister(waypoint, role, this.uid);
        }
    }
    AbstractFlightPlanLegWaypointsRecord.uidSource = 0;
    /**
     * A record with a single waypoint based on its flight plan leg's fixIcao property.
     */
    class FixIcaoWaypointsRecord extends AbstractFlightPlanLegWaypointsRecord {
        /**
         * Constructor.
         * @param leg The flight plan leg associated with this record.
         * @param waypointRenderer The renderer used to render this record's waypoints.
         * @param facLoader The facility loader used by this waypoint.
         * @param facWaypointCache The facility waypoint cache used by this record.
         * @param inactiveRenderRole The role(s) under which the waypoint should be registered when it is part of an inactive
         * leg.
         * @param activeRenderRole The role(s) under which the waypoint should be registered when it is part of an active
         * leg.
         */
        constructor(leg, waypointRenderer, facLoader, facWaypointCache, inactiveRenderRole, activeRenderRole) {
            super(leg, waypointRenderer, facLoader, inactiveRenderRole, activeRenderRole);
            this.facWaypointCache = facWaypointCache;
            this._waypoint = null;
        }
        // eslint-disable-next-line jsdoc/require-returns
        /**
         * This record's waypoint.
         */
        get waypoint() {
            return this._waypoint;
        }
        /** @inheritdoc */
        async refresh(isActive) {
            const icao = this.leg.leg.fixIcao;
            if (!this._waypoint && icao !== '' && icao !== msfsSdk.ICAO.emptyIcao) {
                this._waypoint = await this.getFacilityWaypoint(icao);
                if (this._waypoint) {
                    this.registerWaypoint(this._waypoint, this.inactiveRenderRole);
                    if (this.isActive) {
                        this.registerWaypoint(this._waypoint, this.activeRenderRole);
                    }
                }
            }
            if (isActive !== this.isActive) {
                if (this._waypoint) {
                    isActive
                        ? this.registerWaypoint(this._waypoint, this.activeRenderRole)
                        : this.deregisterWaypoint(this._waypoint, this.activeRenderRole);
                }
                this.isActive = isActive;
            }
        }
        /**
         * Gets a facility waypoint from an ICAO string.
         * @param icao A facility ICAO string.
         * @returns a facility waypoint, or null if one could not be created.
         */
        async getFacilityWaypoint(icao) {
            try {
                const facility = await this.facLoader.getFacility(msfsSdk.ICAO.getFacilityType(icao), icao);
                return this.facWaypointCache.get(facility);
            }
            catch (e) {
                // noop
            }
            return null;
        }
        /** @inheritdoc */
        destroy() {
            if (!this._waypoint) {
                return;
            }
            this.deregisterWaypoint(this._waypoint, this.inactiveRenderRole);
            this.isActive && this.deregisterWaypoint(this._waypoint, this.activeRenderRole);
        }
    }
    /**
     * A record with a single flight path waypoint representing its flight plan leg's terminator fix.
     */
    class FlightPathTerminatorWaypointsRecord extends AbstractFlightPlanLegWaypointsRecord {
        constructor() {
            super(...arguments);
            this.id = FlightPathTerminatorWaypointsRecord.id++;
            this._waypoint = null;
        }
        // eslint-disable-next-line jsdoc/require-returns
        /**
         * This record's flight path waypoint.
         */
        get waypoint() {
            return this._waypoint;
        }
        /** @inheritdoc */
        async refresh(isActive) {
            var _a, _b, _c;
            const lastVector = (_a = this.leg.calculated) === null || _a === void 0 ? void 0 : _a.flightPath[this.leg.calculated.flightPath.length - 1];
            if (lastVector) {
                (_b = this._waypointLocation) !== null && _b !== void 0 ? _b : (this._waypointLocation = msfsSdk.GeoPointSubject.create(new msfsSdk.GeoPoint(0, 0)));
                this._waypointLocation.set(lastVector.endLat, lastVector.endLon);
                if (!this._waypoint) {
                    const ident = (_c = this.leg.name) !== null && _c !== void 0 ? _c : '';
                    this._waypoint = new msfsSdk.FlightPathWaypoint(this._waypointLocation, this.leg, `${ident}_${this.id}`, ident);
                    this.registerWaypoint(this._waypoint, this.inactiveRenderRole);
                    if (this.isActive) {
                        this.registerWaypoint(this._waypoint, this.activeRenderRole);
                    }
                }
            }
            else {
                this.cleanUpWaypoint();
            }
            if (isActive !== this.isActive) {
                if (this._waypoint) {
                    isActive
                        ? this.registerWaypoint(this._waypoint, this.activeRenderRole)
                        : this.deregisterWaypoint(this._waypoint, this.activeRenderRole);
                }
                this.isActive = isActive;
            }
        }
        /** @inheritdoc */
        destroy() {
            this.cleanUpWaypoint();
        }
        /**
         * Deregisters this record's waypoint, if it exists, from the waypoint renderer.
         */
        cleanUpWaypoint() {
            if (!this._waypoint) {
                return;
            }
            this.deregisterWaypoint(this._waypoint, this.inactiveRenderRole);
            this.isActive && this.deregisterWaypoint(this._waypoint, this.activeRenderRole);
        }
    }
    FlightPathTerminatorWaypointsRecord.id = 0;
    /**
     * A record for procedure turn (PI) legs. Maintains two waypoints, both located at the PI leg's origin fix. The first
     * waypoint is a standard FacilityWaypoint which is never rendered in an active flight plan waypoint role. The second
     * is a ProcedureTurnWaypoint with an ident string equal to the PI leg's given name and which can be rendered in an
     * active flight plan waypoint role.
     */
    class ProcedureTurnLegWaypointsRecord extends AbstractFlightPlanLegWaypointsRecord {
        /**
         * Constructor.
         * @param leg The flight plan leg associated with this record.
         * @param waypointRenderer The renderer used to render this record's waypoints.
         * @param facLoader The facility loader used by this waypoint.
         * @param facWaypointCache The facility waypoint cache used by this record.
         * @param inactiveRenderRole The role(s) under which the waypoint should be registered when it is part of an inactive
         * leg.
         * @param activeRenderRole The role(s) under which the waypoint should be registered when it is part of an active
         * leg.
         */
        constructor(leg, waypointRenderer, facLoader, facWaypointCache, inactiveRenderRole, activeRenderRole) {
            super(leg, waypointRenderer, facLoader, inactiveRenderRole, activeRenderRole);
            this.ptWaypoint = null;
            this.fixIcaoRecord = new FixIcaoWaypointsRecord(leg, waypointRenderer, facLoader, facWaypointCache, inactiveRenderRole, activeRenderRole);
        }
        /** @inheritdoc */
        async refresh(isActive) {
            var _a;
            await this.fixIcaoRecord.refresh(false);
            if (!this.ptWaypoint && this.fixIcaoRecord.waypoint) {
                this.ptWaypoint = new ProcedureTurnLegWaypoint(this.fixIcaoRecord.waypoint, (_a = this.leg.name) !== null && _a !== void 0 ? _a : '');
                this.registerWaypoint(this.ptWaypoint, this.inactiveRenderRole);
                if (this.isActive) {
                    this.deregisterWaypoint(this.ptWaypoint, this.activeRenderRole);
                }
            }
            if (isActive !== this.isActive) {
                if (this.ptWaypoint) {
                    isActive
                        ? this.registerWaypoint(this.ptWaypoint, this.activeRenderRole)
                        : this.deregisterWaypoint(this.ptWaypoint, this.activeRenderRole);
                }
                this.isActive = isActive;
            }
        }
        /** @inheritdoc */
        destroy() {
            this.fixIcaoRecord.destroy();
            if (!this.ptWaypoint) {
                return;
            }
            this.deregisterWaypoint(this.ptWaypoint, this.inactiveRenderRole);
            this.isActive && this.deregisterWaypoint(this.ptWaypoint, this.activeRenderRole);
        }
    }
    /**
     * A waypoint marking a procedure turn leg.
     */
    class ProcedureTurnLegWaypoint extends msfsSdk.CustomWaypoint {
        /**
         * Constructor.
         * @param waypoint The facility waypoint marking this waypoint's location.
         * @param ident The ident string of this waypoint.
         */
        constructor(waypoint, ident) {
            super(waypoint.location, `${ProcedureTurnLegWaypoint.UID_PREFIX}_${ident}_${waypoint.facility.get().icao}`);
            this.ident = ident;
        }
    }
    ProcedureTurnLegWaypoint.UID_PREFIX = 'PI';

    /**
     * Manages flight plan waypoint records.
     */
    class MapFlightPlanWaypointRecordManager {
        /**
         * Constructor.
         * @param facLoader This manager's facility loader.
         * @param facWaypointCache This manager's facility waypoint cache.
         * @param waypointRenderer This manager's waypoint renderer.
         * @param inactiveRenderRole The role(s) under which waypoints should be registered when they are part of an inactive
         * leg.
         * @param activeRenderRole The role(s) under which waypoints should be registered when they are part of an active
         * leg.
         */
        constructor(facLoader, facWaypointCache, waypointRenderer, inactiveRenderRole, activeRenderRole) {
            this.facLoader = facLoader;
            this.facWaypointCache = facWaypointCache;
            this.waypointRenderer = waypointRenderer;
            this.inactiveRenderRole = inactiveRenderRole;
            this.activeRenderRole = activeRenderRole;
            this.legWaypointRecords = new Map();
            this._isBusy = false;
        }
        /**
         * Checks whether this manager is busy with a waypoint refresh.
         * @returns Whether this manager is busy with a waypoint refresh.
         */
        isBusy() {
            return this._isBusy;
        }
        // eslint-disable-next-line jsdoc/require-throws
        /**
         * Refreshes this manager's waypoint records, keeping them up to date with a specified flight plan.
         * @param flightPlan A flight plan.
         * @param activeLegIndex The global index of the active flight plan leg, or -1 if there is no active leg.
         * @param repick Whether to repick waypoints.
         * @param startIndex The global index of the first flight plan leg from which to pick waypoints, inclusive. Defaults
         * to 0. Ignored if `repick` is false.
         * @param endIndex The global index of the last flight plan leg from which to pick waypoints, inclusive. Defaults to
         * `flightPlan.length - 1`. Ignored if `repick` is false.
         */
        async refreshWaypoints(flightPlan, activeLegIndex, repick, startIndex, endIndex) {
            if (this._isBusy) {
                throw new Error('MapFlightPlanWaypointRecordManager: cannot refresh waypoints while busy');
            }
            this._isBusy = true;
            if (!flightPlan) {
                // Remove all waypoint records.
                for (const record of this.legWaypointRecords.values()) {
                    record.destroy();
                }
                this.legWaypointRecords.clear();
                this._isBusy = false;
                return;
            }
            const activeLeg = flightPlan.tryGetLeg(activeLegIndex);
            if (repick) {
                startIndex !== null && startIndex !== void 0 ? startIndex : (startIndex = 0);
                endIndex !== null && endIndex !== void 0 ? endIndex : (endIndex = flightPlan.length - 1);
                const legsToDisplay = new Set();
                // Gather all legs to display.
                let legIndex = startIndex;
                for (const leg of flightPlan.legs(false, startIndex)) {
                    if (legIndex > endIndex) {
                        break;
                    }
                    if (!msfsSdk.BitFlags.isAny(leg.flags, msfsSdk.LegDefinitionFlags.DirectTo | msfsSdk.LegDefinitionFlags.VectorsToFinal) || legIndex === flightPlan.activeLateralLeg) {
                        legsToDisplay.add(leg);
                    }
                    legIndex++;
                }
                // Remove records of legs that are no longer in the set of legs to display.
                for (const record of this.legWaypointRecords.values()) {
                    if (legsToDisplay.has(record.leg)) {
                        legsToDisplay.delete(record.leg);
                    }
                    else {
                        record.destroy();
                        this.legWaypointRecords.delete(record.leg);
                    }
                }
                // Create new records for legs to display that don't already have records.
                for (const leg of legsToDisplay) {
                    const record = this.createLegWaypointsRecord(leg);
                    this.legWaypointRecords.set(leg, record);
                }
            }
            const waypointRefreshes = [];
            for (const record of this.legWaypointRecords.values()) {
                waypointRefreshes.push(record.refresh(record.leg === activeLeg));
            }
            await Promise.all(waypointRefreshes);
            this._isBusy = false;
        }
        /**
         * Creates a FlightPlanLegWaypointsRecord for a specified flight plan leg.
         * @param leg A flight plan leg.
         * @returns A FlightPlanLegWaypointsRecord for the specified flight plan leg.
         */
        createLegWaypointsRecord(leg) {
            switch (leg.leg.type) {
                case msfsSdk.LegType.CD:
                case msfsSdk.LegType.VD:
                case msfsSdk.LegType.CR:
                case msfsSdk.LegType.VR:
                case msfsSdk.LegType.FC:
                case msfsSdk.LegType.FD:
                case msfsSdk.LegType.FA:
                case msfsSdk.LegType.CA:
                case msfsSdk.LegType.VA:
                case msfsSdk.LegType.FM:
                case msfsSdk.LegType.VM:
                case msfsSdk.LegType.CI:
                case msfsSdk.LegType.VI:
                    return new FlightPathTerminatorWaypointsRecord(leg, this.waypointRenderer, this.facLoader, this.inactiveRenderRole, this.activeRenderRole);
                case msfsSdk.LegType.PI:
                    return new ProcedureTurnLegWaypointsRecord(leg, this.waypointRenderer, this.facLoader, this.facWaypointCache, this.inactiveRenderRole, this.activeRenderRole);
                default:
                    return new FixIcaoWaypointsRecord(leg, this.waypointRenderer, this.facLoader, this.facWaypointCache, this.inactiveRenderRole, this.activeRenderRole);
            }
        }
    }

    /**
     * A {@link MapFlightPlanDataProvider} which provides data for a standalone flight plan not owned by a flight planner.
     */
    class MapStandaloneFlightPlanPlanDataProvider {
        /**
         * Constructor.
         * @param plan A subscribable which provides the flight plan for this data provider.
         */
        constructor(plan) {
            this.plan = plan;
            /** @inheritdoc */
            this.planModified = new msfsSdk.SubEvent();
            /** @inheritdoc */
            this.planCalculated = new msfsSdk.SubEvent();
            this._activeLateralLegIndex = msfsSdk.Subject.create(0);
            /** @inheritdoc */
            this.activeLateralLegIndex = this._activeLateralLegIndex;
            /** @inheritdoc */
            this.lnavData = msfsSdk.Subject.create(undefined);
            /** @inheritdoc */
            this.vnavState = msfsSdk.Subject.create(msfsSdk.VNavState.Disabled);
            /** @inheritdoc */
            this.vnavPathMode = msfsSdk.Subject.create(msfsSdk.VNavPathMode.None);
            /** @inheritdoc */
            this.vnavTodLegIndex = msfsSdk.Subject.create(-1);
            /** @inheritdoc */
            this.vnavBodLegIndex = msfsSdk.Subject.create(-1);
            /** @inheritdoc */
            this.vnavTodLegDistance = msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.METER.createNumber(0));
            /** @inheritdoc */
            this.vnavDistanceToTod = msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.METER.createNumber(0));
            /** @inheritdoc */
            this.vnavTocLegIndex = msfsSdk.Subject.create(-1);
            /** @inheritdoc */
            this.vnavBocLegIndex = msfsSdk.Subject.create(-1);
            /** @inheritdoc */
            this.vnavTocLegDistance = msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.METER.createNumber(0));
            /** @inheritdoc */
            this.vnavDistanceToToc = msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.METER.createNumber(0));
            /** @inheritdoc */
            this.obsCourse = msfsSdk.Subject.create(undefined);
            this.oldPlan = null;
            this.planSub = plan.sub(flightPlan => {
                if (this.oldPlan !== null) {
                    this.oldPlan.events.onActiveLegChanged = undefined;
                    this.oldPlan.events.onCalculated = undefined;
                }
                this.oldPlan = flightPlan;
                if (flightPlan !== null) {
                    this._activeLateralLegIndex.set(flightPlan.activeLateralLeg);
                    flightPlan.events.onActiveLegChanged = (index, segmentIndex, legIndex, previousSegmentIndex, previousLegIndex, type) => {
                        if (type === msfsSdk.ActiveLegType.Lateral) {
                            this._activeLateralLegIndex.set(index);
                        }
                    };
                    flightPlan.events.onCalculated = () => {
                        this.planCalculated.notify(this);
                    };
                }
                else {
                    this._activeLateralLegIndex.set(0);
                }
            }, true);
        }
        /**
         * Destroys this data provider.
         */
        destroy() {
            this.planSub.destroy();
            if (this.oldPlan !== null) {
                this.oldPlan.events.onActiveLegChanged = undefined;
                this.oldPlan.events.onCalculated = undefined;
            }
            this.oldPlan = null;
        }
    }

    /**
     * The full-route renderer for procedure preview flight plan paths. Renders all flight path vectors within flight plan
     * legs, including transition vectors, with support for different styles for procedure and transition previews.
     */
    class ProcMapFullFlightPathPlanRenderer extends msfsSdk.AbstractFlightPathPlanRenderer {
        constructor() {
            super(...arguments);
            this.lineRenderer = new FlightPathLegContinuousLineRenderer();
            this.defaultRenderer = new FlightPathDefaultLegRenderer();
            this.defaultTransitionRenderer = new msfsSdk.FlightPathLegLineRenderer(this.selectTransitionLineStyle.bind(this));
            this.holdRenderer = new FlightPathHoldLegRenderer();
            this.procTurnRenderer = new FlightPathProcTurnLegRenderer();
            this.vtfRenderer = new FlightPathVtfLegRenderer();
        }
        /** @inheritdoc */
        renderLeg(leg, plan, activeLeg, legIndex, activeLegIndex, context, streamStack, isTransition) {
            var _a, _b;
            let width, style;
            if (msfsSdk.BitFlags.isAny(leg.flags, msfsSdk.LegDefinitionFlags.MissedApproach)) {
                width = MapFlightPathStyles.MISSED_APPROACH_STROKE_WIDTH;
                style = MapFlightPathStyles.MISSED_APPROACH_STROKE_COLOR;
            }
            else if (isTransition) {
                width = MapFlightPathStyles.TRANSITION_STROKE_WIDTH;
                style = MapFlightPathStyles.TRANSITION_STROKE_COLOR;
            }
            else {
                width = MapFlightPathStyles.STROKE_WIDTH;
                style = MapFlightPathStyles.STROKE_COLOR;
            }
            switch (leg.leg.type) {
                case msfsSdk.LegType.HF:
                case msfsSdk.LegType.HM:
                case msfsSdk.LegType.HA:
                    if (isTransition) {
                        this.lineRenderer.render(leg, context, streamStack, msfsSdk.FlightPathLegRenderPart.Base, width, style);
                    }
                    else {
                        this.holdRenderer.render(leg, context, streamStack, plan, activeLeg, legIndex, -1, undefined);
                    }
                    break;
                case msfsSdk.LegType.PI:
                    this.procTurnRenderer.render(leg, context, streamStack, plan, activeLeg, legIndex, -1);
                    break;
                case msfsSdk.LegType.CF:
                    if (msfsSdk.BitFlags.isAll(leg.flags, msfsSdk.LegDefinitionFlags.VectorsToFinalFaf)) {
                        this.vtfRenderer.render(leg, context, streamStack, width, style);
                        break;
                    }
                // eslint-disable-next-line no-fallthrough
                default:
                    if (isTransition) {
                        this.defaultTransitionRenderer.render(leg, context, streamStack, msfsSdk.FlightPathLegRenderPart.All);
                    }
                    else {
                        this.defaultRenderer.render(leg, context, streamStack, legIndex, activeLegIndex, (_a = plan.tryGetLeg(legIndex - 1)) !== null && _a !== void 0 ? _a : undefined, (_b = plan.tryGetLeg(legIndex + 1)) !== null && _b !== void 0 ? _b : undefined, false);
                    }
            }
        }
        /**
         * Selects a line style for a vector in a transition preview leg.
         * @param vector The vector for which to select a style.
         * @param isIngress Whether the vector is part of the ingress transition.
         * @param isEgress Whether the vector is part of the egress transition.
         * @param leg The flight plan leg containing the vector to render.
         * @param projection The map projection to use when rendering.
         * @param out The line style object to which to write the selected style.
         * @returns The selected line style for the vector.
         */
        selectTransitionLineStyle(vector, isIngress, isEgress, leg, projection, out) {
            if (msfsSdk.BitFlags.isAny(vector.flags, msfsSdk.FlightPathVectorFlags.Fallback)) {
                out.strokeWidth = 0;
            }
            else {
                out.strokeWidth = MapFlightPathStyles.TRANSITION_STROKE_WIDTH;
                out.strokeStyle = MapFlightPathStyles.TRANSITION_STROKE_COLOR;
                if (this.isRollHeadingVector(vector, leg)) {
                    out.strokeDash = MapFlightPathStyles.ROLL_HEADING_DASH_ARRAY;
                }
                else {
                    out.strokeDash = null;
                }
            }
            out.outlineWidth = 0;
            out.isContinuous = true;
            return out;
        }
        /**
         * Checks if a flight path vector in a transition preview leg should be styled as a roll-heading vector.
         * @param vector A flight path vector in a transition preview leg.
         * @param leg The flight plan leg to which the vector belongs.
         * @returns Whether the flight path vector should be styled as a roll-heading vector.
         */
        isRollHeadingVector(vector, leg) {
            switch (leg.leg.type) {
                case msfsSdk.LegType.CF:
                    return msfsSdk.BitFlags.isAny(vector.flags, msfsSdk.FlightPathVectorFlags.InterceptCourse | msfsSdk.FlightPathVectorFlags.Direct);
                default:
                    return false;
            }
        }
    }
    /**
     * The default procedure preview renderer for Garmin maps.
     */
    class ProcMapFlightPathPlanRenderer {
        constructor() {
            this.baseRouteRenderer = new DefaultBaseFlightPathPlanRenderer();
            this.fullRouteRenderer = new ProcMapFullFlightPathPlanRenderer();
        }
        /** @inheritdoc */
        render(plan, context, streamStack, isTransition = false) {
            this.baseRouteRenderer.render(plan, undefined, undefined, context, streamStack);
            this.fullRouteRenderer.render(plan, undefined, undefined, context, streamStack, isTransition);
        }
    }

    /**
     * Manages transition preview waypoint records.
     */
    class ProcMapTransitionWaypointRecordManager {
        /**
         * Constructor.
         * @param facLoader This manager's facility loader.
         * @param facWaypointCache This manager's facility waypoint cache.
         * @param waypointRenderer This manager's waypoint renderer.
         * @param renderRole The role(s) under which waypoints should be registered.
         */
        constructor(facLoader, facWaypointCache, waypointRenderer, renderRole) {
            this.facLoader = facLoader;
            this.facWaypointCache = facWaypointCache;
            this.waypointRenderer = waypointRenderer;
            this.renderRole = renderRole;
            this.legWaypointRecords = new Map();
            this._isBusy = false;
        }
        /**
         * Checks whether this manager is busy with a waypoint refresh.
         * @returns Whether this manager is busy with a waypoint refresh.
         */
        isBusy() {
            return this._isBusy;
        }
        // eslint-disable-next-line jsdoc/require-throws
        /**
         * Refreshes this manager's waypoint records, keeping them up to date with a specified flight plan.
         * @param flightPlan A flight plan.
         * @param repick Whether to repick waypoints.
         * @param pickPosition The position within each transition from which to pick waypoints. Ignored if `repick` is
         * false. Defaults to `first`.
         */
        async refreshWaypoints(flightPlan, repick, pickPosition) {
            if (this._isBusy) {
                throw new Error('ProcMapTransitionWaypointRecordManager: cannot refresh waypoints while busy');
            }
            this._isBusy = true;
            if (!flightPlan) {
                // Remove all waypoint records.
                for (const record of this.legWaypointRecords.values()) {
                    record.destroy();
                }
                this.legWaypointRecords.clear();
                this._isBusy = false;
                return;
            }
            if (repick) {
                const legsToDisplay = new Set();
                if ((pickPosition !== null && pickPosition !== void 0 ? pickPosition : 'first') === 'first') {
                    this.pickFirst(flightPlan, legsToDisplay);
                }
                else {
                    this.pickLast(flightPlan, legsToDisplay);
                }
                // Remove records of legs that are no longer in the set of legs to display.
                for (const record of this.legWaypointRecords.values()) {
                    if (legsToDisplay.has(record.leg)) {
                        legsToDisplay.delete(record.leg);
                    }
                    else {
                        record.destroy();
                        this.legWaypointRecords.delete(record.leg);
                    }
                }
                // Create new records for legs to display that don't already have records.
                for (const leg of legsToDisplay) {
                    const record = this.createLegWaypointsRecord(leg);
                    this.legWaypointRecords.set(leg, record);
                }
            }
            const waypointRefreshes = [];
            for (const record of this.legWaypointRecords.values()) {
                waypointRefreshes.push(record.refresh(false));
            }
            await Promise.all(waypointRefreshes);
            this._isBusy = false;
        }
        /**
         * Picks the first waypoint in each transition to display.
         * @param flightPlan The transition preview flight plan.
         * @param legsToDisplay A set of legs from which to display waypoints.
         */
        pickFirst(flightPlan, legsToDisplay) {
            let isFirst = true;
            for (const leg of flightPlan.legs()) {
                if (isFirst && !msfsSdk.BitFlags.isAny(leg.flags, msfsSdk.LegDefinitionFlags.DirectTo | msfsSdk.LegDefinitionFlags.VectorsToFinal)) {
                    legsToDisplay.add(leg);
                }
                isFirst = leg.leg.type === msfsSdk.LegType.Discontinuity;
            }
        }
        /**
         * Picks the last waypoint in each transition to display.
         * @param flightPlan The transition preview flight plan.
         * @param legsToDisplay A set of legs from which to display waypoints.
         */
        pickLast(flightPlan, legsToDisplay) {
            let lastLeg;
            for (const leg of flightPlan.legs()) {
                if (lastLeg && leg.leg.type === msfsSdk.LegType.Discontinuity && !msfsSdk.BitFlags.isAny(lastLeg.flags, msfsSdk.LegDefinitionFlags.DirectTo | msfsSdk.LegDefinitionFlags.VectorsToFinal)) {
                    legsToDisplay.add(lastLeg);
                }
                lastLeg = leg;
            }
            if (lastLeg) {
                legsToDisplay.add(lastLeg);
            }
        }
        /**
         * Creates a FlightPlanLegWaypointsRecord for a specified flight plan leg.
         * @param leg A flight plan leg.
         * @returns A FlightPlanLegWaypointsRecord for the specified flight plan leg.
         */
        createLegWaypointsRecord(leg) {
            switch (leg.leg.type) {
                case msfsSdk.LegType.CD:
                case msfsSdk.LegType.VD:
                case msfsSdk.LegType.CR:
                case msfsSdk.LegType.VR:
                case msfsSdk.LegType.FC:
                case msfsSdk.LegType.FD:
                case msfsSdk.LegType.FA:
                case msfsSdk.LegType.CA:
                case msfsSdk.LegType.VA:
                case msfsSdk.LegType.FM:
                case msfsSdk.LegType.VM:
                case msfsSdk.LegType.CI:
                case msfsSdk.LegType.VI:
                    return new FlightPathTerminatorWaypointsRecord(leg, this.waypointRenderer, this.facLoader, this.renderRole, this.renderRole);
                case msfsSdk.LegType.PI:
                    return new ProcedureTurnLegWaypointsRecord(leg, this.waypointRenderer, this.facLoader, this.facWaypointCache, this.renderRole, this.renderRole);
                default:
                    return new FixIcaoWaypointsRecord(leg, this.waypointRenderer, this.facLoader, this.facWaypointCache, this.renderRole, this.renderRole);
            }
        }
    }

    /**
     * Setting modes for map orientation.
     */
    exports.MapOrientationSettingMode = void 0;
    (function (MapOrientationSettingMode) {
        MapOrientationSettingMode["NorthUp"] = "NorthUp";
        MapOrientationSettingMode["TrackUp"] = "TrackUp";
        MapOrientationSettingMode["HeadingUp"] = "HeadingUp";
        MapOrientationSettingMode["DtkUp"] = "DtkUp";
    })(exports.MapOrientationSettingMode || (exports.MapOrientationSettingMode = {}));
    /**
     * Setting modes for map terrain display.
     */
    exports.MapTerrainSettingMode = void 0;
    (function (MapTerrainSettingMode) {
        MapTerrainSettingMode["None"] = "None";
        MapTerrainSettingMode["Absolute"] = "Absolute";
        MapTerrainSettingMode["Relative"] = "Relative";
    })(exports.MapTerrainSettingMode || (exports.MapTerrainSettingMode = {}));
    /**
     * Setting modes for map declutter.
     */
    exports.MapDeclutterSettingMode = void 0;
    (function (MapDeclutterSettingMode) {
        MapDeclutterSettingMode["All"] = "All";
        MapDeclutterSettingMode["Level3"] = "Level3";
        MapDeclutterSettingMode["Level2"] = "Level2";
        MapDeclutterSettingMode["Level1"] = "Level1";
    })(exports.MapDeclutterSettingMode || (exports.MapDeclutterSettingMode = {}));
    exports.MapTrafficAlertLevelSettingMode = void 0;
    (function (MapTrafficAlertLevelSettingMode) {
        MapTrafficAlertLevelSettingMode["All"] = "All";
        MapTrafficAlertLevelSettingMode["Advisories"] = "Advisories";
        MapTrafficAlertLevelSettingMode["TA_RA"] = "TA/RA";
        MapTrafficAlertLevelSettingMode["RA"] = "RA";
    })(exports.MapTrafficAlertLevelSettingMode || (exports.MapTrafficAlertLevelSettingMode = {}));
    /**
     * A utility class for working with map user settings.
     */
    class MapUserSettingsUtils {
    }
    /** An array of all map user setting names. */
    MapUserSettingsUtils.SETTING_NAMES = [
        'mapRangeIndex',
        'mapOrientation',
        'mapAutoNorthUpActive',
        'mapAutoNorthUpRangeIndex',
        'mapGroundNorthUpActive',
        'mapDeclutter',
        'mapTerrainMode',
        'mapTerrainRangeIndex',
        'mapTerrainScaleShow',
        'mapAirportLargeShow',
        'mapAirportLargeRangeIndex',
        'mapAirportMediumShow',
        'mapAirportMediumRangeIndex',
        'mapAirportSmallShow',
        'mapAirportSmallRangeIndex',
        'mapVorShow',
        'mapVorRangeIndex',
        'mapNdbShow',
        'mapNdbRangeIndex',
        'mapIntersectionShow',
        'mapIntersectionRangeIndex',
        'mapUserWaypointShow',
        'mapUserWaypointRangeIndex',
        'mapAirspaceClassBShow',
        'mapAirspaceClassBRangeIndex',
        'mapAirspaceClassCShow',
        'mapAirspaceClassCRangeIndex',
        'mapAirspaceClassDShow',
        'mapAirspaceClassDRangeIndex',
        'mapAirspaceRestrictedShow',
        'mapAirspaceRestrictedRangeIndex',
        'mapAirspaceMoaShow',
        'mapAirspaceMoaRangeIndex',
        'mapAirspaceOtherShow',
        'mapAirspaceOtherRangeIndex',
        'mapTrafficShow',
        'mapTrafficRangeIndex',
        'mapTrafficLabelShow',
        'mapTrafficLabelRangeIndex',
        'mapTrafficAlertLevelMode',
        'mapNexradShow',
        'mapNexradRangeIndex',
        'mapTrackVectorShow',
        'mapTrackVectorLookahead',
        'mapAltitudeArcShow',
        'mapWindVectorShow'
    ];

    /**
     * A collection of common keys used by the GarminMap API.
     */
    class GarminMapKeys {
    }
    GarminMapKeys.Units = 'units';
    GarminMapKeys.Range = 'range';
    GarminMapKeys.RangeRTR = 'rangeRTR';
    GarminMapKeys.UseRangeSetting = 'useRangeSettingModerator';
    GarminMapKeys.Orientation = 'orientation';
    GarminMapKeys.OrientationRTR = 'orientationRTR';
    GarminMapKeys.DesiredOrientation = 'desiredOrientation';
    GarminMapKeys.OrientationSettings = 'orientationSettings';
    GarminMapKeys.RotationModeControl = 'rotationModeControlModerator';
    GarminMapKeys.OrientationControl = 'orientationControlModerator';
    GarminMapKeys.DesiredOrientationControl = 'desiredOrientationControl';
    GarminMapKeys.Declutter = 'declutter';
    GarminMapKeys.Terrain = 'terrain';
    GarminMapKeys.Nexrad = 'nexrad';
    GarminMapKeys.RangeRing = 'rangeRing';
    GarminMapKeys.RangeCompass = 'rangeCompass';
    GarminMapKeys.Crosshair = 'crosshair';
    GarminMapKeys.WaypointDisplayBuilder = 'waypointDisplayBuilder';
    GarminMapKeys.WaypointsVisibility = 'waypointsVis';
    GarminMapKeys.RunwayVisibility = 'runwayVisibility';
    GarminMapKeys.RunwayLabelVisibility = 'runwayLabelVisibility';
    GarminMapKeys.WaypointHighlight = 'waypointHighlight';
    GarminMapKeys.WaypointHighlightLine = 'waypointHighlightLine';
    GarminMapKeys.WaypointHighlightRTR = 'waypointHighlightRTR';
    GarminMapKeys.FlightPlanWaypoints = 'flightPlanWaypoints';
    GarminMapKeys.FlightPlanFocus = 'flightPlanFocus';
    GarminMapKeys.AirspaceVisibility = 'airspaceVis';
    GarminMapKeys.Traffic = 'garminTraffic';
    GarminMapKeys.TrafficRange = 'trafficRange';
    GarminMapKeys.Panning = 'panning';
    GarminMapKeys.PanningRTR = 'panningRTR';
    GarminMapKeys.Pointer = 'pointer';
    GarminMapKeys.PointerRTR = 'pointerRTR';
    GarminMapKeys.PointerInfo = 'pointerInfo';
    GarminMapKeys.TrackVector = 'trackVector';
    GarminMapKeys.WindVector = 'windVector';
    GarminMapKeys.MiniCompass = 'miniCompass';
    GarminMapKeys.WaypointSelection = 'waypointSelection';
    GarminMapKeys.WaypointRTR = 'waypointRTR';
    GarminMapKeys.Nearest = 'nearest';
    GarminMapKeys.ProcedurePreview = 'procedurePreview';
    GarminMapKeys.DataIntegrityRTR = 'dataIntegrityRTR';
    GarminMapKeys.DeadReckoning = 'deadReckoning';
    GarminMapKeys.TopLeftIndicators = 'topLeftIndicators';
    GarminMapKeys.TopRightIndicators = 'topRightIndicators';
    GarminMapKeys.BottomLeftIndicators = 'bottomLeftIndicators';
    GarminMapKeys.BottomCenterIndicators = 'bottomCenterIndicators';
    GarminMapKeys.BottomRightIndicators = 'bottomRightIndicators';
    GarminMapKeys.CenterIndicators = 'centerIndicators';

    /**
     * Airspace show types for Garmin maps.
     */
    exports.AirspaceShowType = void 0;
    (function (AirspaceShowType) {
        AirspaceShowType["ClassB"] = "ClassB";
        AirspaceShowType["ClassC"] = "ClassC";
        AirspaceShowType["ClassD"] = "ClassD";
        AirspaceShowType["Restricted"] = "Restricted";
        AirspaceShowType["MOA"] = "MOA";
        AirspaceShowType["Other"] = "Other";
    })(exports.AirspaceShowType || (exports.AirspaceShowType = {}));
    /**
     * A utility class containing a map of Garmin map airspace show types to their associated boundary filters.
     */
    class GarminAirspaceShowTypeMap {
    }
    /** A map of Garmin map airspace show types to their associated boundary filters. */
    GarminAirspaceShowTypeMap.MAP = {
        [exports.AirspaceShowType.ClassB]: 1 << msfsSdk.BoundaryType.ClassB,
        [exports.AirspaceShowType.ClassC]: 1 << msfsSdk.BoundaryType.ClassC,
        [exports.AirspaceShowType.ClassD]: 1 << msfsSdk.BoundaryType.ClassD,
        [exports.AirspaceShowType.Restricted]: msfsSdk.BitFlags.union(1 << msfsSdk.BoundaryType.Restricted, 1 << msfsSdk.BoundaryType.Prohibited),
        [exports.AirspaceShowType.MOA]: 1 << msfsSdk.BoundaryType.MOA,
        [exports.AirspaceShowType.Other]: msfsSdk.BitFlags.union(1 << msfsSdk.BoundaryType.ClassE, 1 << msfsSdk.BoundaryType.Warning, 1 << msfsSdk.BoundaryType.Alert, 1 << msfsSdk.BoundaryType.Danger, 1 << msfsSdk.BoundaryType.Training)
    };

    exports.MapDeclutterMode = void 0;
    (function (MapDeclutterMode) {
        MapDeclutterMode[MapDeclutterMode["All"] = 0] = "All";
        MapDeclutterMode[MapDeclutterMode["Level3"] = 1] = "Level3";
        MapDeclutterMode[MapDeclutterMode["Level2"] = 2] = "Level2";
        MapDeclutterMode[MapDeclutterMode["Level1"] = 3] = "Level1";
    })(exports.MapDeclutterMode || (exports.MapDeclutterMode = {}));
    /**
     * A module describing the declutter mode.
     */
    class MapDeclutterModule {
        constructor() {
            this.mode = msfsSdk.Subject.create(exports.MapDeclutterMode.All);
        }
    }

    /**
     * Controls the visibility of a specific type of map symbol whose visibility is dependent on its own show and maximum
     * range index settings as well as the global map declutter setting.
     */
    class MapSymbolVisController extends msfsSdk.MapSystemController {
        /**
         * Constructor.
         * @param context This controller's map context.
         * @param showSetting A subscribable which provides the show setting of this controller's symbol.
         * @param rangeIndexSetting A subscribable which provides the maximum range index setting of this controller's
         * symbol.
         * @param maxDeclutterMode The highest global declutter mode, inclusive, at which this controller's symbol remains
         * visible.
         * @param symbolVisibility The mutable subscribable which controls the visibility of this controller's symbol.
         */
        constructor(context, showSetting, rangeIndexSetting, maxDeclutterMode, symbolVisibility) {
            super(context);
            this.showSetting = showSetting;
            this.rangeIndexSetting = rangeIndexSetting;
            this.maxDeclutterMode = maxDeclutterMode;
            this.symbolVisibility = symbolVisibility;
            this.rangeModule = this.context.model.getModule(GarminMapKeys.Range);
            this.declutterModule = this.context.model.getModule(GarminMapKeys.Declutter);
        }
        /** @inheritdoc */
        onAfterMapRender() {
            var _a, _b;
            this.isSymbolVisible = msfsSdk.MappedSubject.create(([showSetting, rangeIndexSetting, rangeIndex, declutterMode]) => {
                return showSetting && (declutterMode <= this.maxDeclutterMode) && (rangeIndex <= rangeIndexSetting);
            }, this.showSetting, this.rangeIndexSetting, this.rangeModule.nominalRangeIndex, (_b = (_a = this.declutterModule) === null || _a === void 0 ? void 0 : _a.mode) !== null && _b !== void 0 ? _b : msfsSdk.Subject.create(0));
            this.isSymbolVisiblePipe = this.isSymbolVisible.pipe(this.symbolVisibility);
        }
        /** @inheritdoc */
        onMapDestroyed() {
            this.destroy();
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b;
            super.destroy();
            (_a = this.isSymbolVisible) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.isSymbolVisiblePipe) === null || _b === void 0 ? void 0 : _b.destroy();
        }
    }

    /**
     * Controls the visibility of map airspace boundaries.
     */
    class MapAirspaceVisController extends msfsSdk.MapSystemController {
        /**
         * Constructor.
         * @param context This controller's map context.
         * @param settingManager A setting manager containing the user settings controlling airspace visibility.
         */
        constructor(context, settingManager) {
            var _a, _b, _c, _d, _e, _f;
            super(context);
            this.airspaceModule = this.context.model.getModule(msfsSdk.MapSystemKeys.Airspace);
            this.controllers = [];
            const classBShow = settingManager.tryGetSetting('mapAirspaceClassBShow');
            const classBRangeIndex = (_a = settingManager.tryGetSetting('mapAirspaceClassBRangeIndex')) !== null && _a !== void 0 ? _a : msfsSdk.Subject.create(Number.MAX_SAFE_INTEGER);
            if (classBShow) {
                this.controllers.push(new MapSymbolVisController(context, classBShow, classBRangeIndex, exports.MapDeclutterMode.Level3, this.airspaceModule.show[exports.AirspaceShowType.ClassB]));
            }
            const classCShow = settingManager.tryGetSetting('mapAirspaceClassCShow');
            const classCRangeIndex = (_b = settingManager.tryGetSetting('mapAirspaceClassCRangeIndex')) !== null && _b !== void 0 ? _b : msfsSdk.Subject.create(Number.MAX_SAFE_INTEGER);
            if (classCShow) {
                this.controllers.push(new MapSymbolVisController(context, classCShow, classCRangeIndex, exports.MapDeclutterMode.Level3, this.airspaceModule.show[exports.AirspaceShowType.ClassC]));
            }
            const classDShow = settingManager.tryGetSetting('mapAirspaceClassDShow');
            const classDRangeIndex = (_c = settingManager.tryGetSetting('mapAirspaceClassDRangeIndex')) !== null && _c !== void 0 ? _c : msfsSdk.Subject.create(Number.MAX_SAFE_INTEGER);
            if (classDShow) {
                this.controllers.push(new MapSymbolVisController(context, classDShow, classDRangeIndex, exports.MapDeclutterMode.Level3, this.airspaceModule.show[exports.AirspaceShowType.ClassD]));
            }
            const moaShow = settingManager.tryGetSetting('mapAirspaceMoaShow');
            const moaRangeIndex = (_d = settingManager.tryGetSetting('mapAirspaceMoaRangeIndex')) !== null && _d !== void 0 ? _d : msfsSdk.Subject.create(Number.MAX_SAFE_INTEGER);
            if (moaShow) {
                this.controllers.push(new MapSymbolVisController(context, moaShow, moaRangeIndex, exports.MapDeclutterMode.Level3, this.airspaceModule.show[exports.AirspaceShowType.MOA]));
            }
            const restrictedShow = settingManager.tryGetSetting('mapAirspaceRestrictedShow');
            const restrictedRangeIndex = (_e = settingManager.tryGetSetting('mapAirspaceRestrictedRangeIndex')) !== null && _e !== void 0 ? _e : msfsSdk.Subject.create(Number.MAX_SAFE_INTEGER);
            if (restrictedShow) {
                this.controllers.push(new MapSymbolVisController(context, restrictedShow, restrictedRangeIndex, exports.MapDeclutterMode.Level3, this.airspaceModule.show[exports.AirspaceShowType.Restricted]));
            }
            const otherShow = settingManager.tryGetSetting('mapAirspaceOtherShow');
            const otherRangeIndex = (_f = settingManager.tryGetSetting('mapAirspaceOtherRangeIndex')) !== null && _f !== void 0 ? _f : msfsSdk.Subject.create(Number.MAX_SAFE_INTEGER);
            if (otherShow) {
                this.controllers.push(new MapSymbolVisController(context, otherShow, otherRangeIndex, exports.MapDeclutterMode.Level3, this.airspaceModule.show[exports.AirspaceShowType.Other]));
            }
        }
        /** @inheritdoc */
        onAfterMapRender() {
            this.controllers.forEach(controller => { controller.onAfterMapRender(); });
        }
        /** @inheritdoc */
        onMapDestroyed() {
            this.destroy();
        }
        /** @inheritdoc */
        destroy() {
            super.destroy();
            this.controllers.forEach(controller => { controller.destroy(); });
        }
    }

    /**
     * Utility class defining the priority values for common resource consumers.
     */
    class MapResourcePriority {
    }
    /** Default follow player airplane behavior. */
    MapResourcePriority.FOLLOW_AIRPLANE = 0;
    /** Orientation mode. */
    MapResourcePriority.ORIENTATION = 0;
    /** Desired orientation mode. */
    MapResourcePriority.DESIRED_ORIENTATION = 0;
    /** Rotation behavior from orientation mode. */
    MapResourcePriority.ORIENTATION_ROTATION = 0;
    /** Panning. */
    MapResourcePriority.PANNING = 100;
    /** Pointer. */
    MapResourcePriority.POINTER = 100;
    /** Waypoint highlight. */
    MapResourcePriority.WAYPOINT_HIGHLIGHT = 75;
    /** Flight plan focus. */
    MapResourcePriority.FLIGHT_PLAN_FOCUS = 50;
    /** Data integrity. */
    MapResourcePriority.DATA_INTEGRITY = 25;

    /**
     * Orientation types for a map.
     */
    exports.MapOrientation = void 0;
    (function (MapOrientation) {
        MapOrientation[MapOrientation["NorthUp"] = 0] = "NorthUp";
        MapOrientation[MapOrientation["TrackUp"] = 1] = "TrackUp";
        MapOrientation[MapOrientation["HeadingUp"] = 2] = "HeadingUp";
        MapOrientation[MapOrientation["DtkUp"] = 3] = "DtkUp";
    })(exports.MapOrientation || (exports.MapOrientation = {}));
    /**
     * A module describing the map orientation.
     */
    class MapOrientationModule {
        constructor() {
            /** The actual orientation of the map. */
            this.orientation = msfsSdk.Subject.create(exports.MapOrientation.HeadingUp);
            /** The desired orientation of the map. */
            this.desiredOrientation = msfsSdk.Subject.create(exports.MapOrientation.HeadingUp);
            /** The map orientation commanded by the user. */
            this.commandedOrientation = msfsSdk.Subject.create(exports.MapOrientation.HeadingUp);
            /** Whether north up-above is active. */
            this.northUpAboveActive = msfsSdk.Subject.create(false);
            /** The range index above which north up-above applies. */
            this.northUpAboveRangeIndex = msfsSdk.Subject.create(Infinity);
            /** Whether north up on ground is active. */
            this.northUpOnGroundActive = msfsSdk.Subject.create(false);
        }
    }

    /**
     * Controls the map's projected target, orientation mode, and player airplane icon based on heading and GPS signal validity.
     */
    class MapDataIntegrityRTRController extends msfsSdk.MapSystemController {
        /**
         * Constructor.
         * @param context This controller's map context.
         * @param airplaneIconSrc A mutable subscribable which controls the player airplane icon's image source URI.
         * Required for this controller to change the player airplane icon.
         * @param airplaneIconAnchor A mutable subscribable which controls the anchor point of the player airplane icon.
         * Required for this controller to change the player airplane icon.
         * @param normalIconSrc A subscribable which provides the URI of the normal player airplane icon's image source.
         * Required for this controller to change the player airplane icon.
         * @param normalIconAnchor A subscribable which provides the anchor point of the normal player airplane icon, as
         * `[x, y]`, where each component is relative to the width or height of the icon. Required for this controller to
         * change the player airplane icon.
         * @param noHeadingIconSrc A subscribable which provides the URI of the no-heading player airplane icon's image
         * source. Required for this controller to change the player airplane icon.
         * @param noHeadingIconAnchor A subscribable which provides the anchor point of the no-heading player airplane icon,
         * as `[x, y]`, where each component is relative to the width or height of the icon. Required for this controller to
         * change the player airplane icon.
         */
        constructor(context, airplaneIconSrc, airplaneIconAnchor, normalIconSrc, normalIconAnchor, noHeadingIconSrc, noHeadingIconAnchor) {
            super(context);
            this.airplaneIconSrc = airplaneIconSrc;
            this.airplaneIconAnchor = airplaneIconAnchor;
            this.normalIconSrc = normalIconSrc;
            this.normalIconAnchor = normalIconAnchor;
            this.noHeadingIconSrc = noHeadingIconSrc;
            this.noHeadingIconAnchor = noHeadingIconAnchor;
            this.dataIntegrityModule = this.context.model.getModule(msfsSdk.MapSystemKeys.DataIntegrity);
            this.orientationModule = this.context.model.getModule(GarminMapKeys.Orientation);
            this.ownAirplaneIconModule = this.context.model.getModule(msfsSdk.MapSystemKeys.OwnAirplaneIcon);
            this.targetControl = this.context[msfsSdk.MapSystemKeys.TargetControl];
            this.targetControlConsumer = {
                priority: MapResourcePriority.DATA_INTEGRITY,
                onAcquired: () => { }, // if we have loss of GPS signal, we simply do nothing since there is nothing to follow.
                onCeded: () => { }
            };
            this.orientationControl = this.context[GarminMapKeys.OrientationControl];
            this.orientationControlConsumer = {
                priority: MapResourcePriority.DATA_INTEGRITY,
                onAcquired: () => {
                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                    this.orientationOverridePipe.resume(true);
                },
                onCeded: () => {
                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                    this.orientationOverridePipe.pause();
                }
            };
            this.canChangeAirplaneIcon = this.airplaneIconSrc !== undefined
                && this.airplaneIconAnchor !== undefined
                && this.normalIconSrc !== undefined
                && this.normalIconAnchor !== undefined
                && this.noHeadingIconSrc !== undefined
                && this.noHeadingIconAnchor !== undefined;
        }
        /** @inheritdoc */
        onAfterMapRender() {
            if (this.canChangeAirplaneIcon) {
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                this.normalIconSrcPipe = this.normalIconSrc.pipe(this.airplaneIconSrc, true);
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                this.normalIconAnchorPipe = this.normalIconAnchor.pipe(this.airplaneIconAnchor, true);
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                this.noHeadingIconSrcPipe = this.noHeadingIconSrc.pipe(this.airplaneIconSrc, true);
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                this.noHeadingIconAnchorPipe = this.noHeadingIconAnchor.pipe(this.airplaneIconAnchor, true);
            }
            if (this.orientationModule) {
                this.orientationOverride = msfsSdk.MappedSubject.create(([desiredOrientation, isHeadingValid, isGpsValid]) => {
                    if (isHeadingValid && isGpsValid) {
                        return null;
                    }
                    switch (desiredOrientation) {
                        case exports.MapOrientation.HeadingUp:
                            return isHeadingValid ? desiredOrientation : exports.MapOrientation.NorthUp;
                        case exports.MapOrientation.TrackUp:
                        case exports.MapOrientation.DtkUp:
                            return isGpsValid ? desiredOrientation : exports.MapOrientation.NorthUp;
                        default:
                            return desiredOrientation;
                    }
                }, this.orientationModule.desiredOrientation, this.dataIntegrityModule.headingSignalValid, this.dataIntegrityModule.gpsSignalValid);
                this.orientationOverridePipe = this.orientationOverride.pipe(this.orientationModule.orientation, true);
                this.orientationOverride.sub(override => {
                    if (override === null) {
                        if (this.orientationControl === undefined) {
                            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                            this.orientationOverridePipe.pause();
                        }
                        else {
                            this.orientationControl.forfeit(this.orientationControlConsumer);
                        }
                    }
                    else {
                        if (this.orientationControl === undefined) {
                            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                            this.orientationOverridePipe.resume(true);
                        }
                        else {
                            this.orientationControl.claim(this.orientationControlConsumer);
                        }
                    }
                }, true);
            }
            this.headingSignalSub = this.dataIntegrityModule.headingSignalValid.sub(isValid => {
                var _a;
                if (isValid) {
                    (_a = this.orientationControl) === null || _a === void 0 ? void 0 : _a.forfeit(this.orientationControlConsumer);
                    this.setNormalAirplaneIcon();
                }
                else {
                    this.setNoHeadingAirplaneIcon();
                }
            }, true);
            this.gpsSignalSub = this.dataIntegrityModule.gpsSignalValid.sub(isValid => {
                var _a, _b, _c, _d;
                if (isValid) {
                    (_a = this.targetControl) === null || _a === void 0 ? void 0 : _a.forfeit(this.targetControlConsumer);
                    (_b = this.ownAirplaneIconModule) === null || _b === void 0 ? void 0 : _b.show.set(true);
                }
                else {
                    (_c = this.ownAirplaneIconModule) === null || _c === void 0 ? void 0 : _c.show.set(false);
                    (_d = this.targetControl) === null || _d === void 0 ? void 0 : _d.claim(this.targetControlConsumer);
                }
            }, true);
        }
        /**
         * Changes the player airplane icon to the normal variety.
         */
        setNormalAirplaneIcon() {
            var _a, _b, _c, _d;
            (_a = this.noHeadingIconSrcPipe) === null || _a === void 0 ? void 0 : _a.pause();
            (_b = this.noHeadingIconAnchorPipe) === null || _b === void 0 ? void 0 : _b.pause();
            (_c = this.normalIconSrcPipe) === null || _c === void 0 ? void 0 : _c.resume(true);
            (_d = this.normalIconAnchorPipe) === null || _d === void 0 ? void 0 : _d.resume(true);
        }
        /**
         * Changes the player airplane icon to the no-heading variety.
         */
        setNoHeadingAirplaneIcon() {
            var _a, _b, _c, _d;
            (_a = this.normalIconSrcPipe) === null || _a === void 0 ? void 0 : _a.pause();
            (_b = this.normalIconAnchorPipe) === null || _b === void 0 ? void 0 : _b.pause();
            (_c = this.noHeadingIconSrcPipe) === null || _c === void 0 ? void 0 : _c.resume(true);
            (_d = this.noHeadingIconAnchorPipe) === null || _d === void 0 ? void 0 : _d.resume(true);
        }
        /** @inheritdoc */
        onMapDestroyed() {
            this.destroy();
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
            (_a = this.targetControl) === null || _a === void 0 ? void 0 : _a.forfeit(this.targetControlConsumer);
            (_b = this.orientationControl) === null || _b === void 0 ? void 0 : _b.forfeit(this.orientationControlConsumer);
            (_c = this.orientationOverride) === null || _c === void 0 ? void 0 : _c.destroy();
            (_d = this.orientationOverridePipe) === null || _d === void 0 ? void 0 : _d.destroy();
            (_e = this.headingSignalSub) === null || _e === void 0 ? void 0 : _e.destroy();
            (_f = this.gpsSignalSub) === null || _f === void 0 ? void 0 : _f.destroy();
            (_g = this.normalIconSrcPipe) === null || _g === void 0 ? void 0 : _g.destroy();
            (_h = this.normalIconAnchorPipe) === null || _h === void 0 ? void 0 : _h.destroy();
            (_j = this.noHeadingIconSrcPipe) === null || _j === void 0 ? void 0 : _j.destroy();
            (_k = this.noHeadingIconAnchorPipe) === null || _k === void 0 ? void 0 : _k.destroy();
            super.destroy();
        }
    }

    /**
     * Controls the desired orientation of a map based on commanded orientation and auto-north up behavior.
     */
    class MapDesiredOrientationController extends msfsSdk.MapSystemController {
        constructor() {
            super(...arguments);
            this.rangeModule = this.context.model.getModule(GarminMapKeys.Range);
            this.orientationModule = this.context.model.getModule(GarminMapKeys.Orientation);
            this.ownAirplanePropsModule = this.context.model.getModule(msfsSdk.MapSystemKeys.OwnAirplaneProps);
            this.desiredOrientationControl = this.context[GarminMapKeys.DesiredOrientationControl];
            this.desiredOrientationControlConsumer = {
                priority: MapResourcePriority.DESIRED_ORIENTATION,
                onAcquired: () => {
                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                    this.orientationPipe.resume(true);
                },
                onCeded: () => {
                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                    this.orientationPipe.pause();
                }
            };
        }
        /** @inheritdoc */
        onAfterMapRender() {
            this.orientation = msfsSdk.MappedSubject.create(([commandedOrientation, isNorthUpAboveActive, northUpAboveRangeIndex, rangeIndex, isNorthUpOnGroundActive, isOnGround]) => {
                return (isNorthUpAboveActive && rangeIndex > northUpAboveRangeIndex) || (isNorthUpOnGroundActive && isOnGround)
                    ? exports.MapOrientation.NorthUp
                    : commandedOrientation;
            }, this.orientationModule.commandedOrientation, this.orientationModule.northUpAboveActive, this.orientationModule.northUpAboveRangeIndex, this.rangeModule.nominalRangeIndex, this.orientationModule.northUpOnGroundActive, this.ownAirplanePropsModule.isOnGround);
            this.orientationPipe = this.orientation.pipe(this.orientationModule.desiredOrientation, true);
            this.desiredOrientationControl.claim(this.desiredOrientationControlConsumer);
        }
        /** @inheritdoc */
        onMapDestroyed() {
            this.destroy();
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c;
            this.desiredOrientationControl.forfeit(this.desiredOrientationControlConsumer);
            (_a = this.orientation) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.orientationPipe) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.isPointerActiveSub) === null || _c === void 0 ? void 0 : _c.destroy();
            super.destroy();
        }
    }

    /**
     * Controls the pointer of a map.
     */
    class MapFlightPlanFocusRTRController extends msfsSdk.MapSystemController {
        /**
         * Constructor.
         * @param context This controller's map context.
         * @param nominalFocusMargins A subscribable which provides the nominal focus margins, as
         * `[left, top, right, bottom]` in pixels. The nominal margins define the offset of the boundaries of the focus
         * region relative to the map's projected window, *excluding* the dead zone. Positive values represent offsets
         * toward the center of the window. When the flight plan is focused, the focused elements of the plan are guaranteed
         * to be contained within the focus region.
         * @param defaultFocusRangeIndex The default map range index to apply when the flight plan focus consists of only a
         * single point in space.
         * @param focusDebounceDelay The debounce delay, in milliseconds, to apply to focus target calculations when the
         * flight plan focus changes. Defaults to 500 milliseconds.
         */
        constructor(context, nominalFocusMargins, defaultFocusRangeIndex, focusDebounceDelay = MapFlightPlanFocusRTRController.DEFAULT_FOCUS_DEBOUNCE_DELAY) {
            super(context);
            this.nominalFocusMargins = nominalFocusMargins;
            this.defaultFocusRangeIndex = defaultFocusRangeIndex;
            this.focusDebounceDelay = focusDebounceDelay;
            this.rangeModule = this.context.model.getModule(GarminMapKeys.Range);
            this.ownAirplanePropsModule = this.context.model.getModule(msfsSdk.MapSystemKeys.OwnAirplaneProps);
            this.focusModule = this.context.model.getModule(GarminMapKeys.FlightPlanFocus);
            this.orientationModule = this.context.model.getModule(GarminMapKeys.Orientation);
            this.mapTargetParam = {
                target: new msfsSdk.GeoPoint(0, 0)
            };
            this.hasTargetControl = msfsSdk.Subject.create(this.context.targetControlModerator === undefined);
            this.hasRangeSettingControl = msfsSdk.Subject.create(this.context.useRangeSettingModerator === undefined);
            this.canActivateFocus = msfsSdk.MappedSubject.create(([hasTargetControl, hasRangeSettingControl]) => {
                return hasTargetControl && hasRangeSettingControl;
            }, this.hasTargetControl, this.hasRangeSettingControl);
            this.targetControl = this.context[msfsSdk.MapSystemKeys.TargetControl];
            this.targetControlConsumer = {
                priority: MapResourcePriority.FLIGHT_PLAN_FOCUS,
                onAcquired: () => {
                    this.hasTargetControl.set(true);
                },
                onCeded: () => {
                    this.hasTargetControl.set(false);
                }
            };
            this.desiredOrientationControl = this.context[GarminMapKeys.DesiredOrientationControl];
            this.desiredOrientationControlConsumer = {
                priority: MapResourcePriority.FLIGHT_PLAN_FOCUS,
                onAcquired: () => {
                    var _a;
                    (_a = this.orientationModule) === null || _a === void 0 ? void 0 : _a.desiredOrientation.set(exports.MapOrientation.NorthUp);
                },
                onCeded: () => { }
            };
            this.useRangeSetting = this.context[GarminMapKeys.UseRangeSetting];
            this.useRangeSettingConsumer = {
                priority: MapResourcePriority.FLIGHT_PLAN_FOCUS,
                onAcquired: (useRangeSetting) => {
                    useRangeSetting.set(false);
                    this.hasRangeSettingControl.set(true);
                },
                onCeded: () => {
                    this.hasRangeSettingControl.set(false);
                }
            };
            this.focusMargins = msfsSdk.VecNMath.create(4, 20, 20, 20, 20);
            this.isPlanFocusValid = msfsSdk.MappedSubject.create(([planHasFocus, planFocus]) => {
                return planHasFocus && planFocus !== null;
            }, this.focusModule.planHasFocus, this.focusModule.focus);
            this.isFocusActive = msfsSdk.MappedSubject.create(([isPlanFocusValid, canActivateFocus]) => {
                return isPlanFocusValid && canActivateFocus;
            }, this.isPlanFocusValid, this.canActivateFocus);
            this.focusCalculator = new MapFlightPlanFocusCalculator(this.context.projection);
            this.focusRangeTarget = { range: 0, target: new msfsSdk.GeoPoint(0, 0) };
            this.focusDebounceTimer = new msfsSdk.DebounceTimer();
            this.isRangeTargetUpdatePending = false;
            this.pendRangeTargetUpdate = () => { this.isRangeTargetUpdatePending = true; };
            this.focusMarginsSub = this.nominalFocusMargins.sub(this.updateFocusMargins.bind(this), true);
        }
        /** @inheritdoc */
        onDeadZoneChanged() {
            this.updateFocusMargins();
        }
        /**
         * Updates the flight plan focus margins.
         */
        updateFocusMargins() {
            const deadZone = this.context.deadZone.get();
            const nominalMargins = this.nominalFocusMargins.get();
            this.focusMargins[0] = deadZone[0] + nominalMargins[0];
            this.focusMargins[1] = deadZone[1] + nominalMargins[1];
            this.focusMargins[2] = deadZone[2] + nominalMargins[2];
            this.focusMargins[3] = deadZone[3] + nominalMargins[3];
        }
        /** @inheritdoc */
        onAfterMapRender() {
            this.rangeArraySub = this.rangeModule.nominalRanges.sub(this.onFlightPlanFocusChanged.bind(this), false, true);
            this.focusSub = this.focusModule.focus.sub(this.onFlightPlanFocusChanged.bind(this), false, true);
            this.dataProviderSub = this.focusModule.dataProvider.sub(this.onDataProviderChanged.bind(this), true);
            this.isFocusActivePipe = this.isFocusActive.pipe(this.focusModule.isActive);
            this.isFocusActiveSub = this.focusModule.isActive.sub(this.onIsFocusActiveChanged.bind(this), true);
            this.isPlanFocusValid.sub(this.onIsPlanFocusValidChanged.bind(this), true);
        }
        /**
         * Responds to changes in whether the current flight plan focus is valid.
         * @param isValid Whether the current flight plan focus is valid.
         */
        onIsPlanFocusValidChanged(isValid) {
            var _a, _b, _c, _d;
            if (isValid) {
                (_a = this.useRangeSetting) === null || _a === void 0 ? void 0 : _a.claim(this.useRangeSettingConsumer);
                (_b = this.targetControl) === null || _b === void 0 ? void 0 : _b.claim(this.targetControlConsumer);
            }
            else {
                (_c = this.targetControl) === null || _c === void 0 ? void 0 : _c.forfeit(this.targetControlConsumer);
                (_d = this.useRangeSetting) === null || _d === void 0 ? void 0 : _d.forfeit(this.useRangeSettingConsumer);
            }
        }
        /**
         * Responds to changes in whether flight plan focus is active.
         * @param isActive Whether flight plan focus is active.
         */
        onIsFocusActiveChanged(isActive) {
            var _a, _b;
            if (isActive) {
                if (this.desiredOrientationControl === undefined) {
                    // If there is no moderator, assume we have control
                    (_a = this.orientationModule) === null || _a === void 0 ? void 0 : _a.desiredOrientation.set(exports.MapOrientation.NorthUp);
                }
                else {
                    this.desiredOrientationControl.claim(this.desiredOrientationControlConsumer);
                }
            }
            else {
                this.focusDebounceTimer.clear();
                (_b = this.desiredOrientationControl) === null || _b === void 0 ? void 0 : _b.forfeit(this.desiredOrientationControlConsumer);
            }
            this.setFlightPlanFocusListenersActive(isActive);
        }
        /**
         * Activates or deactivates flight plan focus listeners.
         * @param isActive Whether to activate flight plan focus listeners.
         */
        setFlightPlanFocusListenersActive(isActive) {
            var _a, _b;
            if (isActive) {
                this.pendRangeTargetUpdate();
                this.focusSub.resume();
                (_a = this.planCalculatedSub) === null || _a === void 0 ? void 0 : _a.resume();
                this.rangeArraySub.resume();
            }
            else {
                this.focusSub.pause();
                (_b = this.planCalculatedSub) === null || _b === void 0 ? void 0 : _b.pause();
                this.rangeArraySub.pause();
            }
        }
        /**
         * Responds to changes in the flight plan focus.
         */
        onFlightPlanFocusChanged() {
            this.schedulePendRangeTargetUpdate();
        }
        /**
         * Responds to changes in the flight plan focus data provider.
         * @param dataProvider The new flight plan focus data provider.
         */
        onDataProviderChanged(dataProvider) {
            var _a;
            (_a = this.planCalculatedSub) === null || _a === void 0 ? void 0 : _a.destroy();
            this.planCalculatedSub = dataProvider === null || dataProvider === void 0 ? void 0 : dataProvider.planCalculated.on(this.onFlightPlanCalculated.bind(this), !this.focusModule.isActive.get());
        }
        /**
         * A callback which is called when the flight plan is calculated.
         */
        onFlightPlanCalculated() {
            // only update from flight plan focus if the focus is not null and a valid range and target do not already exist.
            if (this.focusModule.planHasFocus.get() && this.focusModule.focus.get() !== null && isNaN(this.focusRangeTarget.range)) {
                this.pendRangeTargetUpdate();
            }
        }
        /**
         * Schedules an update of the map target and range from the current flight plan focus after a debounce delay.
         */
        schedulePendRangeTargetUpdate() {
            this.focusDebounceTimer.schedule(this.pendRangeTargetUpdate, this.focusDebounceDelay);
        }
        /**
         * Updates the map target and range from the current flight plan focus.
         */
        updateRangeTargetFromFocus() {
            const targetRange = this.focusCalculator.calculateRangeTarget(this.focusModule.focus.get(), this.focusMargins, this.ownAirplanePropsModule.position.get(), this.focusRangeTarget);
            if (isNaN(targetRange.range)) {
                return;
            }
            this.mapTargetParam.target.set(targetRange.target);
            this.context.projection.setQueued(this.mapTargetParam);
            const ranges = this.rangeModule.nominalRanges.get();
            const rangeIndex = targetRange.range > 0
                ? ranges.findIndex(range => range.asUnit(msfsSdk.UnitType.GA_RADIAN) >= targetRange.range)
                : this.defaultFocusRangeIndex;
            const rangeIndexToSet = rangeIndex < 0 ? ranges.length - 1 : rangeIndex;
            this.context.getController(GarminMapKeys.Range).setRangeIndex(rangeIndexToSet);
        }
        /** @inheritdoc */
        onMapProjectionChanged(mapProjection, changeFlags) {
            if (msfsSdk.BitFlags.isAny(changeFlags, msfsSdk.MapProjectionChangeType.ProjectedSize | msfsSdk.MapProjectionChangeType.RangeEndpoints)) {
                this.pendRangeTargetUpdate();
            }
        }
        /** @inheritdoc */
        onBeforeUpdated() {
            if (!this.isRangeTargetUpdatePending) {
                return;
            }
            this.updateRangeTargetFromFocus();
            this.isRangeTargetUpdatePending = false;
        }
        /** @inheritdoc */
        onMapDestroyed() {
            this.destroy();
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c, _d, _e, _f, _g, _h, _j;
            super.destroy();
            (_a = this.targetControl) === null || _a === void 0 ? void 0 : _a.forfeit(this.targetControlConsumer);
            (_b = this.desiredOrientationControl) === null || _b === void 0 ? void 0 : _b.forfeit(this.desiredOrientationControlConsumer);
            (_c = this.useRangeSetting) === null || _c === void 0 ? void 0 : _c.forfeit(this.useRangeSettingConsumer);
            this.isPlanFocusValid.destroy();
            (_d = this.focusSub) === null || _d === void 0 ? void 0 : _d.destroy();
            (_e = this.dataProviderSub) === null || _e === void 0 ? void 0 : _e.destroy();
            (_f = this.planCalculatedSub) === null || _f === void 0 ? void 0 : _f.destroy();
            (_g = this.rangeArraySub) === null || _g === void 0 ? void 0 : _g.destroy();
            (_h = this.isFocusActivePipe) === null || _h === void 0 ? void 0 : _h.destroy();
            (_j = this.isFocusActiveSub) === null || _j === void 0 ? void 0 : _j.destroy();
            this.focusMarginsSub.destroy();
        }
    }
    MapFlightPlanFocusRTRController.DEFAULT_FOCUS_DEBOUNCE_DELAY = 500; // ms

    /**
     * Binds the properties in a {@link MapGarminAutopilotPropsModule} to event bus topics.
     */
    class MapGarminAutopilotPropsController extends msfsSdk.MapSystemController {
        /**
         * Constructor.
         * @param context This controller's map context.
         * @param properties The properties to update on the module.
         * @param updateFreq The update frequency, in hertz. If not defined, the properties will be updated every frame.
         */
        constructor(context, properties, updateFreq) {
            super(context);
            this.properties = properties;
            this.module = this.context.model.getModule(msfsSdk.MapSystemKeys.AutopilotProps);
            this.subs = [];
            this.updateFreq = updateFreq === undefined ? undefined : msfsSdk.SubscribableUtils.toSubscribable(updateFreq, true);
        }
        /** @inheritdoc */
        onAfterMapRender() {
            const sub = this.context.bus.getSubscriber();
            if (this.updateFreq) {
                this.updateFreqSub = this.updateFreq.sub(freq => {
                    for (const subscription of this.subs) {
                        subscription.destroy();
                    }
                    this.subs.length = 0;
                    for (const property of this.properties) {
                        this.subs.push(this.bindProperty(sub, property, freq));
                    }
                }, true);
            }
            else {
                for (const property of this.properties) {
                    this.subs.push(this.bindProperty(sub, property));
                }
            }
        }
        /**
         * Binds a module property to data received through the event bus.
         * @param sub The event bus subscriber.
         * @param property The property to bind.
         * @param updateFreq The data update frequency.
         * @returns The subscription created by the binding.
         * @throws Error if the property is invalid.
         */
        bindProperty(sub, property, updateFreq) {
            let key;
            let topic = undefined;
            if (typeof property === 'string') {
                key = property;
            }
            else {
                key = property.key;
                topic = property.topic;
            }
            switch (key) {
                case 'selectedAltitude':
                    topic !== null && topic !== void 0 ? topic : (topic = 'ap_altitude_selected');
                    return (updateFreq === undefined ? sub.on(topic) : sub.on(topic).atFrequency(updateFreq))
                        .handle(alt => { this.module.selectedAltitude.set(alt, msfsSdk.UnitType.FOOT); });
                case 'selectedHeading':
                    topic !== null && topic !== void 0 ? topic : (topic = 'ap_heading_selected');
                    return (updateFreq === undefined ? sub.on(topic) : sub.on(topic).atFrequency(updateFreq))
                        .handle(hdg => { this.module.selectedHeading.set(hdg); });
                case 'isTurnHdgAdjustActive':
                    topic !== null && topic !== void 0 ? topic : (topic = 'hdg_sync_turn_adjust_active');
                    return (updateFreq === undefined ? sub.on(topic) : sub.on(topic).atFrequency(updateFreq))
                        .handle(active => { this.module.isTurnHdgAdjustActive.set(active); });
                case 'isHdgSyncModeActive':
                    topic !== null && topic !== void 0 ? topic : (topic = 'hdg_sync_mode_active');
                    return (updateFreq === undefined ? sub.on(topic) : sub.on(topic).atFrequency(updateFreq))
                        .handle(active => { this.module.isHdgSyncModeActive.set(active); });
                case 'manualHeadingSelect':
                    topic !== null && topic !== void 0 ? topic : (topic = 'hdg_sync_manual_select');
                    return sub.on(topic).handle(() => { this.module.manualHeadingSelect.notify(); });
                default:
                    throw new Error(`MapGarminAutopilotPropsController: invalid property key: ${key}`);
            }
        }
        /** @inheritdoc */
        onMapDestroyed() {
            this.destroy();
        }
        /** @inheritdoc */
        destroy() {
            var _a;
            (_a = this.updateFreqSub) === null || _a === void 0 ? void 0 : _a.destroy();
            for (const sub of this.subs) {
                sub.destroy();
            }
            super.destroy();
        }
    }

    exports.TrafficOperatingModeSetting = void 0;
    (function (TrafficOperatingModeSetting) {
        TrafficOperatingModeSetting["Standby"] = "Standby";
        TrafficOperatingModeSetting["Operating"] = "Operating";
        TrafficOperatingModeSetting["Auto"] = "Auto";
        TrafficOperatingModeSetting["TAOnly"] = "TAOnly";
        TrafficOperatingModeSetting["Test"] = "Test";
    })(exports.TrafficOperatingModeSetting || (exports.TrafficOperatingModeSetting = {}));
    exports.TrafficAltitudeModeSetting = void 0;
    (function (TrafficAltitudeModeSetting) {
        TrafficAltitudeModeSetting["Below"] = "Below";
        TrafficAltitudeModeSetting["Normal"] = "Normal";
        TrafficAltitudeModeSetting["Above"] = "Above";
        TrafficAltitudeModeSetting["Unrestricted"] = "Unrestricted";
    })(exports.TrafficAltitudeModeSetting || (exports.TrafficAltitudeModeSetting = {}));
    exports.TrafficMotionVectorModeSetting = void 0;
    (function (TrafficMotionVectorModeSetting) {
        TrafficMotionVectorModeSetting["Off"] = "Off";
        TrafficMotionVectorModeSetting["Absolute"] = "Absolute";
        TrafficMotionVectorModeSetting["Relative"] = "Relative";
    })(exports.TrafficMotionVectorModeSetting || (exports.TrafficMotionVectorModeSetting = {}));
    /**
     * Utility class for retrieving traffic user setting managers.
     */
    class TrafficUserSettings {
        /**
         * Gets an instance of the traffic user settings manager.
         * @param bus The event bus.
         * @returns An instance of the traffic user settings manager.
         */
        static getManager(bus) {
            var _a;
            return (_a = TrafficUserSettings.INSTANCE) !== null && _a !== void 0 ? _a : (TrafficUserSettings.INSTANCE = new msfsSdk.DefaultUserSettingManager(bus, [
                {
                    name: 'trafficOperatingMode',
                    defaultValue: exports.TrafficOperatingModeSetting.Standby
                },
                {
                    name: 'trafficAdsbEnabled',
                    defaultValue: true
                },
                {
                    name: 'trafficAltitudeMode',
                    defaultValue: exports.TrafficAltitudeModeSetting.Unrestricted
                },
                {
                    name: 'trafficAltitudeRelative',
                    defaultValue: true
                },
                {
                    name: 'trafficMotionVectorMode',
                    defaultValue: exports.TrafficMotionVectorModeSetting.Off
                },
                {
                    name: 'trafficMotionVectorLookahead',
                    defaultValue: 60
                }
            ]));
        }
    }

    /**
     * Traffic alert level modes.
     */
    exports.MapTrafficAlertLevelMode = void 0;
    (function (MapTrafficAlertLevelMode) {
        MapTrafficAlertLevelMode[MapTrafficAlertLevelMode["All"] = 0] = "All";
        MapTrafficAlertLevelMode[MapTrafficAlertLevelMode["Advisories"] = 1] = "Advisories";
        MapTrafficAlertLevelMode[MapTrafficAlertLevelMode["TA_RA"] = 2] = "TA_RA";
        MapTrafficAlertLevelMode[MapTrafficAlertLevelMode["RA"] = 3] = "RA";
    })(exports.MapTrafficAlertLevelMode || (exports.MapTrafficAlertLevelMode = {}));
    /**
     * Traffic motion vector modes.
     */
    exports.MapTrafficMotionVectorMode = void 0;
    (function (MapTrafficMotionVectorMode) {
        MapTrafficMotionVectorMode[MapTrafficMotionVectorMode["Off"] = 0] = "Off";
        MapTrafficMotionVectorMode[MapTrafficMotionVectorMode["Absolute"] = 1] = "Absolute";
        MapTrafficMotionVectorMode[MapTrafficMotionVectorMode["Relative"] = 2] = "Relative";
    })(exports.MapTrafficMotionVectorMode || (exports.MapTrafficMotionVectorMode = {}));
    /**
     * Traffic display altitude restriction modes.
     */
    exports.MapTrafficAltitudeRestrictionMode = void 0;
    (function (MapTrafficAltitudeRestrictionMode) {
        MapTrafficAltitudeRestrictionMode[MapTrafficAltitudeRestrictionMode["Unrestricted"] = 0] = "Unrestricted";
        MapTrafficAltitudeRestrictionMode[MapTrafficAltitudeRestrictionMode["Above"] = 1] = "Above";
        MapTrafficAltitudeRestrictionMode[MapTrafficAltitudeRestrictionMode["Normal"] = 2] = "Normal";
        MapTrafficAltitudeRestrictionMode[MapTrafficAltitudeRestrictionMode["Below"] = 3] = "Below";
    })(exports.MapTrafficAltitudeRestrictionMode || (exports.MapTrafficAltitudeRestrictionMode = {}));
    /**
     * A module describing the display of traffic.
     */
    class MapGarminTrafficModule {
        /**
         * Constructor.
         * @param trafficSystem This module's associated traffic system.
         */
        constructor(trafficSystem) {
            var _a;
            this.trafficSystem = trafficSystem;
            /** Whether to show traffic information. */
            this.show = msfsSdk.Subject.create(true);
            /** The TCAS operating mode. */
            this.operatingMode = msfsSdk.Subject.create(msfsSdk.TcasOperatingMode.Standby);
            /** The ADS-B operating mode. */
            this.adsbOperatingMode = msfsSdk.Subject.create(msfsSdk.AdsbOperatingMode.Standby);
            /** Whether to show intruder labels. */
            this.showIntruderLabel = msfsSdk.Subject.create(true);
            /** The index of the outer ring range. */
            this.outerRangeIndex = msfsSdk.Subject.create(0);
            /** The index of the inner ring range. */
            this.innerRangeIndex = msfsSdk.Subject.create(0);
            /** The alert level mode. */
            this.alertLevelMode = msfsSdk.Subject.create(exports.MapTrafficAlertLevelMode.All);
            /** The altitude restriction mode. */
            this.altitudeRestrictionMode = msfsSdk.Subject.create(exports.MapTrafficAltitudeRestrictionMode.Normal);
            /** Whether displayed intruder altitude is relative. */
            this.isAltitudeRelative = msfsSdk.Subject.create(true);
            /** The motion vector mode. */
            this.motionVectorMode = msfsSdk.Subject.create(exports.MapTrafficMotionVectorMode.Off);
            /** The motion vector mode. */
            this.motionVectorLookahead = msfsSdk.NumberUnitSubject.createFromNumberUnit(msfsSdk.UnitType.SECOND.createNumber(60));
            trafficSystem.getEventSubscriber().on('tcas_operating_mode').whenChanged().handle(mode => {
                this.operatingMode.set(mode);
            });
            (_a = trafficSystem.adsb) === null || _a === void 0 ? void 0 : _a.getEventSubscriber().on('adsb_operating_mode').whenChanged().handle(mode => {
                this.adsbOperatingMode.set(mode);
            });
        }
    }

    /**
     * Controls the display of traffic on a map based on user settings.
     */
    class MapGarminTrafficController extends msfsSdk.MapSystemController {
        /**
         * Constructor.
         * @param context This controller's map context.
         * @param trafficSettingManager A user settings manager containing traffic settings.
         * @param mapSettingManager A user settings manager containing map traffic settings. If not defined, the display of
         * traffic will not be bound to map traffic user settings.
         */
        constructor(context, trafficSettingManager, mapSettingManager) {
            var _a, _b, _c, _d;
            super(context);
            this.garminTrafficModule = this.context.model.getModule(GarminMapKeys.Traffic);
            this.altitudeModeSetting = (_a = trafficSettingManager.tryGetSetting('trafficAltitudeMode')) !== null && _a !== void 0 ? _a : msfsSdk.Subject.create(exports.TrafficAltitudeModeSetting.Normal);
            this.altitudeRelativeSetting = (_b = trafficSettingManager.tryGetSetting('trafficAltitudeRelative')) !== null && _b !== void 0 ? _b : msfsSdk.Subject.create(true);
            this.motionVectorModeSetting = (_c = trafficSettingManager.tryGetSetting('trafficMotionVectorMode')) !== null && _c !== void 0 ? _c : msfsSdk.Subject.create(exports.TrafficMotionVectorModeSetting.Off);
            this.motionVectorLookaheadSetting = (_d = trafficSettingManager.tryGetSetting('trafficMotionVectorLookahead')) !== null && _d !== void 0 ? _d : msfsSdk.Subject.create(60);
            if (mapSettingManager !== undefined) {
                const iconShowSetting = mapSettingManager.tryGetSetting('mapTrafficShow');
                const iconRangeIndexSetting = mapSettingManager.tryGetSetting('mapTrafficRangeIndex');
                if (iconShowSetting !== undefined) {
                    this.iconVisController = new MapSymbolVisController(context, iconShowSetting, iconRangeIndexSetting !== null && iconRangeIndexSetting !== void 0 ? iconRangeIndexSetting : msfsSdk.Subject.create(Number.MAX_SAFE_INTEGER), exports.MapDeclutterMode.Level1, this.garminTrafficModule.show);
                }
                const labelShowSetting = mapSettingManager.tryGetSetting('mapTrafficLabelShow');
                const labelRangeIndexSetting = mapSettingManager.tryGetSetting('mapTrafficLabelRangeIndex');
                if (labelShowSetting !== undefined) {
                    this.labelVisController = new MapSymbolVisController(context, labelShowSetting, labelRangeIndexSetting !== null && labelRangeIndexSetting !== void 0 ? labelRangeIndexSetting : msfsSdk.Subject.create(Number.MAX_SAFE_INTEGER), exports.MapDeclutterMode.Level1, this.garminTrafficModule.showIntruderLabel);
                }
                this.alertLevelModeSetting = mapSettingManager.tryGetSetting('mapTrafficAlertLevelMode');
            }
        }
        /** @inheritdoc */
        onAfterMapRender() {
            var _a, _b, _c;
            this.altitudeModeSettingPipe = this.altitudeModeSetting.pipe(this.garminTrafficModule.altitudeRestrictionMode, setting => { var _a; return (_a = MapGarminTrafficController.ALT_MODE_MAP[setting]) !== null && _a !== void 0 ? _a : exports.MapTrafficAltitudeRestrictionMode.Unrestricted; });
            this.altitudeRelativeSettingPipe = this.altitudeRelativeSetting.pipe(this.garminTrafficModule.isAltitudeRelative);
            this.motionVectorModeSettingPipe = this.motionVectorModeSetting.pipe(this.garminTrafficModule.motionVectorMode, setting => { var _a; return (_a = MapGarminTrafficController.MOTION_VECTOR_MODE_MAP[setting]) !== null && _a !== void 0 ? _a : exports.MapTrafficMotionVectorMode.Off; });
            this.motionVectorLookaheadSettingSub = this.motionVectorLookaheadSetting.sub(setting => {
                this.garminTrafficModule.motionVectorLookahead.set(setting, msfsSdk.UnitType.SECOND);
            });
            (_a = this.iconVisController) === null || _a === void 0 ? void 0 : _a.onAfterMapRender();
            (_b = this.labelVisController) === null || _b === void 0 ? void 0 : _b.onAfterMapRender();
            this.alertLevelModePipe = (_c = this.alertLevelModeSetting) === null || _c === void 0 ? void 0 : _c.pipe(this.garminTrafficModule.alertLevelMode, setting => { var _a; return (_a = MapGarminTrafficController.ALERT_LEVEL_MODE_MAP[setting]) !== null && _a !== void 0 ? _a : exports.MapTrafficAlertLevelMode.All; });
        }
        /** @inheritdoc */
        onMapDestroyed() {
            this.destroy();
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c, _d, _e, _f, _g;
            super.destroy();
            (_a = this.altitudeModeSettingPipe) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.altitudeRelativeSettingPipe) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.motionVectorModeSettingPipe) === null || _c === void 0 ? void 0 : _c.destroy();
            (_d = this.motionVectorLookaheadSettingSub) === null || _d === void 0 ? void 0 : _d.destroy();
            (_e = this.iconVisController) === null || _e === void 0 ? void 0 : _e.destroy();
            (_f = this.labelVisController) === null || _f === void 0 ? void 0 : _f.destroy();
            (_g = this.alertLevelModePipe) === null || _g === void 0 ? void 0 : _g.destroy();
        }
    }
    MapGarminTrafficController.ALT_MODE_MAP = {
        [exports.TrafficAltitudeModeSetting.Above]: exports.MapTrafficAltitudeRestrictionMode.Above,
        [exports.TrafficAltitudeModeSetting.Below]: exports.MapTrafficAltitudeRestrictionMode.Below,
        [exports.TrafficAltitudeModeSetting.Normal]: exports.MapTrafficAltitudeRestrictionMode.Normal,
        [exports.TrafficAltitudeModeSetting.Unrestricted]: exports.MapTrafficAltitudeRestrictionMode.Unrestricted
    };
    MapGarminTrafficController.MOTION_VECTOR_MODE_MAP = {
        [exports.TrafficMotionVectorModeSetting.Off]: exports.MapTrafficMotionVectorMode.Off,
        [exports.TrafficMotionVectorModeSetting.Absolute]: exports.MapTrafficMotionVectorMode.Absolute,
        [exports.TrafficMotionVectorModeSetting.Relative]: exports.MapTrafficMotionVectorMode.Relative
    };
    MapGarminTrafficController.ALERT_LEVEL_MODE_MAP = {
        [exports.MapTrafficAlertLevelSettingMode.All]: exports.MapTrafficAlertLevelMode.All,
        [exports.MapTrafficAlertLevelSettingMode.Advisories]: exports.MapTrafficAlertLevelMode.Advisories,
        [exports.MapTrafficAlertLevelSettingMode.TA_RA]: exports.MapTrafficAlertLevelMode.TA_RA,
        [exports.MapTrafficAlertLevelSettingMode.RA]: exports.MapTrafficAlertLevelMode.RA
    };

    /**
     * Controls the display of NEXRAD based on user settings.
     */
    class MapNexradController extends msfsSdk.MapSystemController {
        /**
         * Creates a new instance of MapNexradController.
         * @param context This controller's map context.
         * @param minRangeIndex The minimum range range index, inclusive, at which NEXRAD is visible.
         * @param settingManager A setting manager containing the user settings controlling the display of NEXRAD. If not
         * defined, the display of NEXRAD will not be bound to user settings.
         * @param maxDeclutterMode The highest global declutter mode, inclusive, at which NEXRAD is visible. Defaults to
         * `MapDeclutterMode.Level2`. Ignored if `settingManager` is not defined.
         */
        constructor(context, minRangeIndex, settingManager, maxDeclutterMode = exports.MapDeclutterMode.Level2) {
            var _a, _b, _c;
            super(context);
            this.minRangeIndex = minRangeIndex;
            this.maxDeclutterMode = maxDeclutterMode;
            this.nexradModule = this.context.model.getModule(GarminMapKeys.Nexrad);
            this.rangeIndex = this.context.model.getModule(GarminMapKeys.Range).nominalRangeIndex;
            this.declutterMode = (_b = (_a = this.context.model.getModule(GarminMapKeys.Declutter)) === null || _a === void 0 ? void 0 : _a.mode) !== null && _b !== void 0 ? _b : msfsSdk.Subject.create(exports.MapDeclutterMode.All);
            this.showSetting = settingManager === null || settingManager === void 0 ? void 0 : settingManager.tryGetSetting('mapNexradShow');
            this.rangeIndexSetting = (_c = settingManager === null || settingManager === void 0 ? void 0 : settingManager.tryGetSetting('mapNexradRangeIndex')) !== null && _c !== void 0 ? _c : msfsSdk.Subject.create(Number.MAX_SAFE_INTEGER);
        }
        /** @inheritdoc */
        onAfterMapRender() {
            if (this.showSetting) {
                this.show = msfsSdk.MappedSubject.create(([showSetting, rangeIndexSetting, declutterMode, rangeIndex]) => {
                    return showSetting && declutterMode <= this.maxDeclutterMode && rangeIndex >= this.minRangeIndex && rangeIndex <= rangeIndexSetting;
                }, this.showSetting, this.rangeIndexSetting, this.declutterMode, this.rangeIndex);
                this.show.pipe(this.nexradModule.showNexrad);
            }
        }
        /** @inheritdoc */
        onMapDestroyed() {
            this.destroy();
        }
        /** @inheritdoc */
        destroy() {
            var _a;
            super.destroy();
            (_a = this.show) === null || _a === void 0 ? void 0 : _a.destroy();
        }
    }

    /**
     * Controls the orientation of a map based on user settings.
     * @deprecated New, preferred logic for controlling map orientation based on user settings is available using
     * `MapOrientationSettingsController`.
     */
    class MapOrientationController extends msfsSdk.MapSystemController {
        /**
         * Constructor.
         * @param context This controller's map context.
         * @param settingManager The setting manager used by this controller.
         */
        constructor(context, settingManager) {
            super(context);
            this.settingManager = settingManager;
            this.rangeModule = this.context.model.getModule(GarminMapKeys.Range);
            this.orientationModule = this.context.model.getModule(GarminMapKeys.Orientation);
            this.pointerModule = this.context.model.getModule(GarminMapKeys.Pointer);
            this.orientationControl = this.context[GarminMapKeys.OrientationControl];
            this.orientationControlConsumer = {
                priority: MapResourcePriority.ORIENTATION,
                onAcquired: () => {
                    var _a;
                    // If pointer is active, preserve the initial orientation mode when we regain control, but pass through any
                    // further changes in the orientation setting to the model. This ensures that if some other controller was
                    // manually setting the orientation mode but forfeited control, we don't force a change in orientation as control
                    // passes back to us
                    (_a = this.orientationSub) === null || _a === void 0 ? void 0 : _a.resume(this.pointerModule === undefined || !this.pointerModule.isActive.get());
                },
                onCeded: () => {
                    var _a;
                    (_a = this.orientationSub) === null || _a === void 0 ? void 0 : _a.pause();
                }
            };
        }
        /** @inheritdoc */
        onAfterMapRender() {
            var _a, _b, _c;
            const orientationSetting = this.settingManager.tryGetSetting('mapOrientation');
            if (orientationSetting !== undefined) {
                this.orientation = msfsSdk.MappedSubject.create(([orientation, isAutoNorthUpActive, autoNorthUpRangeIndex, rangeIndex]) => {
                    var _a;
                    return isAutoNorthUpActive && rangeIndex > autoNorthUpRangeIndex
                        ? exports.MapOrientation.NorthUp
                        : (_a = MapOrientationController.MODE_MAP[orientation]) !== null && _a !== void 0 ? _a : exports.MapOrientation.NorthUp;
                }, orientationSetting, (_a = this.settingManager.tryGetSetting('mapAutoNorthUpActive')) !== null && _a !== void 0 ? _a : msfsSdk.Subject.create(false), (_b = this.settingManager.tryGetSetting('mapAutoNorthUpRangeIndex')) !== null && _b !== void 0 ? _b : msfsSdk.Subject.create(0), this.rangeModule.nominalRangeIndex);
                const orientationSub = this.orientationSub = this.orientation.sub(orientation => { this.orientationModule.orientation.set(orientation); }, false, true);
                this.isPointerActiveSub = (_c = this.pointerModule) === null || _c === void 0 ? void 0 : _c.isActive.sub(isActive => {
                    if (!isActive && !orientationSub.isPaused) {
                        // If pointer deactivates while we have control, sync orientation with setting in case we inhibited
                        // the sync when control passed to us.
                        orientationSub.pause();
                        orientationSub.resume(true);
                    }
                });
                this.orientationControl.claim(this.orientationControlConsumer);
            }
        }
        /** @inheritdoc */
        onMapDestroyed() {
            this.destroy();
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c;
            super.destroy();
            this.orientationControl.forfeit(this.orientationControlConsumer);
            (_a = this.orientation) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.orientationSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.isPointerActiveSub) === null || _c === void 0 ? void 0 : _c.destroy();
        }
    }
    MapOrientationController.MODE_MAP = {
        [exports.MapOrientationSettingMode.NorthUp]: exports.MapOrientation.NorthUp,
        [exports.MapOrientationSettingMode.HeadingUp]: exports.MapOrientation.HeadingUp,
        [exports.MapOrientationSettingMode.TrackUp]: exports.MapOrientation.TrackUp,
        [exports.MapOrientationSettingMode.DtkUp]: exports.MapOrientation.DtkUp
    };

    /**
     * Controls the orientation of a map based on the desired orientation mode.
     */
    class MapOrientationModeController extends msfsSdk.MapSystemController {
        constructor() {
            super(...arguments);
            this.orientationModule = this.context.model.getModule(GarminMapKeys.Orientation);
            this.orientationControl = this.context[GarminMapKeys.OrientationControl];
            this.orientationControlConsumer = {
                priority: MapResourcePriority.ORIENTATION,
                onAcquired: () => {
                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                    this.orientationPipe.resume(true);
                },
                onCeded: () => {
                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                    this.orientationPipe.pause();
                }
            };
        }
        /** @inheritdoc */
        onAfterMapRender() {
            this.orientationPipe = this.orientationModule.desiredOrientation.pipe(this.orientationModule.orientation, true);
            this.orientationControl.claim(this.orientationControlConsumer);
        }
        /** @inheritdoc */
        onMapDestroyed() {
            this.destroy();
        }
        /** @inheritdoc */
        destroy() {
            var _a;
            this.orientationControl.forfeit(this.orientationControlConsumer);
            (_a = this.orientationPipe) === null || _a === void 0 ? void 0 : _a.destroy();
            super.destroy();
        }
    }

    /**
     * Controls the rotation, range, and projected target offset of a map based on the orientation module's orientation
     * value.
     */
    class MapOrientationRTRController extends msfsSdk.MapSystemController {
        /**
         * Constructor.
         * @param context This controller's map context.
         * @param nominalTargetOffsets The nominal projected target offsets this controller applies for each orientation, as
         * `[x, y]` relative to the width and height of the map's projected window excluding the dead zone. If an orientation
         * does not have a defined offset, it will default to `[0, 0]`.
         * @param nominalRangeEndpoints The nominal range endpoints this controller applies for each orientation, as
         * `[x1, y1, x2, y2]` relative to the width and height of the map's projected window excluding the dead zone. If an
         * orientation does not have defined range endpoints, it will default to `[0.5, 0.5, 0.5, 0]`.
         */
        constructor(context, nominalTargetOffsets, nominalRangeEndpoints) {
            super(context);
            this.nominalTargetOffsets = nominalTargetOffsets;
            this.nominalRangeEndpoints = nominalRangeEndpoints;
            this.orientationModule = this.context.model.getModule(GarminMapKeys.Orientation);
            this.rotationModule = this.context.model.getModule(msfsSdk.MapSystemKeys.Rotation);
            this.rotationModeControl = this.context[GarminMapKeys.RotationModeControl];
            this.hasRotationModeControl = false;
            this.rotationModeControlConsumer = {
                priority: MapResourcePriority.ORIENTATION_ROTATION,
                onAcquired: () => {
                    this.hasRotationModeControl = true;
                    this.updateRotation();
                },
                onCeded: () => {
                    this.hasRotationModeControl = false;
                }
            };
            this.targetOffsetParam = {
                targetProjectedOffset: msfsSdk.Vec2Math.create()
            };
            this.rangeEndpointsParam = {
                rangeEndpoints: msfsSdk.VecNMath.create(4)
            };
            this.nominalTargetOffsetSubject = msfsSdk.VecNSubject.createFromVector(msfsSdk.Vec2Math.create());
            this.nominalRangeEndpointsSubject = msfsSdk.VecNSubject.createFromVector(msfsSdk.VecNMath.create(4));
            this.needUpdateTargetOffset = false;
            this.needUpdateRangeEndpoints = false;
        }
        /** @inheritdoc */
        onAfterMapRender() {
            this.rotationModeControl.claim(this.rotationModeControlConsumer);
            this.orientationSub = this.orientationModule.orientation.sub(orientation => {
                var _a, _b, _c, _d, _e, _f;
                if (this.hasRotationModeControl) {
                    this.updateRotation();
                }
                (_a = this.targetOffsetPipe) === null || _a === void 0 ? void 0 : _a.destroy();
                (_b = this.rangeEndpointsPipe) === null || _b === void 0 ? void 0 : _b.destroy();
                this.targetOffsetPipe = undefined;
                this.rangeEndpointsPipe = undefined;
                const targetOffset = (_d = (_c = this.nominalTargetOffsets) === null || _c === void 0 ? void 0 : _c[orientation]) !== null && _d !== void 0 ? _d : MapOrientationRTRController.DEFAULT_TARGET_OFFSET;
                const rangeEndpoints = (_f = (_e = this.nominalRangeEndpoints) === null || _e === void 0 ? void 0 : _e[orientation]) !== null && _f !== void 0 ? _f : MapOrientationRTRController.DEFAULT_RANGE_ENDPOINTS;
                if (msfsSdk.SubscribableUtils.isSubscribable(targetOffset)) {
                    this.targetOffsetPipe = targetOffset.pipe(this.nominalTargetOffsetSubject);
                }
                else {
                    this.nominalTargetOffsetSubject.set(targetOffset);
                }
                if (msfsSdk.SubscribableUtils.isSubscribable(rangeEndpoints)) {
                    this.rangeEndpointsPipe = rangeEndpoints.pipe(this.nominalRangeEndpointsSubject);
                }
                else {
                    this.nominalRangeEndpointsSubject.set(rangeEndpoints);
                }
            }, true);
            this.nominalTargetOffsetSubject.sub(() => { this.needUpdateTargetOffset = true; }, true);
            this.nominalRangeEndpointsSubject.sub(() => { this.needUpdateRangeEndpoints = true; }, true);
        }
        /** @inheritdoc */
        onDeadZoneChanged() {
            this.needUpdateTargetOffset = true;
            this.needUpdateRangeEndpoints = true;
        }
        /** @inheritdoc */
        onMapProjectionChanged(mapProjection, changeFlags) {
            const projectedSizeChanged = msfsSdk.BitFlags.isAny(changeFlags, msfsSdk.MapProjectionChangeType.ProjectedSize);
            this.needUpdateTargetOffset || (this.needUpdateTargetOffset = projectedSizeChanged);
            this.needUpdateRangeEndpoints || (this.needUpdateRangeEndpoints = projectedSizeChanged);
        }
        /** @inheritdoc */
        onBeforeUpdated() {
            if (this.needUpdateTargetOffset) {
                this.updateTargetOffset();
                this.context.projection.setQueued(this.targetOffsetParam);
                this.needUpdateTargetOffset = false;
            }
            if (this.needUpdateRangeEndpoints) {
                this.updateRangeEndpoints();
                this.context.projection.setQueued(this.rangeEndpointsParam);
                this.needUpdateRangeEndpoints = false;
            }
        }
        /**
         * Updates the map rotation mode based on the current map orientation.
         */
        updateRotation() {
            let rotationType;
            switch (this.orientationModule.orientation.get()) {
                case exports.MapOrientation.HeadingUp:
                    rotationType = msfsSdk.MapRotation.HeadingUp;
                    break;
                case exports.MapOrientation.TrackUp:
                    rotationType = msfsSdk.MapRotation.TrackUp;
                    break;
                case exports.MapOrientation.DtkUp:
                    rotationType = msfsSdk.MapRotation.DtkUp;
                    break;
                default:
                    rotationType = msfsSdk.MapRotation.NorthUp;
            }
            this.rotationModule.rotationType.set(rotationType);
        }
        /**
         * Updates this controller's projected target offset.
         */
        updateTargetOffset() {
            const projectedSize = this.context.projection.getProjectedSize();
            const nominalTargetOffset = this.nominalTargetOffsetSubject.get();
            const targetOffset = msfsSdk.Vec2Math.copy(nominalTargetOffset, this.targetOffsetParam.targetProjectedOffset);
            targetOffset[0] += 0.5;
            targetOffset[1] += 0.5;
            msfsSdk.MapSystemUtils.nominalToTrueRelativeXY(targetOffset, projectedSize, this.context.deadZone.get(), targetOffset);
            targetOffset[0] -= 0.5;
            targetOffset[1] -= 0.5;
            targetOffset[0] *= projectedSize[0];
            targetOffset[1] *= projectedSize[1];
        }
        /**
         * Updates this controller's range endpoints.
         */
        updateRangeEndpoints() {
            const projectedSize = this.context.projection.getProjectedSize();
            const deadZone = this.context.deadZone.get();
            const nominalEndpoints = this.nominalRangeEndpointsSubject.get();
            const rangeEndpoints = this.rangeEndpointsParam.rangeEndpoints;
            rangeEndpoints[0] = msfsSdk.MapSystemUtils.nominalToTrueRelativeX(nominalEndpoints[0], projectedSize[0], deadZone);
            rangeEndpoints[1] = msfsSdk.MapSystemUtils.nominalToTrueRelativeY(nominalEndpoints[1], projectedSize[1], deadZone);
            rangeEndpoints[2] = msfsSdk.MapSystemUtils.nominalToTrueRelativeX(nominalEndpoints[2], projectedSize[0], deadZone);
            rangeEndpoints[3] = msfsSdk.MapSystemUtils.nominalToTrueRelativeY(nominalEndpoints[3], projectedSize[1], deadZone);
        }
        /** @inheritdoc */
        onMapDestroyed() {
            this.destroy();
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c;
            this.rotationModeControl.forfeit(this.rotationModeControlConsumer);
            (_a = this.orientationSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.targetOffsetPipe) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.rangeEndpointsPipe) === null || _c === void 0 ? void 0 : _c.destroy();
            super.destroy();
        }
    }
    MapOrientationRTRController.DEFAULT_TARGET_OFFSET = msfsSdk.Vec2Math.create();
    MapOrientationRTRController.DEFAULT_RANGE_ENDPOINTS = msfsSdk.VecNMath.create(4, 0.5, 0.5, 0.5, 0);

    /**
     * Controls the orientation of a map based on user settings.
     */
    class MapOrientationSettingsController extends msfsSdk.MapSystemController {
        /**
         * Creates a new instance of MapOrientationSettingsController.
         * @param context This controller's map context.
         * @param settingManager The setting manager used by this controller.
         */
        constructor(context, settingManager) {
            super(context);
            this.settingManager = settingManager;
            this.orientationModule = this.context.model.getModule(GarminMapKeys.Orientation);
            this.subs = [];
        }
        /** @inheritdoc */
        onAfterMapRender() {
            const orientationSetting = this.settingManager.tryGetSetting('mapOrientation');
            if (orientationSetting) {
                this.subs.push(orientationSetting.pipe(this.orientationModule.commandedOrientation, setting => {
                    var _a;
                    return (_a = MapOrientationSettingsController.MODE_MAP[setting]) !== null && _a !== void 0 ? _a : exports.MapOrientation.NorthUp;
                }));
            }
            const northUpAboveActiveSetting = this.settingManager.tryGetSetting('mapAutoNorthUpActive');
            if (northUpAboveActiveSetting) {
                this.subs.push(northUpAboveActiveSetting.pipe(this.orientationModule.northUpAboveActive, setting => {
                    return setting === true;
                }));
            }
            const northUpAboveRangeIndexSetting = this.settingManager.tryGetSetting('mapAutoNorthUpRangeIndex');
            if (northUpAboveRangeIndexSetting) {
                this.subs.push(northUpAboveRangeIndexSetting.pipe(this.orientationModule.northUpAboveRangeIndex, setting => {
                    return typeof setting === 'number' ? setting : Infinity;
                }));
            }
            const northUpOnGroundActiveSetting = this.settingManager.tryGetSetting('mapGroundNorthUpActive');
            if (northUpOnGroundActiveSetting) {
                this.subs.push(northUpOnGroundActiveSetting.pipe(this.orientationModule.northUpOnGroundActive, setting => {
                    return setting === true;
                }));
            }
        }
        /** @inheritdoc */
        onMapDestroyed() {
            this.destroy();
        }
        /** @inheritdoc */
        destroy() {
            for (const sub of this.subs) {
                sub.destroy();
            }
            super.destroy();
        }
    }
    MapOrientationSettingsController.MODE_MAP = {
        [exports.MapOrientationSettingMode.NorthUp]: exports.MapOrientation.NorthUp,
        [exports.MapOrientationSettingMode.HeadingUp]: exports.MapOrientation.HeadingUp,
        [exports.MapOrientationSettingMode.TrackUp]: exports.MapOrientation.TrackUp,
        [exports.MapOrientationSettingMode.DtkUp]: exports.MapOrientation.DtkUp
    };

    /* eslint-disable @typescript-eslint/no-non-null-assertion */
    /**
     * Controls the target, orientation, and range of a map while manual map panning is active.
     */
    class MapPanningRTRController extends msfsSdk.MapSystemController {
        constructor() {
            super(...arguments);
            this.panningModule = this.context.model.getModule(GarminMapKeys.Panning);
            this.rotationModule = this.context.model.getModule(msfsSdk.MapSystemKeys.Rotation);
            this.mapProjectionParams = {
                target: new msfsSdk.GeoPoint(0, 0)
            };
            this.targetControl = this.context[msfsSdk.MapSystemKeys.TargetControl];
            this.hasTargetControl = this.context.targetControlModerator === undefined;
            this.targetControlConsumer = {
                priority: MapResourcePriority.PANNING,
                onAcquired: () => {
                    this.hasTargetControl = true;
                    if (this.panningModule.isActive.get()) {
                        this.setMapTarget(this.panningModule.target.get());
                    }
                },
                onCeded: () => {
                    this.hasTargetControl = false;
                }
            };
            this.rotationModeControl = this.context[GarminMapKeys.RotationModeControl];
            this.rotationModeControlConsumer = {
                priority: MapResourcePriority.PANNING,
                onAcquired: () => {
                    var _a;
                    // While panning is active, the map keeps its rotation from when panning was activated.
                    (_a = this.rotationModule) === null || _a === void 0 ? void 0 : _a.rotationType.set(msfsSdk.MapRotation.Undefined);
                },
                onCeded: () => { }
            };
            this.orientationControl = this.context[GarminMapKeys.OrientationControl];
            this.orientationControlConsumer = {
                priority: MapResourcePriority.PANNING,
                onAcquired: () => { }, // While panning is active, the map keeps its desired orientation mode from when panning was activated, so we do nothing.
                onCeded: () => { }
            };
            this.rangeControl = this.context[msfsSdk.MapSystemKeys.RangeControl];
            this.rangeControlConsumer = {
                priority: MapResourcePriority.PANNING,
                onAcquired: () => { }, // We are just holding this to keep other things of lower priority from changing the range.
                onCeded: () => { }
            };
            this.useRangeSetting = this.context[GarminMapKeys.UseRangeSetting];
            this.useRangeSettingConsumer = {
                priority: MapResourcePriority.PANNING,
                onAcquired: () => { }, // Panning mode uses the use range setting state that was active when panning was activated, so we do nothing while we have control.
                onCeded: () => { }
            };
        }
        /** @inheritdoc */
        onAfterMapRender() {
            this.panningTargetSub = this.panningModule.target.sub(this.onTargetChanged.bind(this), false, true);
            this.panningActiveSub = this.panningModule.isActive.sub(this.onPanningActiveChanged.bind(this), true);
        }
        /**
         * Responds to map panning activation changes.
         * @param isActive Whether map panning is active.
         */
        onPanningActiveChanged(isActive) {
            if (isActive) {
                this.onPanningActivated();
            }
            else {
                this.onPanningDeactivated();
            }
        }
        /**
         * Responds to map panning activation.
         */
        onPanningActivated() {
            var _a, _b, _c, _d;
            (_a = this.targetControl) === null || _a === void 0 ? void 0 : _a.claim(this.targetControlConsumer);
            if (this.rotationModeControl) {
                this.rotationModeControl.claim(this.rotationModeControlConsumer);
            }
            else if (this.rotationModule) {
                // While panning is active, the map keeps its rotation from when panning was activated.
                this.rotationModule.rotationType.set(msfsSdk.MapRotation.Undefined);
            }
            (_b = this.orientationControl) === null || _b === void 0 ? void 0 : _b.claim(this.orientationControlConsumer);
            (_c = this.rangeControl) === null || _c === void 0 ? void 0 : _c.claim(this.rangeControlConsumer);
            (_d = this.useRangeSetting) === null || _d === void 0 ? void 0 : _d.claim(this.useRangeSettingConsumer);
            this.panningTargetSub.resume();
        }
        /**
         * Responds to map panning deactivation.
         */
        onPanningDeactivated() {
            var _a, _b, _c, _d, _e;
            this.panningTargetSub.pause();
            (_a = this.targetControl) === null || _a === void 0 ? void 0 : _a.forfeit(this.targetControlConsumer);
            (_b = this.rotationModeControl) === null || _b === void 0 ? void 0 : _b.forfeit(this.rotationModeControlConsumer);
            (_c = this.orientationControl) === null || _c === void 0 ? void 0 : _c.forfeit(this.orientationControlConsumer);
            (_d = this.rangeControl) === null || _d === void 0 ? void 0 : _d.forfeit(this.rangeControlConsumer);
            (_e = this.useRangeSetting) === null || _e === void 0 ? void 0 : _e.forfeit(this.useRangeSettingConsumer);
        }
        /**
         * Responds to when the map panning target changes.
         * @param target The new map panning target.
         */
        onTargetChanged(target) {
            if (this.hasTargetControl) {
                this.setMapTarget(target);
            }
        }
        /**
         * Sets the map projection's target.
         * @param target The target to set.
         */
        setMapTarget(target) {
            this.mapProjectionParams.target.set(target);
            this.context.projection.setQueued(this.mapProjectionParams);
        }
        /** @inheritdoc */
        onMapDestroyed() {
            this.destroy();
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c, _d, _e, _f, _g;
            (_a = this.targetControl) === null || _a === void 0 ? void 0 : _a.forfeit(this.targetControlConsumer);
            (_b = this.rotationModeControl) === null || _b === void 0 ? void 0 : _b.forfeit(this.rotationModeControlConsumer);
            (_c = this.orientationControl) === null || _c === void 0 ? void 0 : _c.forfeit(this.orientationControlConsumer);
            (_d = this.rangeControl) === null || _d === void 0 ? void 0 : _d.forfeit(this.rangeControlConsumer);
            (_e = this.useRangeSetting) === null || _e === void 0 ? void 0 : _e.forfeit(this.useRangeSettingConsumer);
            (_f = this.panningActiveSub) === null || _f === void 0 ? void 0 : _f.destroy();
            (_g = this.panningTargetSub) === null || _g === void 0 ? void 0 : _g.destroy();
            super.destroy();
        }
    }

    /**
     * Controls the pointer of a map.
     */
    class MapPointerController extends msfsSdk.MapSystemController {
        constructor() {
            super(...arguments);
            this.pointerModule = this.context.model.getModule(GarminMapKeys.Pointer);
        }
        /**
         * Activates or deactivates the map pointer.
         * @param isActive Whether to activate the map pointer.
         */
        setPointerActive(isActive) {
            if (isActive === this.pointerModule.isActive.get()) {
                return;
            }
            if (isActive) {
                this.pointerModule.target.set(this.context.projection.getTarget());
                this.pointerModule.position.set(this.context.projection.getTargetProjected());
            }
            this.pointerModule.isActive.set(isActive);
        }
        /**
         * Toggles activation of the map pointer.
         * @returns Whether the map pointer is active after the toggle operation.
         */
        togglePointerActive() {
            this.setPointerActive(!this.pointerModule.isActive.get());
            return this.pointerModule.isActive.get();
        }
        /**
         * Moves the map pointer.
         * @param dx The horizontal displacement, in pixels.
         * @param dy The vertical dispacement, in pixels.
         */
        movePointer(dx, dy) {
            const currentPos = this.pointerModule.position.get();
            this.pointerModule.position.set(currentPos[0] + dx, currentPos[1] + dy);
        }
        /**
         * Sets the map target to the current position of the pointer. The pointer will also be moved to the new projected
         * target position.
         */
        targetPointer() {
            const target = this.context.projection.invert(this.pointerModule.position.get(), MapPointerController.geoPointCache[0]);
            this.pointerModule.target.set(target);
            this.pointerModule.position.set(this.context.projection.getTargetProjected());
        }
    }
    MapPointerController.geoPointCache = [new msfsSdk.GeoPoint(0, 0)];

    /**
     * Controls the target, orientation, and range of a map while the map pointer is active.
     */
    class MapPointerRTRController extends msfsSdk.MapSystemController {
        /**
         * Creates a new instance of MapPointerRTRController.
         * @param context This controller's map context.
         * @param pointerBoundsOffset The offset of the boundary surrounding the area in which the pointer can freely move,
         * from the edge of the projected map, excluding the dead zone. Expressed as `[left, top, right, bottom]`, relative
         * to the width and height, as appropriate, of the projected map. A positive offset is directed toward the center of
         * the map.
         */
        constructor(context, pointerBoundsOffset) {
            super(context);
            this.pointerModule = this.context.model.getModule(GarminMapKeys.Pointer);
            this.panningModule = this.context.model.getModule(GarminMapKeys.Panning);
            this.orientationModule = this.context.model.getModule(GarminMapKeys.Orientation);
            this.pointerBounds = msfsSdk.VecNSubject.create(msfsSdk.VecNMath.create(4));
            this.needUpdatePointerScroll = false;
            this.pointerVec2Cache = [new Float64Array(2)];
            this.targetCache = new msfsSdk.GeoPoint(0, 0);
            this.pointerBoundsOffset = msfsSdk.SubscribableUtils.toSubscribable(pointerBoundsOffset, true);
        }
        /** @inheritdoc */
        onAfterMapRender() {
            var _a;
            this.targetPipe = this.pointerModule.target.pipe(this.panningModule.target, true);
            this.pointerBoundsSub = this.pointerBounds.sub(this.onPointerBoundsChanged.bind(this), false, true);
            this.pointerPositionSub = this.pointerModule.position.sub(this.onPointerPositionChanged.bind(this), false, true);
            this.pointerBoundsOffsetSub = this.pointerBoundsOffset.sub(this.updatePointerBounds.bind(this), true);
            this.pointerActiveSub = this.pointerModule.isActive.sub(this.onPointerActiveChanged.bind(this), true);
            this.commandedOrientationSub = (_a = this.orientationModule) === null || _a === void 0 ? void 0 : _a.commandedOrientation.sub(() => { this.pointerModule.isActive.set(false); }, false, true);
        }
        /**
         * Updates this controller's pointer bounds.
         */
        updatePointerBounds() {
            const deadZone = this.context.deadZone.get();
            const offset = this.pointerBoundsOffset.get();
            const size = this.context.projection.getProjectedSize();
            const minX = deadZone[0];
            const minY = deadZone[1];
            const maxX = size[0] - deadZone[2];
            const maxY = size[1] - deadZone[3];
            const width = maxX - minX;
            const height = maxY - minY;
            const centerX = (minX + maxX) / 2;
            const centerY = (minY + maxY) / 2;
            this.pointerBounds.set(Math.min(centerX, minX + width * offset[0]), Math.min(centerY, minY + height * offset[1]), Math.max(centerX, maxX - width * offset[2]), Math.max(centerY, maxY - height * offset[3]));
        }
        /**
         * Responds to map pointer activation changes.
         * @param isActive Whether the map pointer is active.
         */
        onPointerActiveChanged(isActive) {
            if (isActive) {
                this.onPointerActivated();
            }
            else {
                this.onPointerDeactivated();
            }
        }
        /**
         * Responds to map pointer activation.
         */
        onPointerActivated() {
            var _a, _b, _c, _d;
            (_a = this.targetPipe) === null || _a === void 0 ? void 0 : _a.resume(true);
            this.panningModule.isActive.set(true);
            (_b = this.pointerBoundsSub) === null || _b === void 0 ? void 0 : _b.resume();
            (_c = this.pointerPositionSub) === null || _c === void 0 ? void 0 : _c.resume();
            (_d = this.commandedOrientationSub) === null || _d === void 0 ? void 0 : _d.resume();
        }
        /**
         * Responds to map pointer deactivation.
         */
        onPointerDeactivated() {
            var _a, _b, _c, _d;
            (_a = this.commandedOrientationSub) === null || _a === void 0 ? void 0 : _a.pause();
            (_b = this.pointerBoundsSub) === null || _b === void 0 ? void 0 : _b.pause();
            (_c = this.pointerPositionSub) === null || _c === void 0 ? void 0 : _c.pause();
            (_d = this.targetPipe) === null || _d === void 0 ? void 0 : _d.pause();
            this.panningModule.isActive.set(false);
        }
        /**
         * Responds to map pointer position changes.
         */
        onPointerPositionChanged() {
            this.schedulePointerScrollUpdate();
        }
        /**
         * Responds to map pointer bounds changes.
         */
        onPointerBoundsChanged() {
            const position = this.pointerModule.position.get();
            const bounds = this.pointerBounds.get();
            const clampedPositionX = msfsSdk.MathUtils.clamp(position[0], bounds[0], bounds[2]);
            const clampedPositionY = msfsSdk.MathUtils.clamp(position[1], bounds[1], bounds[3]);
            this.pointerModule.position.set(clampedPositionX, clampedPositionY);
        }
        /**
         * Schedules an update to scrolling due to the pointer.
         */
        schedulePointerScrollUpdate() {
            this.needUpdatePointerScroll = true;
        }
        /**
         * Updates scrolling due to the pointer.
         */
        updatePointerScroll() {
            if (!this.needUpdatePointerScroll) {
                return;
            }
            const position = this.pointerModule.position.get();
            const bounds = this.pointerBounds.get();
            const clampedPositionX = msfsSdk.MathUtils.clamp(position[0], bounds[0], bounds[2]);
            const clampedPositionY = msfsSdk.MathUtils.clamp(position[1], bounds[1], bounds[3]);
            const scrollDeltaX = position[0] - clampedPositionX;
            const scrollDeltaY = position[1] - clampedPositionY;
            if (scrollDeltaX === 0 && scrollDeltaY === 0) {
                return;
            }
            this.pointerModule.position.set(clampedPositionX, clampedPositionY);
            const newTargetProjected = msfsSdk.Vec2Math.add(this.context.projection.getTargetProjected(), msfsSdk.Vec2Math.set(scrollDeltaX, scrollDeltaY, this.pointerVec2Cache[0]), this.pointerVec2Cache[0]);
            this.context.projection.invert(newTargetProjected, this.targetCache);
            this.pointerModule.target.set(this.targetCache);
            this.needUpdatePointerScroll = false;
        }
        /** @inheritdoc */
        onDeadZoneChanged() {
            this.updatePointerBounds();
        }
        /** @inheritdoc */
        onMapProjectionChanged(mapProjection, changeFlags) {
            if (msfsSdk.BitFlags.isAny(changeFlags, msfsSdk.MapProjectionChangeType.ProjectedSize)) {
                this.updatePointerBounds();
            }
        }
        /** @inheritdoc */
        onBeforeUpdated() {
            this.updatePointerScroll();
        }
        /** @inheritdoc */
        onMapDestroyed() {
            this.destroy();
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c, _d, _e, _f;
            if (this.pointerModule.isActive.get()) {
                this.panningModule.isActive.set(false);
            }
            (_a = this.targetPipe) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.pointerBoundsOffsetSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.pointerActiveSub) === null || _c === void 0 ? void 0 : _c.destroy();
            (_d = this.pointerBoundsSub) === null || _d === void 0 ? void 0 : _d.destroy();
            (_e = this.pointerPositionSub) === null || _e === void 0 ? void 0 : _e.destroy();
            (_f = this.commandedOrientationSub) === null || _f === void 0 ? void 0 : _f.destroy();
            super.destroy();
        }
    }

    /**
     * Controls the display of the range compass, and optionally the range ring, based on map orientation.
     */
    class MapRangeCompassController extends msfsSdk.MapSystemController {
        constructor() {
            var _a;
            super(...arguments);
            this.orientation = this.context.model.getModule(GarminMapKeys.Orientation).orientation;
            this.rangeCompassShow = this.context.model.getModule(GarminMapKeys.RangeCompass).show;
            this.rangeRingShow = (_a = this.context.model.getModule(GarminMapKeys.RangeRing)) === null || _a === void 0 ? void 0 : _a.show;
        }
        /** @inheritdoc */
        onAfterMapRender() {
            this.orientationSub = this.orientation.sub(mode => {
                var _a;
                const show = mode === exports.MapOrientation.HeadingUp || mode === exports.MapOrientation.TrackUp;
                this.rangeCompassShow.set(show);
                (_a = this.rangeRingShow) === null || _a === void 0 ? void 0 : _a.set(!show);
            }, true);
        }
        /** @inheritdoc */
        onMapDestroyed() {
            this.destroy();
        }
        /** @inheritdoc */
        destroy() {
            var _a;
            super.destroy();
            (_a = this.orientationSub) === null || _a === void 0 ? void 0 : _a.destroy();
        }
    }

    /**
     * Controls map range.
     */
    class MapRangeController extends msfsSdk.MapSystemController {
        /**
         * Constructor.
         * @param context This controller's map context.
         * @param nauticalRangeArray The map range array this controller sets for nautical distance mode. If not defined,
         * this controller will not change the range array when entering nautical distance mode.
         * @param metricRangeArray The map range array this controller sets for metric distance mode. If not defined, this
         * controller will not change the range array when entering metric distance mode.
         * @param settingManager A setting manager containing the map range index setting. If not defined, map range will
         * be set directly through the map model.
         * @param useSetting A subscribable which provides whether to control map range via the user setting. If not defined,
         * map range will always be controlled via the user setting. Ignored if `settingManager` is undefined.
         */
        constructor(context, nauticalRangeArray, metricRangeArray, settingManager, useSetting) {
            var _a, _b;
            super(context);
            this.nauticalRangeArray = nauticalRangeArray;
            this.metricRangeArray = metricRangeArray;
            this.useSetting = useSetting;
            this.rangeModule = this.context.model.getModule(GarminMapKeys.Range);
            this.distanceUnitsMode = (_b = (_a = this.context.model.getModule(GarminMapKeys.Units)) === null || _a === void 0 ? void 0 : _a.distanceMode) !== null && _b !== void 0 ? _b : msfsSdk.Subject.create(exports.UnitsDistanceSettingMode.Nautical);
            this.rangeSetting = settingManager === null || settingManager === void 0 ? void 0 : settingManager.getSetting('mapRangeIndex');
        }
        /** @inheritdoc */
        onAfterMapRender() {
            var _a;
            this.distanceModeSub = this.distanceUnitsMode.sub(mode => {
                if (mode === exports.UnitsDistanceSettingMode.Nautical) {
                    if (this.nauticalRangeArray !== undefined) {
                        this.rangeModule.nominalRanges.set(this.nauticalRangeArray);
                    }
                }
                else {
                    if (this.metricRangeArray) {
                        this.rangeModule.nominalRanges.set(this.metricRangeArray);
                    }
                }
            }, true);
            this.settingSub = (_a = this.rangeSetting) === null || _a === void 0 ? void 0 : _a.sub(setting => {
                this.rangeModule.setNominalRangeIndex(setting);
            }, false, true);
            if (this.settingSub) {
                if (this.useSetting) {
                    this.useSettingSub = this.useSetting.sub(useSetting => {
                        if (useSetting) {
                            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                            this.settingSub.resume(true);
                        }
                        else {
                            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                            this.settingSub.pause();
                        }
                    }, true);
                }
                else {
                    this.settingSub.resume(true);
                }
            }
        }
        /**
         * Sets the map range index. If the index is out of bounds, it will be clamped before being set.
         * @param index The index to set.
         * @param bypassUserSetting Whether to bypass the map range index user setting, if one is defined for
         * this controller, and set the range index directly on the map range module. Defaults to `false`.
         * @returns The index that was set.
         */
        setRangeIndex(index, bypassUserSetting = false) {
            var _a, _b;
            index = msfsSdk.MathUtils.clamp(index, 0, this.rangeModule.nominalRanges.get().length - 1);
            if (!bypassUserSetting && this.rangeSetting !== undefined && ((_b = (_a = this.useSetting) === null || _a === void 0 ? void 0 : _a.get()) !== null && _b !== void 0 ? _b : true)) {
                this.rangeSetting.value = index;
            }
            else {
                this.rangeModule.setNominalRangeIndex(index);
            }
            return index;
        }
        /**
         * Changes the map range index by a given amount. If the change results in an index that is out of bounds, it will
         * be clamped before being set.
         * @param delta The change to apply to the index.
         * @param bypassUserSetting Whether to bypass the map range index user setting, if one is defined for
         * this controller, and change the range index directly on the map range module. Defaults to `false`.
         * @returns The final index that was set.
         */
        changeRangeIndex(delta, bypassUserSetting = false) {
            return this.setRangeIndex(this.rangeModule.nominalRangeIndex.get() + delta, bypassUserSetting);
        }
        /** @inheritdoc */
        onMapDestroyed() {
            this.destroy();
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c;
            super.destroy();
            (_a = this.distanceModeSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.settingSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.useSettingSub) === null || _c === void 0 ? void 0 : _c.destroy();
        }
    }

    /**
     * Controls map projection range based on the range module's nominal range value.
     */
    class MapRangeRTRController extends msfsSdk.MapSystemController {
        constructor() {
            super(...arguments);
            this.rangeModule = this.context.model.getModule(GarminMapKeys.Range);
            this.rangeParam = {
                range: 0
            };
            this.needUpdateRange = false;
        }
        /** @inheritdoc */
        onAfterMapRender() {
            this.rangeSub = this.rangeModule.nominalRange.sub(() => {
                this.needUpdateRange = true;
            }, true);
        }
        /** @inheritdoc */
        onBeforeUpdated() {
            if (this.needUpdateRange) {
                const range = this.rangeModule.nominalRange.get();
                this.rangeParam.range = range.asUnit(msfsSdk.UnitType.GA_RADIAN);
                this.context.projection.setQueued(this.rangeParam);
                this.needUpdateRange = false;
            }
        }
        /** @inheritdoc */
        onMapDestroyed() {
            this.destroy();
        }
        /** @inheritdoc */
        destroy() {
            var _a;
            super.destroy();
            (_a = this.rangeSub) === null || _a === void 0 ? void 0 : _a.destroy();
        }
    }

    /**
     * Provides utility functions for working with Garmin maps.
     */
    class MapUtils {
        /**
         * Gets the standard map range array for next-generation (NXi, G3000, etc) avionics units.
         * @param units The distance units mode for which to get the array.
         * @returns The standard map range array for next-generation (NXi, G3000, etc) avionics units.
         */
        static nextGenMapRanges(units) {
            return MapUtils.NEXT_GEN_MAP_RANGES[units];
        }
        /**
         * Gets the standard traffic map range array for next-generation (NXi, G3000, etc) avionics units.
         * @returns The standard traffic map range array for next-generation (NXi, G3000, etc) avionics units.
         */
        static nextGenTrafficMapRanges() {
            return MapUtils.NEXT_GEN_TRAFFIC_MAP_RANGES;
        }
        /**
         * Gets the earth colors definition for no terrain colors.
         * @returns The earth colors definition for no terrain colors.
         */
        static noTerrainEarthColors() {
            return MapUtils.NO_TERRAIN_EARTH_COLORS;
        }
        /**
         * Gets the earth colors definition for absolute terrain colors.
         * @returns The earth colors definition for absolute terrain colors.
         */
        static absoluteTerrainEarthColors() {
            return MapUtils.ABSOLUTE_TERRAIN_EARTH_COLORS;
        }
        /**
         * Gets the earth colors definition for relative terrain colors.
         * @returns The earth colors definition for relative terrain colors.
         */
        static relativeTerrainEarthColors() {
            return MapUtils.RELATIVE_TERRAIN_EARTH_COLORS;
        }
        /**
         * Gets the earth colors definition for on ground relative terrain colors.
         * @returns The earth colors definition for on ground relative terrain colors.
         */
        static groundTerrainEarthColors() {
            return MapUtils.GROUND_TERRAIN_EARTH_COLORS;
        }
        /**
         * Gets the weather color array for the Connext precipitation radar overlay.
         * @returns The weather color array for the Connext precipitation radar overlay.
         */
        static connextPrecipRadarColors() {
            return MapUtils.CONNEXT_PRECIP_RADAR_COLORS;
        }
    }
    MapUtils.NEXT_GEN_MAP_RANGES = {
        [exports.UnitsDistanceSettingMode.Nautical]: [
            ...[
                250,
                400,
                500,
                750,
                1000,
                1500,
                2500
            ].map(value => msfsSdk.UnitType.FOOT.createNumber(value).readonly),
            ...[
                0.5,
                0.75,
                1,
                1.5,
                2.5,
                4,
                5,
                7.5,
                10,
                15,
                25,
                40,
                50,
                75,
                100,
                150,
                250,
                400,
                500,
                750,
                1000
            ].map(value => msfsSdk.UnitType.NMILE.createNumber(value).readonly)
        ],
        [exports.UnitsDistanceSettingMode.Metric]: [
            ...[
                75,
                100,
                150,
                250,
                400,
                500,
                750
            ].map(value => msfsSdk.UnitType.METER.createNumber(value).readonly),
            ...[
                1,
                1.5,
                2.5,
                4,
                5,
                8,
                10,
                15,
                20,
                40,
                50,
                75,
                100,
                150,
                250,
                350,
                500,
                800,
                1000,
                1500,
                2000
            ].map(value => msfsSdk.UnitType.KILOMETER.createNumber(value).readonly)
        ],
        [exports.UnitsDistanceSettingMode.Statute]: []
    };
    MapUtils.NEXT_GEN_TRAFFIC_MAP_RANGES = [
        ...[
            500,
            500,
            500,
            1000,
            1000,
            1000,
            2000,
            2000
        ].map(value => msfsSdk.UnitType.FOOT.createNumber(value).readonly),
        ...[
            1,
            1,
            2,
            2,
            6,
            6,
            12,
            12,
            24,
            24,
            40,
            40,
            40,
            40,
            40,
            40,
            40,
            40,
            40,
            40
        ].map(value => msfsSdk.UnitType.NMILE.createNumber(value).readonly)
    ];
    MapUtils.NO_TERRAIN_EARTH_COLORS = {
        colors: msfsSdk.BingComponent.createEarthColorsArray('#000084', [
            {
                elev: 0,
                color: '#000000'
            }
        ], 0, 0, 1),
        elevationRange: msfsSdk.Vec2Math.create(0, 30000)
    };
    MapUtils.ABSOLUTE_TERRAIN_EARTH_COLORS = {
        colors: msfsSdk.BingComponent.createEarthColorsArray('#000084', [
            {
                elev: -1400,
                color: '#39737b'
            },
            {
                elev: -1300,
                color: '#397b7b'
            },
            {
                elev: -1200,
                color: '#397b84'
            },
            {
                elev: -1100,
                color: '#427b84'
            },
            {
                elev: -1000,
                color: '#428484'
            },
            {
                elev: -900,
                color: '#428c8c'
            },
            {
                elev: -800,
                color: '#4a8c8c'
            },
            {
                elev: -700,
                color: '#4a948c'
            },
            {
                elev: -600,
                color: '#4a9494'
            },
            {
                elev: -500,
                color: '#4a9c94'
            },
            {
                elev: -400,
                color: '#4a9484'
            },
            {
                elev: -300,
                color: '#4a8c6b'
            },
            {
                elev: -200,
                color: '#4a8c63'
            },
            {
                elev: -100,
                color: '#4a845a'
            },
            {
                elev: 0,
                color: '#427238'
            },
            {
                elev: 100,
                color: '#427b42'
            },
            {
                elev: 200,
                color: '#427331'
            },
            {
                elev: 300,
                color: '#427329'
            },
            {
                elev: 400,
                color: '#426b18'
            },
            {
                elev: 500,
                color: '#426b08'
            },
            {
                elev: 600,
                color: '#4a6b10'
            },
            {
                elev: 700,
                color: '#527310'
            },
            {
                elev: 800,
                color: '#5a7318'
            },
            {
                elev: 900,
                color: '#637b18'
            },
            {
                elev: 1000,
                color: '#6b8421'
            },
            {
                elev: 1100,
                color: '#738421'
            },
            {
                elev: 1200,
                color: '#848c29'
            },
            {
                elev: 1300,
                color: '#8c8c29'
            },
            {
                elev: 1400,
                color: '#949431'
            },
            {
                elev: 1500,
                color: '#9c9c31'
            },
            {
                elev: 1600,
                color: '#a59c31'
            },
            {
                elev: 1700,
                color: '#ada539'
            },
            {
                elev: 1800,
                color: '#b5ad39'
            },
            {
                elev: 1900,
                color: '#c5ad42'
            },
            {
                elev: 2000,
                color: '#c5b542'
            },
            {
                elev: 2100,
                color: '#c5ad42'
            },
            {
                elev: 2299,
                color: '#c5ad42'
            },
            {
                elev: 2300,
                color: '#c5a442'
            },
            {
                elev: 2599,
                color: '#c5a442'
            },
            {
                elev: 2600,
                color: '#c59c42'
            },
            {
                elev: 2899,
                color: '#c59c42'
            },
            {
                elev: 2900,
                color: '#c59442'
            },
            {
                elev: 3000,
                color: '#c59439'
            },
            {
                elev: 3100,
                color: '#c58c39'
            },
            {
                elev: 3200,
                color: '#bd8c3a'
            },
            {
                elev: 3699,
                color: '#bd8c3a'
            },
            {
                elev: 3700,
                color: '#bd8339'
            },
            {
                elev: 3800,
                color: '#b58339'
            },
            {
                elev: 3900,
                color: '#b58331'
            },
            {
                elev: 4199,
                color: '#b58331'
            },
            {
                elev: 4200,
                color: '#b57b31'
            },
            {
                elev: 4300,
                color: '#ad7b31'
            },
            {
                elev: 4699,
                color: '#ad7b31'
            },
            {
                elev: 4700,
                color: '#ad7329'
            },
            {
                elev: 4800,
                color: '#a47329'
            },
            {
                elev: 5199,
                color: '#a47329'
            },
            {
                elev: 5200,
                color: '#a46b29'
            },
            {
                elev: 5399,
                color: '#a46b29'
            },
            {
                elev: 5400,
                color: '#9c6b29'
            },
            {
                elev: 5599,
                color: '#9c6b29'
            },
            {
                elev: 5600,
                color: '#9c6b21'
            },
            {
                elev: 5700,
                color: '#9c6321'
            },
            {
                elev: 5899,
                color: '#9c6321'
            },
            {
                elev: 5900,
                color: '#946321'
            },
            {
                elev: 6299,
                color: '#946321'
            },
            {
                elev: 6300,
                color: '#945a21'
            },
            {
                elev: 6599,
                color: '#945a21'
            },
            {
                elev: 6600,
                color: '#945a19'
            },
            {
                elev: 6899,
                color: '#945a19'
            },
            {
                elev: 6900,
                color: '#945219'
            },
            {
                elev: 7299,
                color: '#945219'
            },
            {
                elev: 7300,
                color: '#8c5218'
            },
            {
                elev: 7599,
                color: '#8c5218'
            },
            {
                elev: 7600,
                color: '#8c4a19'
            },
            {
                elev: 7700,
                color: '#8c4a10'
            },
            {
                elev: 8499,
                color: '#8c4a10'
            },
            {
                elev: 8500,
                color: '#8c4210'
            },
            {
                elev: 8999,
                color: '#8c4210'
            },
            {
                elev: 9000,
                color: '#8c4208'
            },
            {
                elev: 9799,
                color: '#8c4208'
            },
            {
                elev: 9800,
                color: '#8c3a08'
            },
            {
                elev: 9999,
                color: '#8c3a08'
            },
            {
                elev: 10000,
                color: '#8c3a00'
            },
            {
                elev: 10899,
                color: '#8c3a00'
            },
            {
                elev: 10900,
                color: '#8c3a08'
            },
            {
                elev: 11199,
                color: '#8c3a08'
            },
            {
                elev: 11200,
                color: '#8c4208'
            },
            {
                elev: 11799,
                color: '#8c4208'
            },
            {
                elev: 11800,
                color: '#8c4210'
            },
            {
                elev: 12699,
                color: '#8c4210'
            },
            {
                elev: 12700,
                color: '#8c4a19'
            },
            {
                elev: 13599,
                color: '#8c4a19'
            },
            {
                elev: 13600,
                color: '#8c4a21'
            },
            {
                elev: 14099,
                color: '#8c4a21'
            },
            {
                elev: 14100,
                color: '#8c5221'
            },
            {
                elev: 14499,
                color: '#8c5221'
            },
            {
                elev: 14500,
                color: '#8c5229'
            },
            {
                elev: 15399,
                color: '#8c5229'
            },
            {
                elev: 15400,
                color: '#8c5231'
            },
            {
                elev: 15599,
                color: '#8c5231'
            },
            {
                elev: 15600,
                color: '#8c5a31'
            },
            {
                elev: 16299,
                color: '#8c5a31'
            },
            {
                elev: 16300,
                color: '#8c5a39'
            },
            {
                elev: 16300,
                color: '#8c5a39'
            },
            {
                elev: 16999,
                color: '#8b5a39'
            },
            {
                elev: 17000,
                color: '#8c6339'
            },
            {
                elev: 17199,
                color: '#8c6339'
            },
            {
                elev: 17200,
                color: '#8c6342'
            },
            {
                elev: 17399,
                color: '#8c6342'
            },
            {
                elev: 17400,
                color: '#946342'
            },
            {
                elev: 18099,
                color: '#946342'
            },
            {
                elev: 18100,
                color: '#94634a'
            },
            {
                elev: 18499,
                color: '#94634a'
            },
            {
                elev: 18500,
                color: '#946b4a'
            },
            {
                elev: 18999,
                color: '#946b4a'
            },
            {
                elev: 19000,
                color: '#946b52'
            },
            {
                elev: 19899,
                color: '#946b52'
            },
            {
                elev: 19900,
                color: '#946b5b'
            },
            {
                elev: 20000,
                color: '#94735a'
            },
            {
                elev: 20999,
                color: '#94735a'
            },
            {
                elev: 21000,
                color: '#947363'
            },
            {
                elev: 21499,
                color: '#947363'
            },
            {
                elev: 21500,
                color: '#947b63'
            },
            {
                elev: 21699,
                color: '#947b63'
            },
            {
                elev: 21700,
                color: '#947b6b'
            },
            {
                elev: 22499,
                color: '#947b6b'
            },
            {
                elev: 22500,
                color: '#947b73'
            },
            {
                elev: 22699,
                color: '#947b73'
            },
            {
                elev: 22700,
                color: '#948473'
            },
            {
                elev: 23499,
                color: '#948473'
            },
            {
                elev: 23500,
                color: '#94847b'
            },
            {
                elev: 24299,
                color: '#94847b'
            },
            {
                elev: 24300,
                color: '#948c7b'
            },
            {
                elev: 24400,
                color: '#948c83'
            },
            {
                elev: 25399,
                color: '#948c83'
            },
            {
                elev: 25400,
                color: '#948c8c'
            },
            {
                elev: 25699,
                color: '#948c8c'
            },
            {
                elev: 25700,
                color: '#94948c'
            },
            {
                elev: 26299,
                color: '#94948c'
            },
            {
                elev: 26300,
                color: '#949494'
            },
            {
                elev: 26999,
                color: '#949494'
            },
            {
                elev: 27000,
                color: '#9c9c9c'
            },
            {
                elev: 27499,
                color: '#9c9c9c'
            },
            {
                elev: 27500,
                color: '#a4a4a4'
            },
            {
                elev: 27999,
                color: '#a4a4a4'
            },
            {
                elev: 28000,
                color: '#adadad'
            },
            {
                elev: 28499,
                color: '#adadad'
            },
            {
                elev: 28500,
                color: '#b5b5b5'
            }
        ], -1400, 28500, 300),
        elevationRange: msfsSdk.Vec2Math.create(-1400, 28500)
    };
    MapUtils.RELATIVE_TERRAIN_EARTH_COLORS = {
        colors: msfsSdk.BingComponent.createEarthColorsArray('#000084', [
            {
                elev: 0,
                color: '#7f0000'
            },
            {
                elev: 99,
                color: '#7f0000'
            },
            {
                elev: 100,
                color: '#a0a000'
            },
            {
                elev: 999,
                color: '#a0a000'
            },
            {
                elev: 1000,
                color: '#007a00'
            },
            {
                elev: 1999,
                color: '#007a00'
            },
            {
                elev: 2000,
                color: '#000000'
            }
        ], -400, 2000, 25),
        elevationRange: msfsSdk.Vec2Math.create(-400, 2000)
    };
    MapUtils.GROUND_TERRAIN_EARTH_COLORS = {
        colors: msfsSdk.BingComponent.createEarthColorsArray('#000084', [
            {
                elev: -400,
                color: '#7f0000'
            },
            {
                elev: -399,
                color: '#000000'
            },
            {
                elev: 0,
                color: '#000000'
            }
        ], -400, 2000, 25),
        elevationRange: msfsSdk.Vec2Math.create(-400, 2000)
    };
    MapUtils.CONNEXT_PRECIP_RADAR_COLORS = [
        [msfsSdk.BingComponent.hexaToRGBAColor('#00000000'), 0.03],
        [msfsSdk.BingComponent.hexaToRGBAColor('#04d404ff'), 0.25],
        [msfsSdk.BingComponent.hexaToRGBAColor('#04ab04ff'), 2.5],
        [msfsSdk.BingComponent.hexaToRGBAColor('#ffff00ff'), 11.5],
        [msfsSdk.BingComponent.hexaToRGBAColor('#fbe304ff'), 23.4],
        [msfsSdk.BingComponent.hexaToRGBAColor('#fbab04ff'), 48],
        [msfsSdk.BingComponent.hexaToRGBAColor('#fb6b04ff'), 100],
        [msfsSdk.BingComponent.hexaToRGBAColor('#fb0404ff'), 100]
    ];

    /**
     * Map terrain display mode.
     */
    exports.MapTerrainMode = void 0;
    (function (MapTerrainMode) {
        MapTerrainMode[MapTerrainMode["None"] = 0] = "None";
        MapTerrainMode[MapTerrainMode["Absolute"] = 1] = "Absolute";
        MapTerrainMode[MapTerrainMode["Relative"] = 2] = "Relative";
        MapTerrainMode[MapTerrainMode["Ground"] = 3] = "Ground";
    })(exports.MapTerrainMode || (exports.MapTerrainMode = {}));
    /**
     * A module describing the display of terrain.
     */
    class MapTerrainModule {
        constructor() {
            /** The terrain display mode. */
            this.terrainMode = msfsSdk.Subject.create(exports.MapTerrainMode.Absolute);
            /** Whether to show the terrain scale. */
            this.showScale = msfsSdk.Subject.create(false);
            /** Whether the relative terrain mode is in a failed state. */
            this.isRelativeModeFailed = msfsSdk.Subject.create(false);
        }
    }

    /**
     * Controls the display of terrain colors based on the terrain mode value in {@link MapTerrainModule}.
     */
    class MapTerrainColorsController extends msfsSdk.MapSystemController {
        /**
         * Constructor.
         * @param context This controller's map context.
         * @param colors The terrain colors to use for each terrain mode. Default colors will be used for any mode which does
         * not have a colors definition.
         * @param groundRelativeBlendDuration The amount of time, in milliseconds, over which to blend the on-ground and
         * relative terrain mode colors when transitioning between the two. Defaults to 0 milliseconds. A blend transition is
         * only possible if colors are defined for both the on-ground and relative terrain modes, and the colors for both
         * modes have the same number of steps and are applied over the same elevation range.
         */
        constructor(context, colors, groundRelativeBlendDuration = 0) {
            super(context);
            this.colors = colors;
            this.groundRelativeBlendDuration = groundRelativeBlendDuration;
            this.terrainModule = this.context.model.getModule(GarminMapKeys.Terrain);
            this.terrainColorsModule = this.context.model.getModule(msfsSdk.MapSystemKeys.TerrainColors);
            this.groundRelativeBlendTimeStep = 0;
            this.groundRelativeBlendTimeOffset = 0;
            this.blendArmedMode = undefined;
            this.isBlending = false;
            this.blendDirection = 1;
            this.blendProgress = 0;
            this.lastBlendIndex = -1;
            this.lastBlendTime = 0;
            if (groundRelativeBlendDuration > 0) {
                const groundDef = colors[exports.MapTerrainMode.Ground];
                const relativeDef = colors[exports.MapTerrainMode.Relative];
                if (groundDef && relativeDef && groundDef.colors.length === relativeDef.colors.length && msfsSdk.Vec2Math.equals(groundDef.elevationRange, relativeDef.elevationRange)) {
                    const desiredHz = 1000 / (groundRelativeBlendDuration / (MapTerrainColorsController.BLEND_UPDATE_TARGET_STEP_COUNT + 1));
                    const actualHz = Math.min(desiredHz, MapTerrainColorsController.BLEND_UPDATE_MAX_HZ);
                    const blendTimeStep = 1000 / actualHz;
                    const blendSteps = Math.ceil(groundRelativeBlendDuration / blendTimeStep) - 1;
                    this.groundRelativeBlendTimeStep = blendTimeStep;
                    this.groundRelativeBlendTimeOffset = (groundRelativeBlendDuration - (blendSteps - 1) * blendTimeStep) / 2;
                    this.groundRelativeBlendColors = MapTerrainColorsController.createGroundRelativeBlendedColors(groundDef.colors, relativeDef.colors, groundRelativeBlendDuration, blendTimeStep);
                }
            }
        }
        /** @inheritdoc */
        onAfterMapRender() {
            this.modeSub = this.terrainModule.terrainMode.sub(mode => {
                var _a;
                this.terrainColorsModule.reference.set(MapTerrainColorsController.MODE_REFERENCE_MAP[mode]);
                const colorsDef = (_a = this.colors[mode]) !== null && _a !== void 0 ? _a : MapTerrainColorsController.DEFAULT_COLORS;
                this.terrainColorsModule.colorsElevationRange.set(colorsDef.elevationRange);
                let isBlending = false;
                // Check if we need to start blending between the on-ground and relative terrain colors.
                if (this.groundRelativeBlendColors && (mode === exports.MapTerrainMode.Ground || mode === exports.MapTerrainMode.Relative)) {
                    if (this.blendArmedMode === mode) {
                        isBlending = true;
                        if (!this.isBlending) {
                            this.isBlending = true;
                            this.blendProgress = mode === exports.MapTerrainMode.Relative ? 0 : this.groundRelativeBlendDuration;
                            this.lastBlendIndex = -1;
                            this.lastBlendTime = Date.now();
                        }
                        this.blendDirection = mode === exports.MapTerrainMode.Relative ? 1 : -1;
                    }
                    this.blendArmedMode = mode === exports.MapTerrainMode.Ground ? exports.MapTerrainMode.Relative : exports.MapTerrainMode.Ground;
                }
                if (!isBlending) {
                    this.isBlending = false;
                    this.terrainColorsModule.colors.set(colorsDef.colors);
                }
            }, true);
        }
        /** @inheritdoc */
        onAfterUpdated() {
            var _a;
            if (!this.isBlending || !this.groundRelativeBlendColors) {
                return;
            }
            const currentTime = Date.now();
            this.blendProgress += (currentTime - this.lastBlendTime) * this.blendDirection;
            if (this.blendProgress <= 0 || this.blendProgress >= this.groundRelativeBlendDuration) {
                this.isBlending = false;
                const colorsDef = (_a = this.colors[this.terrainModule.terrainMode.get()]) !== null && _a !== void 0 ? _a : MapTerrainColorsController.DEFAULT_COLORS;
                this.terrainColorsModule.colors.set(colorsDef.colors);
            }
            else {
                const blendColorsIndex = this.blendDirection === 1
                    ? Math.floor((this.blendProgress - this.groundRelativeBlendTimeOffset) / this.groundRelativeBlendTimeStep)
                    : Math.ceil((this.blendProgress - this.groundRelativeBlendTimeOffset) / this.groundRelativeBlendTimeStep);
                if (blendColorsIndex >= 0 && blendColorsIndex < this.groundRelativeBlendColors.length && blendColorsIndex !== this.lastBlendIndex) {
                    this.lastBlendIndex = blendColorsIndex;
                    this.terrainColorsModule.colors.set(this.groundRelativeBlendColors[blendColorsIndex]);
                }
            }
            this.lastBlendTime = currentTime;
        }
        /** @inheritdoc */
        onMapDestroyed() {
            this.destroy();
        }
        /** @inheritdoc */
        destroy() {
            var _a;
            super.destroy();
            (_a = this.modeSub) === null || _a === void 0 ? void 0 : _a.destroy();
        }
        /**
         * Creates an array of terrain color arrays where each color array is an intermediate step in a blend transition
         * between the on-ground mode and relative mode color arrays.
         * @param groundColors The on-ground mode earth color array.
         * @param relativeColors The relative mode earth color array.
         * @param blendDuration The amount of time, in milliseconds, over which the blend transition occurs.
         * @param blendTimeStep The amount of time, in milliseconds, covered by each blend step.
         * @returns An array of terrain color arrays where each color array is an intermediate step in the specified blend
         * transition between the on-ground mode and relative mode color arrays, or `undefined` if a blend transition is not
         * possible.
         */
        static createGroundRelativeBlendedColors(groundColors, relativeColors, blendDuration, blendTimeStep) {
            if (groundColors.length !== relativeColors.length) {
                return undefined;
            }
            const blendSteps = Math.ceil(blendDuration / blendTimeStep) - 1;
            if (blendSteps === 0) {
                return undefined;
            }
            const timeOffset = (blendDuration - (blendSteps - 1) * blendTimeStep) / 2;
            const blendedColors = msfsSdk.ArrayUtils.create(blendSteps, () => []);
            for (let i = 0; i < groundColors.length; i++) {
                const curve = new Avionics.Curve();
                curve.interpolationFunction = Avionics.CurveTool.StringColorRGBInterpolation;
                curve.add(0, msfsSdk.BingComponent.rgbToHexaColor(groundColors[i]));
                curve.add(blendDuration, msfsSdk.BingComponent.rgbToHexaColor(relativeColors[i]));
                for (let j = 0; j < blendSteps; j++) {
                    blendedColors[j][i] = msfsSdk.BingComponent.hexaToRGBColor(curve.evaluate(timeOffset + blendTimeStep * j));
                }
            }
            return blendedColors;
        }
    }
    //PM Modif disable access to EBingReference because it creates some issues in our context and we don't need that
    MapTerrainColorsController.DEFAULT_COLORS = MapUtils.noTerrainEarthColors();
    // MapTerrainColorsController.MODE_REFERENCE_MAP = {
    //     [exports.MapTerrainMode.None]: EBingReference.SEA,
    //     [exports.MapTerrainMode.Absolute]: EBingReference.SEA,
    //     [exports.MapTerrainMode.Relative]: EBingReference.PLANE,
    //     [exports.MapTerrainMode.Ground]: EBingReference.PLANE
    // };
    MapTerrainColorsController.MODE_REFERENCE_MAP = {
        [exports.MapTerrainMode.None]: 0,
        [exports.MapTerrainMode.Absolute]: 0,
        [exports.MapTerrainMode.Relative]: 0,
        [exports.MapTerrainMode.Ground]: 0
    };
    //End PM Modif
    MapTerrainColorsController.BLEND_UPDATE_TARGET_STEP_COUNT = 100;
    MapTerrainColorsController.BLEND_UPDATE_MAX_HZ = 30;

    /**
     * Controls the display of terrain based on user settings.
     */
    class MapTerrainController extends msfsSdk.MapSystemController {
        // eslint-disable-next-line jsdoc/require-jsdoc
        constructor(context, settingManager, arg3) {
            var _a, _b, _c, _d, _e, _f, _g;
            super(context);
            this.terrainModule = this.context.model.getModule(GarminMapKeys.Terrain);
            this.rangeIndex = this.context.model.getModule(GarminMapKeys.Range).nominalRangeIndex;
            this.isOnGround = (_b = (_a = this.context.model.getModule(msfsSdk.MapSystemKeys.OwnAirplaneProps)) === null || _a === void 0 ? void 0 : _a.isOnGround) !== null && _b !== void 0 ? _b : msfsSdk.Subject.create(false);
            this.isGpsDataValid = (_d = (_c = this.context.model.getModule(msfsSdk.MapSystemKeys.DataIntegrity)) === null || _c === void 0 ? void 0 : _c.gpsSignalValid) !== null && _d !== void 0 ? _d : msfsSdk.Subject.create(true);
            const options = arg3 === undefined ? undefined : typeof arg3 === 'object' ? arg3 : { allowRelative: arg3 };
            this.allowRelative = (_e = options === null || options === void 0 ? void 0 : options.allowRelative) !== null && _e !== void 0 ? _e : true;
            this.defaultTerrainMode = msfsSdk.SubscribableUtils.toSubscribable((_f = options === null || options === void 0 ? void 0 : options.defaultMode) !== null && _f !== void 0 ? _f : exports.MapTerrainMode.None, true);
            this.modeSetting = settingManager === null || settingManager === void 0 ? void 0 : settingManager.tryGetSetting('mapTerrainMode');
            this.rangeIndexSetting = (_g = settingManager === null || settingManager === void 0 ? void 0 : settingManager.tryGetSetting('mapTerrainRangeIndex')) !== null && _g !== void 0 ? _g : msfsSdk.Subject.create(Number.MAX_SAFE_INTEGER);
            this.showScaleSetting = settingManager === null || settingManager === void 0 ? void 0 : settingManager.tryGetSetting('mapTerrainScaleShow');
        }
        /** @inheritdoc */
        onAfterMapRender() {
            var _a;
            if (this.modeSetting !== undefined) {
                this.terrainModeState = msfsSdk.MappedSubject.create(this.defaultTerrainMode, this.modeSetting, this.rangeIndexSetting, this.rangeIndex, this.isOnGround, this.isGpsDataValid);
                this.terrainModeState.sub(([defaultMode, modeSetting, rangeIndexSetting, rangeIndex, isOnGround, isGpsDataValid]) => {
                    let mode = exports.MapTerrainMode.None;
                    let isRelativeFailed = false;
                    if (rangeIndex <= rangeIndexSetting) {
                        switch (modeSetting) {
                            case exports.MapTerrainSettingMode.Absolute:
                                mode = exports.MapTerrainMode.Absolute;
                                break;
                            case exports.MapTerrainSettingMode.Relative:
                                if (this.allowRelative) {
                                    if (isGpsDataValid) {
                                        mode = isOnGround ? exports.MapTerrainMode.Ground : exports.MapTerrainMode.Relative;
                                    }
                                    else {
                                        mode = defaultMode;
                                        isRelativeFailed = true;
                                    }
                                }
                                break;
                        }
                    }
                    this.terrainModule.terrainMode.set(mode);
                    this.terrainModule.isRelativeModeFailed.set(isRelativeFailed);
                }, true);
            }
            this.showScalePipe = (_a = this.showScaleSetting) === null || _a === void 0 ? void 0 : _a.pipe(this.terrainModule.showScale);
        }
        /** @inheritdoc */
        onMapDestroyed() {
            this.destroy();
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b;
            super.destroy();
            (_a = this.terrainModeState) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.showScalePipe) === null || _b === void 0 ? void 0 : _b.destroy();
        }
    }

    /**
     * Controls the display of traffic based on the values in {@link MapGarminTrafficModule}.
     */
    class MapTrafficController extends msfsSdk.MapSystemController {
        /**
         * Constructor.
         * @param context This controller's map context.
         * @param useOuterRangeAsOffScale Whether to use the map's outer traffic range as the traffic off-scale range.
         */
        constructor(context, useOuterRangeAsOffScale) {
            super(context);
            this.useOuterRangeAsOffScale = useOuterRangeAsOffScale;
            this.rangeModule = this.context.model.getModule(GarminMapKeys.Range);
            this.trafficModule = this.context.model.getModule(msfsSdk.MapSystemKeys.Traffic);
            this.garminTrafficModule = this.context.model.getModule(GarminMapKeys.Traffic);
        }
        /** @inheritdoc */
        onAfterMapRender() {
            this.showPipe = this.garminTrafficModule.show.pipe(this.trafficModule.show);
            this.alertLevelPipe = this.garminTrafficModule.alertLevelMode.pipe(this.trafficModule.alertLevelVisibility, mode => { var _a; return (_a = MapTrafficController.ALERT_LEVEL_VIS_MAP[mode]) !== null && _a !== void 0 ? _a : msfsSdk.MapTrafficAlertLevelVisibility.All; });
            this.isAltitudeRelativePipe = this.garminTrafficModule.isAltitudeRelative.pipe(this.trafficModule.isAltitudeRelative);
            this.altitudeModeSub = this.garminTrafficModule.altitudeRestrictionMode.sub(mode => {
                if (mode === exports.MapTrafficAltitudeRestrictionMode.Unrestricted || mode === exports.MapTrafficAltitudeRestrictionMode.Above) {
                    this.trafficModule.altitudeRestrictionAbove.set(MapTrafficController.ALTITUDE_RESTRICTION_UNRES);
                }
                else {
                    this.trafficModule.altitudeRestrictionAbove.set(MapTrafficController.ALTITUDE_RESTRICTION_NORMAL);
                }
                if (mode === exports.MapTrafficAltitudeRestrictionMode.Unrestricted || mode === exports.MapTrafficAltitudeRestrictionMode.Below) {
                    this.trafficModule.altitudeRestrictionBelow.set(MapTrafficController.ALTITUDE_RESTRICTION_UNRES);
                }
                else {
                    this.trafficModule.altitudeRestrictionBelow.set(MapTrafficController.ALTITUDE_RESTRICTION_NORMAL);
                }
            }, true);
            if (this.useOuterRangeAsOffScale && this.rangeModule !== undefined) {
                this.offScaleRange = msfsSdk.MappedSubject.create(([rangeArray, outerRangeIndex]) => {
                    var _a;
                    return (_a = rangeArray[outerRangeIndex]) !== null && _a !== void 0 ? _a : MapTrafficController.NAN_RANGE;
                }, this.rangeModule.nominalRanges, this.garminTrafficModule.outerRangeIndex);
                this.offScaleRange.pipe(this.trafficModule.offScaleRange);
            }
        }
        /** @inheritdoc */
        onMapDestroyed() {
            this.destroy();
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c, _d, _e;
            super.destroy();
            (_a = this.showPipe) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.alertLevelPipe) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.isAltitudeRelativePipe) === null || _c === void 0 ? void 0 : _c.destroy();
            (_d = this.altitudeModeSub) === null || _d === void 0 ? void 0 : _d.destroy();
            (_e = this.offScaleRange) === null || _e === void 0 ? void 0 : _e.destroy();
        }
    }
    MapTrafficController.NAN_RANGE = msfsSdk.UnitType.NMILE.createNumber(NaN);
    MapTrafficController.ALERT_LEVEL_VIS_MAP = {
        [exports.MapTrafficAlertLevelMode.All]: msfsSdk.MapTrafficAlertLevelVisibility.All,
        [exports.MapTrafficAlertLevelMode.Advisories]: msfsSdk.BitFlags.not(msfsSdk.MapTrafficAlertLevelVisibility.All, msfsSdk.MapTrafficAlertLevelVisibility.Other),
        [exports.MapTrafficAlertLevelMode.TA_RA]: msfsSdk.MapTrafficAlertLevelVisibility.TrafficAdvisory | msfsSdk.MapTrafficAlertLevelVisibility.ResolutionAdvisory,
        [exports.MapTrafficAlertLevelMode.RA]: msfsSdk.MapTrafficAlertLevelVisibility.ResolutionAdvisory
    };
    MapTrafficController.ALTITUDE_RESTRICTION_UNRES = msfsSdk.UnitType.FOOT.createNumber(9900);
    MapTrafficController.ALTITUDE_RESTRICTION_NORMAL = msfsSdk.UnitType.FOOT.createNumber(2700);

    /**
     * Airport size.
     */
    exports.AirportSize = void 0;
    (function (AirportSize) {
        AirportSize["Large"] = "Large";
        AirportSize["Medium"] = "Medium";
        AirportSize["Small"] = "Small";
    })(exports.AirportSize || (exports.AirportSize = {}));
    /**
     * A waypoint associated with an airport.
     */
    class AirportWaypoint extends msfsSdk.BasicFacilityWaypoint {
        /**
         * Constructor.
         * @param airport The airport associated with this waypoint.
         * @param bus The event bus.
         */
        constructor(airport, bus) {
            super(airport, bus);
            this.longestRunway = AirportWaypoint.getLongestRunway(airport);
            this.size = AirportWaypoint.getAirportSize(airport, this.longestRunway);
        }
        /**
         * Gets the longest runway at an airport.
         * @param airport An airport.
         * @returns The longest runway at the specified airport, or `null` if the airport has no runways.
         */
        static getLongestRunway(airport) {
            if (airport.runways.length === 0) {
                return null;
            }
            return airport.runways.reduce((a, b) => a.length > b.length ? a : b);
        }
        /**
         * Gets the size of an airport.
         * @param airport An airport.
         * @param longestRunway The longest runway at the airport.
         * @returns The size of the airport.
         */
        static getAirportSize(airport, longestRunway) {
            if (!longestRunway) {
                return exports.AirportSize.Small;
            }
            const longestRwyLengthFeet = msfsSdk.UnitType.METER.convertTo(longestRunway.length, msfsSdk.UnitType.FOOT);
            return longestRwyLengthFeet >= 8100 ? exports.AirportSize.Large
                : (longestRwyLengthFeet >= 5000 || airport.towered) ? exports.AirportSize.Medium
                    : exports.AirportSize.Small;
        }
    }

    /**
     * Controls the visibility of map waypoint symbols.
     */
    class MapWaypointsVisController extends msfsSdk.MapSystemController {
        /**
         * Constructor.
         * @param context This controller's map context.
         * @param settingManager A setting manager containing the user settings controlling waypoint visibility.
         * @param options Options with which to configure the controller.
         */
        constructor(context, settingManager, options) {
            var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
            super(context);
            this.waypointsModule = this.context.model.getModule(msfsSdk.MapSystemKeys.NearestWaypoints);
            this.controllers = [];
            const airportLargeShow = settingManager.tryGetSetting('mapAirportLargeShow');
            const airportLargeRangeIndex = (_a = settingManager.tryGetSetting('mapAirportLargeRangeIndex')) !== null && _a !== void 0 ? _a : msfsSdk.Subject.create(Number.MAX_SAFE_INTEGER);
            if (airportLargeShow) {
                this.controllers.push(new MapSymbolVisController(context, airportLargeShow, airportLargeRangeIndex, (_b = options === null || options === void 0 ? void 0 : options.airportLargeMaxDeclutterMode) !== null && _b !== void 0 ? _b : exports.MapDeclutterMode.Level2, this.waypointsModule.airportShow[exports.AirportSize.Large]));
            }
            const airportMediumShow = settingManager.tryGetSetting('mapAirportMediumShow');
            const airportMediumRangeIndex = (_c = settingManager.tryGetSetting('mapAirportMediumRangeIndex')) !== null && _c !== void 0 ? _c : msfsSdk.Subject.create(Number.MAX_SAFE_INTEGER);
            if (airportMediumShow) {
                this.controllers.push(new MapSymbolVisController(context, airportMediumShow, airportMediumRangeIndex, (_d = options === null || options === void 0 ? void 0 : options.airportMediumMaxDeclutterMode) !== null && _d !== void 0 ? _d : exports.MapDeclutterMode.Level2, this.waypointsModule.airportShow[exports.AirportSize.Medium]));
            }
            const airportSmallShow = settingManager.tryGetSetting('mapAirportSmallShow');
            const airportSmallRangeIndex = (_e = settingManager.tryGetSetting('mapAirportSmallRangeIndex')) !== null && _e !== void 0 ? _e : msfsSdk.Subject.create(Number.MAX_SAFE_INTEGER);
            if (airportSmallShow) {
                this.controllers.push(new MapSymbolVisController(context, airportSmallShow, airportSmallRangeIndex, (_f = options === null || options === void 0 ? void 0 : options.airportSmallMaxDeclutterMode) !== null && _f !== void 0 ? _f : exports.MapDeclutterMode.Level2, this.waypointsModule.airportShow[exports.AirportSize.Small]));
            }
            const vorShow = settingManager.tryGetSetting('mapVorShow');
            const vorRangeIndex = (_g = settingManager.tryGetSetting('mapVorRangeIndex')) !== null && _g !== void 0 ? _g : msfsSdk.Subject.create(Number.MAX_SAFE_INTEGER);
            if (vorShow) {
                this.controllers.push(new MapSymbolVisController(context, vorShow, vorRangeIndex, (_h = options === null || options === void 0 ? void 0 : options.vorMaxDeclutterMode) !== null && _h !== void 0 ? _h : exports.MapDeclutterMode.Level3, this.waypointsModule.vorShow));
            }
            const ndbShow = settingManager.tryGetSetting('mapNdbShow');
            const ndbRangeIndex = (_j = settingManager.tryGetSetting('mapNdbRangeIndex')) !== null && _j !== void 0 ? _j : msfsSdk.Subject.create(Number.MAX_SAFE_INTEGER);
            if (ndbShow) {
                this.controllers.push(new MapSymbolVisController(context, ndbShow, ndbRangeIndex, (_k = options === null || options === void 0 ? void 0 : options.ndbMaxDeclutterMode) !== null && _k !== void 0 ? _k : exports.MapDeclutterMode.Level3, this.waypointsModule.ndbShow));
            }
            const intersectionShow = settingManager.tryGetSetting('mapIntersectionShow');
            const intersectionRangeIndex = (_l = settingManager.tryGetSetting('mapIntersectionRangeIndex')) !== null && _l !== void 0 ? _l : msfsSdk.Subject.create(Number.MAX_SAFE_INTEGER);
            if (intersectionShow) {
                this.controllers.push(new MapSymbolVisController(context, intersectionShow, intersectionRangeIndex, (_m = options === null || options === void 0 ? void 0 : options.intMaxDeclutterMode) !== null && _m !== void 0 ? _m : exports.MapDeclutterMode.Level3, this.waypointsModule.intShow));
            }
            const userShow = settingManager.tryGetSetting('mapUserWaypointShow');
            const userRangeIndex = (_o = settingManager.tryGetSetting('mapUserWaypointRangeIndex')) !== null && _o !== void 0 ? _o : msfsSdk.Subject.create(Number.MAX_SAFE_INTEGER);
            if (userShow) {
                this.controllers.push(new MapSymbolVisController(context, userShow, userRangeIndex, (_p = options === null || options === void 0 ? void 0 : options.userMaxDeclutterMode) !== null && _p !== void 0 ? _p : exports.MapDeclutterMode.Level3, this.waypointsModule.userShow));
            }
        }
        /** @inheritdoc */
        onAfterMapRender() {
            this.controllers.forEach(controller => { controller.onAfterMapRender(); });
        }
        /** @inheritdoc */
        onMapDestroyed() {
            this.destroy();
        }
        /** @inheritdoc */
        destroy() {
            super.destroy();
            this.controllers.forEach(controller => { controller.destroy(); });
        }
    }

    /**
     * Controls the display of the wind vector based on user settings.
     *
     * The controller displays the wind vector if and only if user settings are set to show the vector and the map pointer
     * is not active.
     */
    class MapWindVectorController extends msfsSdk.MapSystemController {
        /**
         * Creates a new instance of MapWindVectorController.
         * @param context This controller's map context.
         * @param settingManager A setting manager containing the user settings controlling the display of the wind vector.
         */
        constructor(context, settingManager) {
            var _a, _b;
            super(context);
            this.windVectorModule = this.context.model.getModule(GarminMapKeys.WindVector);
            this.pointerModule = this.context.model.getModule(GarminMapKeys.Pointer);
            const showSetting = settingManager.tryGetSetting('mapWindVectorShow');
            if (showSetting) {
                this.show = msfsSdk.MappedSubject.create(([show, isPointerActive]) => {
                    return show && !isPointerActive;
                }, showSetting, (_b = (_a = this.pointerModule) === null || _a === void 0 ? void 0 : _a.isActive) !== null && _b !== void 0 ? _b : msfsSdk.Subject.create(false)).pause();
            }
        }
        /** @inheritdoc */
        onAfterMapRender() {
            var _a;
            (_a = this.show) === null || _a === void 0 ? void 0 : _a.resume().pipe(this.windVectorModule.show);
        }
        /** @inheritdoc */
        onMapDestroyed() {
            this.destroy();
        }
        /** @inheritdoc */
        destroy() {
            var _a;
            (_a = this.show) === null || _a === void 0 ? void 0 : _a.destroy();
            super.destroy();
        }
    }

    /* eslint-disable @typescript-eslint/no-non-null-assertion */
    /**
     * Controls the display of weather on the map based on options set by {@link MapNexradModule}.
     */
    class MapWxrController extends msfsSdk.MapSystemController {
        constructor() {
            super(...arguments);
            this.nexradModule = this.context.model.getModule(GarminMapKeys.Nexrad);
            this.weatherModule = this.context.model.getModule(msfsSdk.MapSystemKeys.Weather);
        }
        /** @inheritdoc */
        onAfterMapRender() {
            this.nexradColorsSub = this.nexradModule.colors.sub(colors => { this.weatherModule.weatherRadarColors.set(colors); }, false, true);
            this.nexradShowSub = this.nexradModule.showNexrad.sub(show => {
                if (show) {
                    this.weatherModule.weatherRadarMode.set(EWeatherRadar.TOPVIEW);
                    this.nexradColorsSub.resume(true);
                }
                else {
                    this.nexradColorsSub.pause();
                }
                this.weatherModule.isEnabled.set(show);
            }, true);
        }
        /** @inheritdoc */
        onMapDestroyed() {
            this.destroy();
        }
        /** @inheritdoc */
        destroy() {
            var _a;
            super.destroy();
            (_a = this.nexradShowSub) === null || _a === void 0 ? void 0 : _a.destroy();
        }
    }

    /**
     * Controls the range of a nearest waypoint map to keep a highlighted waypoint in view.
     */
    class NearestMapRTRController extends msfsSdk.MapSystemController {
        /**
         * Constructor.
         * @param context This controller's map context.
         * @param defaultNoTargetRangeIndex A subscribable which provides the default map range index to apply when
         * not targeting a waypoint, or `null` if no range index should be applied.
         */
        constructor(context, defaultNoTargetRangeIndex) {
            super(context);
            this.defaultNoTargetRangeIndex = defaultNoTargetRangeIndex;
            this.rangeModule = this.context.model.getModule(GarminMapKeys.Range);
            this.waypointHighlightModule = this.context.model.getModule(GarminMapKeys.WaypointHighlight);
            this.rangeControl = this.context[msfsSdk.MapSystemKeys.RangeControl];
            this.hasRangeControl = this.rangeControl !== undefined;
            this.rangeControlConsumer = {
                priority: MapResourcePriority.WAYPOINT_HIGHLIGHT,
                onAcquired: () => {
                    this.hasRangeControl = true;
                },
                onCeded: () => { this.hasRangeControl = false; }
            };
        }
        /** @inheritdoc */
        onAfterMapRender() {
            var _a;
            (_a = this.rangeControl) === null || _a === void 0 ? void 0 : _a.claim(this.rangeControlConsumer);
            this.waypointSub = this.waypointHighlightModule.waypoint.sub(this.trySetRangeForWaypoint.bind(this), true);
        }
        /**
         * Attempts to set the range of this controller's map so that the highlighted waypoint is in view. If there is no
         * highlighted waypoint or this controller does not have map range control privileges, this method does nothing.
         */
        trySetRangeForWaypoint() {
            if (!this.hasRangeControl) {
                return;
            }
            const waypoint = this.waypointHighlightModule.waypoint.get();
            if (waypoint === null) {
                if (this.defaultNoTargetRangeIndex !== null) {
                    this.setRangeIndex(this.defaultNoTargetRangeIndex.get());
                }
            }
            else {
                const distanceFromTarget = this.context.projection.getTarget().distance(waypoint.location.get());
                const ranges = this.rangeModule.nominalRanges.get();
                let index = ranges.findIndex(range => range.compare(distanceFromTarget, msfsSdk.UnitType.GA_RADIAN) >= 0);
                if (index === -1) {
                    index = ranges.length - 1;
                }
                this.setRangeIndex(index);
            }
        }
        /**
         * Sets the range index of this controller's map.
         * @param index The index to set.
         */
        setRangeIndex(index) {
            this.context.getController(GarminMapKeys.Range).setRangeIndex(index, true);
        }
        /** @inheritdoc */
        onMapDestroyed() {
            this.destroy();
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b;
            super.destroy();
            (_a = this.rangeControl) === null || _a === void 0 ? void 0 : _a.forfeit(this.rangeControlConsumer);
            (_b = this.waypointSub) === null || _b === void 0 ? void 0 : _b.destroy();
        }
    }

    /**
     * Controls traffic map range.
     */
    class TrafficMapRangeController extends msfsSdk.MapSystemController {
        /**
         * Constructor.
         * @param context This controller's map context.
         * @param nauticalRangeArray The map range array this controller sets for nautical distance mode. If not defined,
         * this controller will not change the range array when entering nautical distance mode.
         * @param metricRangeArray The map range array this controller sets for metric distance mode. If not defined, this
         * controller will not change the range array when entering metric distance mode.
         * @param settingManager A setting manager containing the map range index setting. If not defined, map range will
         * be set directly through the map model.
         * @param useSetting A subscribable which provides whether to control map range via the user setting. If not defined,
         * map range will always be controlled via the user setting. Ignored if `settingManager` is undefined.
         */
        constructor(context, nauticalRangeArray, metricRangeArray, settingManager, useSetting) {
            var _a, _b;
            super(context);
            this.nauticalRangeArray = nauticalRangeArray;
            this.metricRangeArray = metricRangeArray;
            this.useSetting = useSetting;
            this.rangeModule = this.context.model.getModule(GarminMapKeys.Range);
            this.trafficModule = this.context.model.getModule(GarminMapKeys.Traffic);
            this.distanceUnitsMode = (_b = (_a = this.context.model.getModule(GarminMapKeys.Units)) === null || _a === void 0 ? void 0 : _a.distanceMode) !== null && _b !== void 0 ? _b : msfsSdk.Subject.create(exports.UnitsDistanceSettingMode.Nautical);
            this.rangeSetting = settingManager === null || settingManager === void 0 ? void 0 : settingManager.getSetting('mapRangeIndex');
        }
        /** @inheritdoc */
        onAfterMapRender() {
            var _a;
            this.distanceModeSub = this.distanceUnitsMode.sub(mode => {
                if (mode === exports.UnitsDistanceSettingMode.Nautical) {
                    if (this.nauticalRangeArray !== undefined) {
                        this.rangeModule.nominalRanges.set(this.nauticalRangeArray);
                    }
                }
                else {
                    if (this.metricRangeArray) {
                        this.rangeModule.nominalRanges.set(this.metricRangeArray);
                    }
                }
            }, true);
            this.indexSub = this.rangeModule.nominalRangeIndex.sub(index => {
                this.trafficModule.outerRangeIndex.set(index);
                const ranges = this.rangeModule.nominalRanges.get();
                const outerRange = ranges[index];
                let innerRangeIndex = index;
                while (--innerRangeIndex >= 0) {
                    if (ranges[innerRangeIndex].compare(outerRange) < 0) {
                        break;
                    }
                }
                this.trafficModule.innerRangeIndex.set(innerRangeIndex);
            }, true);
            this.settingSub = (_a = this.rangeSetting) === null || _a === void 0 ? void 0 : _a.sub(setting => {
                this.rangeModule.setNominalRangeIndex(setting);
            }, false, true);
            if (this.settingSub) {
                if (this.useSetting) {
                    this.useSettingSub = this.useSetting.sub(useSetting => {
                        if (useSetting) {
                            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                            this.settingSub.resume(true);
                        }
                        else {
                            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                            this.settingSub.pause();
                        }
                    }, true);
                }
                else {
                    this.settingSub.resume(true);
                }
            }
        }
        /**
         * Sets the map range index. If the index is out of bounds, it will be clamped before being set.
         * @param index The index to set.
         * @returns The index that was set.
         */
        setRangeIndex(index) {
            var _a, _b;
            index = msfsSdk.MathUtils.clamp(index, 0, this.rangeModule.nominalRanges.get().length - 1);
            if (this.rangeSetting !== undefined && ((_b = (_a = this.useSetting) === null || _a === void 0 ? void 0 : _a.get()) !== null && _b !== void 0 ? _b : true)) {
                this.rangeSetting.value = index;
            }
            else {
                this.rangeModule.setNominalRangeIndex(index);
            }
            return index;
        }
        /**
         * Changes the range index by a given number of steps. Each step changes the range index to the next or previous
         * index that holds a range different from the current range.
         * @param delta The number of steps by which to change the range.
         * @returns The final index that was set.
         */
        changeRangeIndex(delta) {
            const currentIndex = this.rangeModule.nominalRangeIndex.get();
            if (delta === 0) {
                return currentIndex;
            }
            const ranges = this.rangeModule.nominalRanges.get();
            const currentRange = ranges[currentIndex];
            let index = currentIndex;
            let stepsToGo = Math.abs(delta);
            if (delta > 0) {
                while (++index < ranges.length) {
                    if (!ranges[index].equals(currentRange)) {
                        stepsToGo--;
                    }
                    if (stepsToGo === 0) {
                        break;
                    }
                }
            }
            else {
                while (--index >= 0) {
                    if (!ranges[index].equals(currentRange)) {
                        stepsToGo--;
                    }
                    if (stepsToGo === 0) {
                        break;
                    }
                }
            }
            index = msfsSdk.MathUtils.clamp(index, 0, ranges.length - 1);
            if (currentRange.compare(ranges[index]) === 0) {
                return currentIndex;
            }
            return this.setRangeIndex(index);
        }
        /** @inheritdoc */
        onMapDestroyed() {
            this.destroy();
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c, _d;
            super.destroy();
            (_a = this.distanceModeSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.indexSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.settingSub) === null || _c === void 0 ? void 0 : _c.destroy();
            (_d = this.useSettingSub) === null || _d === void 0 ? void 0 : _d.destroy();
        }
    }

    /**
     * Controls the highlighted waypoint of a waypoint map.
     */
    class WaypointMapHighlightController extends msfsSdk.MapSystemController {
        constructor() {
            super(...arguments);
            this.waypointSelectModule = this.context.model.getModule(GarminMapKeys.WaypointSelection);
            this.waypointHighlightModule = this.context.model.getModule(GarminMapKeys.WaypointHighlight);
        }
        /** @inheritdoc */
        onAfterMapRender() {
            this.waypointPipe = this.waypointSelectModule.waypoint.pipe(this.waypointHighlightModule.waypoint);
        }
        /** @inheritdoc */
        onMapDestroyed() {
            this.destroy();
        }
        /** @inheritdoc */
        destroy() {
            var _a;
            (_a = this.waypointPipe) === null || _a === void 0 ? void 0 : _a.destroy();
            super.destroy();
        }
    }

    /**
     * FMS positioning system data modes.
     */
    exports.FmsPositionMode = void 0;
    (function (FmsPositionMode) {
        /** No position data is available. */
        FmsPositionMode["None"] = "None";
        /** Position data is sourced from GPS. */
        FmsPositionMode["Gps"] = "Gps";
        /** Position data is sourced from DME/DME. */
        FmsPositionMode["Dme"] = "Dme";
        /** Position data is sourced from HNS (hybrid inertial navigation). */
        FmsPositionMode["Hns"] = "Hns";
        /** Position data is sourced from dead reckoning. */
        FmsPositionMode["DeadReckoning"] = "DeadReckoning";
        /** Position data is sourced from dead reckoning and more than 20 minutes have elapsed since the last accurate position fix. */
        FmsPositionMode["DeadReckoningExpired"] = "DeadReckoningExpired";
    })(exports.FmsPositionMode || (exports.FmsPositionMode = {}));
    /**
     * A Garmin FMS geo-positioning system.
     */
    class FmsPositionSystem extends msfsSdk.BasicAvionicsSystem {
        /**
         * Creates an instance of an FMS geo-positioning system.
         * @param index The index of the FMS geo-positioning system.
         * @param bus An instance of the event bus.
         * @param gpsReceiverIndex The index of the GPS receiver used by this system. No GPS data will be used if the index
         * is negative.
         * @param adcIndex The index of the ADC used by this system in dead reckoning mode to obtain airspeed data.
         * @param ahrsIndex The index of the AHRS used by this system in dead reckoning mode to obtain heading data.
         * @param hnsIndex The index of the HNS used by this system. No HNS data will be used if the index is negative.
         * Defaults to `-1`.
         * @param dmeIndex The index of the DME/DME navigation system used by this system. No DME/DME data will be used if
         * the index is negative. Defaults to `-1`.
         * @param powerSource The {@link ElectricalEvents} topic or electricity logic element to which to connect the
         * system's power.
         */
        constructor(index, bus, gpsReceiverIndex, adcIndex, ahrsIndex, hnsIndex, dmeIndex, powerSource) {
            super(index, bus, `fms_pos_state_${index}`);
            this.initializationTime = 0;
            this.gnssDataSourceTopicMap = {
                [`fms_pos_gps-position_${this.index}`]: 'gps-position',
                [`fms_pos_ground_speed_${this.index}`]: 'ground_speed',
                [`fms_pos_track_deg_true_${this.index}`]: 'track_deg_true',
                [`fms_pos_track_deg_magnetic_${this.index}`]: 'track_deg_magnetic'
            };
            this.modeTopic = `fms_pos_mode_${this.index}`;
            this.gpsIndexTopic = `fms_pos_gps_index_${this.index}`;
            this.hnsIndexTopic = `fms_pos_hns_index_${this.index}`;
            this.dataSourceSubscriber = this.bus.getSubscriber();
            this.dataSubs = [];
            this.simTime = msfsSdk.ConsumerSubject.create(this.bus.getSubscriber().on('simTime'), 0);
            this.gpsStateSource = msfsSdk.ConsumerSubject.create(null, msfsSdk.GPSSystemState.Searching);
            this.gpsState = msfsSdk.Subject.create(msfsSdk.GPSSystemState.Searching);
            this.mode = exports.FmsPositionMode.None;
            this.lastFixTime = undefined;
            this.gpsIndex = msfsSdk.SubscribableUtils.toSubscribable(gpsReceiverIndex, true);
            this.hnsIndex = msfsSdk.SubscribableUtils.toSubscribable(-1, true);
            this.dmeIndex = msfsSdk.SubscribableUtils.toSubscribable(-1, true);
            this.adcIndex = msfsSdk.SubscribableUtils.toSubscribable(adcIndex, true);
            this.ahrsIndex = msfsSdk.SubscribableUtils.toSubscribable(ahrsIndex, true);
            this.publisher.pub(this.modeTopic, this.mode, false, true);
            if (powerSource !== undefined) {
                this.connectToPower(powerSource);
            }
            this.startDataPublish();
        }
        /**
         * Starts publishing data on the event bus.
         */
        startDataPublish() {
            for (const topic of Object.keys(this.gnssDataSourceTopicMap)) {
                if (this.bus.getTopicSubscriberCount(topic) > 0) {
                    this.onGnssTopicSubscribed(topic);
                }
            }
            this.bus.getSubscriber().on('event_bus_topic_first_sub').handle(topic => {
                if (topic in this.gnssDataSourceTopicMap) {
                    this.onGnssTopicSubscribed(topic);
                }
            });
            const paused = this.state === msfsSdk.AvionicsSystemState.Failed || this.state === msfsSdk.AvionicsSystemState.Off;
            this.dataSubs.push(this.gpsIndex.sub(index => this.publisher.pub(this.gpsIndexTopic, index, false, true), !paused, paused));
            this.dataSubs.push(this.hnsIndex.sub(index => this.publisher.pub(this.hnsIndexTopic, index, false, true), !paused, paused));
            const gpsStatePipe = this.gpsStateSource.pipe(this.gpsState, true);
            this.gpsIndex.sub(index => {
                if (index < 0) {
                    this.gpsStateSource.setConsumer(null);
                    gpsStatePipe.pause();
                    this.gpsState.set(msfsSdk.GPSSystemState.Searching);
                }
                else {
                    this.gpsStateSource.setConsumer(this.dataSourceSubscriber.on(`gps_rec_gps_system_state_changed_${index}`));
                    gpsStatePipe.resume(true);
                }
            }, true);
        }
        /**
         * Responds to when someone first subscribes to one of this system's GNSS-sourced data topics on the event bus.
         * @param topic The topic that was subscribed to.
         */
        onGnssTopicSubscribed(topic) {
            const paused = this.state === msfsSdk.AvionicsSystemState.Failed || this.state === msfsSdk.AvionicsSystemState.Off;
            this.dataSubs.push(this.dataSourceSubscriber.on(this.gnssDataSourceTopicMap[topic]).handle(val => {
                this.publisher.pub(topic, val, false, true);
            }, paused));
        }
        /** @inheritdoc */
        onStateChanged(previousState, currentState) {
            if (currentState === msfsSdk.AvionicsSystemState.Failed || currentState === msfsSdk.AvionicsSystemState.Off) {
                for (const sub of this.dataSubs) {
                    sub.pause();
                }
                this.setMode(exports.FmsPositionMode.None);
                this.lastFixTime = undefined;
            }
            else {
                for (const sub of this.dataSubs) {
                    sub.resume(true);
                }
            }
        }
        /** @inheritdoc */
        onUpdate() {
            super.onUpdate();
            if (this._state === msfsSdk.AvionicsSystemState.On || this._state === undefined) {
                this.updateMode();
            }
        }
        /**
         * Updates this system's data mode.
         */
        updateMode() {
            const gpsState = this.gpsState.get();
            if (gpsState === msfsSdk.GPSSystemState.SolutionAcquired || gpsState === msfsSdk.GPSSystemState.DiffSolutionAcquired) {
                this.setMode(exports.FmsPositionMode.Gps);
                this.lastFixTime = this.simTime.get();
            }
            else if (this.lastFixTime !== undefined) {
                if (this.simTime.get() - this.lastFixTime > FmsPositionSystem.DEAD_RECKONING_EXPIRE_TIME) {
                    this.setMode(exports.FmsPositionMode.DeadReckoningExpired);
                }
                else {
                    this.setMode(exports.FmsPositionMode.DeadReckoning);
                }
            }
            else {
                this.setMode(exports.FmsPositionMode.None);
            }
        }
        /**
         * Sets this system's data mode, and publishes the new value to the event bus if it differs from the current value.
         * @param mode The new data mode.
         */
        setMode(mode) {
            if (this.mode === mode) {
                return;
            }
            this.mode = mode;
            this.publisher.pub(this.modeTopic, this.mode, false, true);
        }
    }
    FmsPositionSystem.DEAD_RECKONING_EXPIRE_TIME = msfsSdk.UnitType.MINUTE.convertTo(20, msfsSdk.UnitType.MILLISECOND);

    /**
     * Automatically selects the best FMS geo-positioning system from a set of candidates based on the accuracy of their
     * provided positioning data.
     */
    class FmsPositionSystemSelector {
        /**
         * Constructor.
         * @param bus The event bus.
         * @param enabledSystemIndexes The indexes of the FMS geo-positioning systems from which to select.
         * @param preferredSystemIndex The index of this selector's preferred system, or `-1` if there is no such system
         * This selector is guaranteed to select the preferred system if its state is at least as desirable as the state of
         * all other systems from which to select.
         */
        constructor(bus, enabledSystemIndexes, preferredSystemIndex) {
            this.bus = bus;
            this._selectedIndex = msfsSdk.Subject.create(-1);
            /** The index of the selected FMS geo-positioning system, or `-1` if no system could be selected. */
            this.selectedIndex = this._selectedIndex;
            this._selectedFmsPosMode = msfsSdk.Subject.create(exports.FmsPositionMode.None);
            /** The positioning mode of the selected FMS geo-positioning system. */
            this.selectedFmsPosMode = this._selectedFmsPosMode;
            this.fmsPosModes = new Map();
            this.isAlive = true;
            this.isInit = false;
            this.enabledSystemIndexes = 'isSubscribableSet' in enabledSystemIndexes ? enabledSystemIndexes : msfsSdk.SetSubject.create(enabledSystemIndexes);
            this.preferredSystemIndex = msfsSdk.SubscribableUtils.toSubscribable(preferredSystemIndex !== null && preferredSystemIndex !== void 0 ? preferredSystemIndex : -1, true);
            this._selectedIndex.sub(index => {
                var _a, _b;
                (_a = this.fmsPosModePipe) === null || _a === void 0 ? void 0 : _a.destroy();
                if (index < 1) {
                    this.fmsPosModePipe = undefined;
                }
                else {
                    this.fmsPosModePipe = (_b = this.fmsPosModes.get(index)) === null || _b === void 0 ? void 0 : _b.pipe(this._selectedFmsPosMode);
                }
            });
        }
        /**
         * Initializes this selector. Once initialized, this selector will automatically select the FMS geo-positioning system
         * that currently provides the most accurate position data.
         * @throws Error if this selector has been destroyed.
         */
        init() {
            if (!this.isAlive) {
                throw new Error('FmsPositionSystemSelector: cannot initialize a dead selector');
            }
            if (this.isInit) {
                return;
            }
            this.isInit = true;
            const sub = this.bus.getSubscriber();
            const selectIndex = this.selectIndex.bind(this);
            this.enabledSystemIndexesSub = this.enabledSystemIndexes.sub((set, type, key) => {
                var _a;
                (_a = this.fmsPosModes.get(key)) === null || _a === void 0 ? void 0 : _a.destroy();
                if (type === msfsSdk.SubscribableSetEventType.Added) {
                    const fmsPosState = msfsSdk.ConsumerSubject.create(sub.on(`fms_pos_mode_${key}`), exports.FmsPositionMode.None);
                    this.fmsPosModes.set(key, fmsPosState);
                    fmsPosState.sub(selectIndex);
                }
                else {
                    this.fmsPosModes.delete(key);
                }
                selectIndex();
            }, true);
            this.preferredSystemIndexSub = this.preferredSystemIndex.sub(selectIndex);
        }
        /**
         * Selects the index of the FMS geo-positioning system with the most desirable state.
         */
        selectIndex() {
            var _a, _b;
            if (this.fmsPosModes.size === 0) {
                this._selectedIndex.set(-1);
                return;
            }
            if (this.fmsPosModes.size === 1) {
                this._selectedIndex.set(this.fmsPosModes.keys().next().value);
                return;
            }
            let bestIndex = this._selectedIndex.get();
            let bestState = (_a = this.fmsPosModes.get(bestIndex)) === null || _a === void 0 ? void 0 : _a.get();
            for (const index of this.fmsPosModes.keys()) {
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                const state = this.fmsPosModes.get(index).get();
                if (bestIndex < 0 || !bestState || FmsPositionSystemSelector.compareFmsPosMode(state, bestState) < 0) {
                    bestIndex = index;
                    bestState = state;
                }
            }
            const preferredIndex = this.preferredSystemIndex.get();
            if (preferredIndex >= 0) {
                const preferredIndexState = (_b = this.fmsPosModes.get(preferredIndex)) === null || _b === void 0 ? void 0 : _b.get();
                if (preferredIndexState !== undefined && FmsPositionSystemSelector.compareFmsPosMode(preferredIndexState, bestState) <= 0) {
                    bestIndex = preferredIndex;
                }
            }
            this._selectedIndex.set(bestIndex);
        }
        /**
         * Destroys this manager.
         */
        destroy() {
            var _a, _b;
            this.isAlive = false;
            for (const state of this.fmsPosModes.values()) {
                state.destroy();
            }
            (_a = this.enabledSystemIndexesSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.preferredSystemIndexSub) === null || _b === void 0 ? void 0 : _b.destroy();
        }
        /**
         * Compares two FMS geo-positioning system modes and returns a number whose sign indicates which one is more
         * desirable.
         * @param a The first mode to compare.
         * @param b The second mode to compare.
         * @returns A negative number of mode `a` is more desirable than `b`, a positive number if mode `b` is more desirable
         * than `a`, or zero if the two modes are equally desirable.
         */
        static compareFmsPosMode(a, b) {
            return FmsPositionSystemSelector.FMS_POS_MODE_PRIORITIES[a] - FmsPositionSystemSelector.FMS_POS_MODE_PRIORITIES[b];
        }
    }
    FmsPositionSystemSelector.FMS_POS_MODE_PRIORITIES = {
        [exports.FmsPositionMode.Gps]: 0,
        [exports.FmsPositionMode.Hns]: 0,
        [exports.FmsPositionMode.Dme]: 0,
        [exports.FmsPositionMode.DeadReckoning]: 1,
        [exports.FmsPositionMode.DeadReckoningExpired]: 2,
        [exports.FmsPositionMode.None]: 3
    };

    /**
     * A Garmin-specific implementation of {@link FacilityWaypointCache}.
     */
    class GarminFacilityWaypointCache {
        /**
         * Constructor.
         * @param bus The event bus.
         * @param size The maximum size of this cache.
         */
        constructor(bus, size) {
            this.bus = bus;
            this.size = size;
            this.cache = new Map();
        }
        /**
         * Gets a waypoint from the cache for a specific facility. If one does not exist, a new waypoint will be created.
         * @param facility The facility for which to get a waypoint.
         * @returns A waypoint.
         */
        get(facility) {
            const key = GarminFacilityWaypointCache.getFacilityKey(facility);
            let existing = this.cache.get(key);
            if (!existing) {
                if (msfsSdk.ICAO.getFacilityType(facility.icao) === msfsSdk.FacilityType.Airport) {
                    existing = new AirportWaypoint(facility, this.bus);
                }
                else {
                    existing = new msfsSdk.BasicFacilityWaypoint(facility, this.bus);
                }
                this.addToCache(key, existing);
            }
            return existing;
        }
        /**
         * Adds a waypoint to this cache. If the size of the cache is greater than the maximum after the new waypoint is
         * added, a waypoint will be removed from the cache in FIFO order.
         * @param key The key of the waypoint to add.
         * @param waypoint The waypoint to add.
         */
        addToCache(key, waypoint) {
            this.cache.set(key, waypoint);
            if (this.cache.size > this.size) {
                this.cache.delete(this.cache.keys().next().value);
            }
        }
        /**
         * Gets a FacilityWaypointCache instance.
         * @param bus The event bus.
         * @returns A FacilityWaypointCache instance.
         */
        static getCache(bus) {
            var _a;
            return (_a = GarminFacilityWaypointCache.INSTANCE) !== null && _a !== void 0 ? _a : (GarminFacilityWaypointCache.INSTANCE = new GarminFacilityWaypointCache(bus, 1000));
        }
        /**
         * Gets the cache key for a facility.
         * @param facility A facility.
         * @returns The cache key for the specified facility.
         */
        static getFacilityKey(facility) {
            if (msfsSdk.FacilityUtils.isFacilityType(facility, msfsSdk.FacilityType.Intersection) && msfsSdk.ICAO.getFacilityType(facility.icao) !== msfsSdk.FacilityType.Intersection) {
                return `mismatch.${facility.icao}`;
            }
            return facility.icao;
        }
    }

    /**
     * Active navigation sources for Garmin avionics.
     */
    exports.ActiveNavSource = void 0;
    (function (ActiveNavSource) {
        ActiveNavSource[ActiveNavSource["Nav1"] = 0] = "Nav1";
        ActiveNavSource[ActiveNavSource["Nav2"] = 1] = "Nav2";
        ActiveNavSource[ActiveNavSource["Gps1"] = 2] = "Gps1";
        ActiveNavSource[ActiveNavSource["Gps2"] = 3] = "Gps2";
    })(exports.ActiveNavSource || (exports.ActiveNavSource = {}));
    /**
     * Sim var names for Garmin navigation data.
     */
    exports.GarminNavVars = void 0;
    (function (GarminNavVars) {
        /** The active navigation source for the pilot. */
        GarminNavVars["ActiveNavSource1"] = "L:WTGarmin_Nav_ActiveNavSource:1";
        /** The active navigation source for the copilot. */
        GarminNavVars["ActiveNavSource2"] = "L:WTGarmin_Nav_ActiveNavSource:2";
    })(exports.GarminNavVars || (exports.GarminNavVars = {}));
    /**
     * A publisher for Garmin navigation sim var events.
     */
    class GarminNavSimVarPublisher extends msfsSdk.SimVarPublisher {
        /**
         * Constructor.
         * @param bus The event bus to which to publish.
         */
        constructor(bus) {
            super(GarminNavSimVarPublisher.simvars, bus);
        }
    }
    GarminNavSimVarPublisher.simvars = new Map([
        ['active_nav_source_1', { name: exports.GarminNavVars.ActiveNavSource1, type: msfsSdk.SimVarValueType.Number }],
        ['active_nav_source_2', { name: exports.GarminNavVars.ActiveNavSource2, type: msfsSdk.SimVarValueType.Number }]
    ]);

    /**
     * Valid CDI scale labels for the LVar scale enum.
     */
    exports.CDIScaleLabel = void 0;
    (function (CDIScaleLabel) {
        CDIScaleLabel[CDIScaleLabel["Departure"] = 0] = "Departure";
        CDIScaleLabel[CDIScaleLabel["Terminal"] = 1] = "Terminal";
        CDIScaleLabel[CDIScaleLabel["TerminalDeparture"] = 2] = "TerminalDeparture";
        CDIScaleLabel[CDIScaleLabel["TerminalArrival"] = 3] = "TerminalArrival";
        CDIScaleLabel[CDIScaleLabel["Enroute"] = 4] = "Enroute";
        CDIScaleLabel[CDIScaleLabel["Oceanic"] = 5] = "Oceanic";
        CDIScaleLabel[CDIScaleLabel["LNav"] = 6] = "LNav";
        CDIScaleLabel[CDIScaleLabel["LNavPlusV"] = 7] = "LNavPlusV";
        CDIScaleLabel[CDIScaleLabel["Visual"] = 8] = "Visual";
        CDIScaleLabel[CDIScaleLabel["LNavVNav"] = 9] = "LNavVNav";
        CDIScaleLabel[CDIScaleLabel["LP"] = 10] = "LP";
        CDIScaleLabel[CDIScaleLabel["LPPlusV"] = 11] = "LPPlusV";
        CDIScaleLabel[CDIScaleLabel["LPV"] = 12] = "LPV";
        CDIScaleLabel[CDIScaleLabel["RNP"] = 13] = "RNP";
        CDIScaleLabel[CDIScaleLabel["Approach"] = 14] = "Approach";
        CDIScaleLabel[CDIScaleLabel["MissedApproach"] = 15] = "MissedApproach";
        CDIScaleLabel[CDIScaleLabel["VfrEnroute"] = 16] = "VfrEnroute";
        CDIScaleLabel[CDIScaleLabel["VfrTerminal"] = 17] = "VfrTerminal";
        CDIScaleLabel[CDIScaleLabel["VfrApproach"] = 18] = "VfrApproach";
    })(exports.CDIScaleLabel || (exports.CDIScaleLabel = {}));
    /**
     * Sim var names for Garmin LNAV-related data.
     */
    exports.GarminLNavDataVars = void 0;
    (function (GarminLNavDataVars) {
        /** The nominal desired track at the beginning of the flight plan leg following the currently tracked leg, in degrees true. */
        GarminLNavDataVars["NextDTKTrue"] = "L:WTGarmin_LNavData_Next_DTK_True";
        /** The nominal desired track at the beginning of the flight plan leg following the currently tracked leg, in degrees magnetic. */
        GarminLNavDataVars["NextDTKMagnetic"] = "L:WTGarmin_LNavData_Next_DTK_Mag";
        /** The current CDI scale label. */
        // eslint-disable-next-line @typescript-eslint/no-shadow
        GarminLNavDataVars["CDIScaleLabel"] = "L:WTGarmin_LNavData_CDI_Scale_Label";
        /** The nominal distance remaining to the egress transition of the currently tracked flight plan leg. */
        GarminLNavDataVars["EgressDistance"] = "L:WTGarmin_LNavData_Egress_Distance";
        /** The nominal distance remaining to the egress transition of the currently tracked flight plan leg. */
        GarminLNavDataVars["ToFrom"] = "L:WTGarmin_LNavData_ToFrom";
    })(exports.GarminLNavDataVars || (exports.GarminLNavDataVars = {}));
    /**
     * A publisher for Garmin LNAV-related data sim var events.
     */
    class LNavDataSimVarPublisher extends msfsSdk.SimVarPublisher {
        /**
         * Constructor.
         * @param bus The event bus to which to publish.
         */
        constructor(bus) {
            const defs = msfsSdk.ArrayUtils.flatMap([
                ['lnavdata_dtk_true', { name: msfsSdk.LNavDataVars.DTKTrue, type: msfsSdk.SimVarValueType.Degree }],
                ['lnavdata_dtk_mag', { name: msfsSdk.LNavDataVars.DTKMagnetic, type: msfsSdk.SimVarValueType.Degree }],
                ['lnavdata_xtk', { name: msfsSdk.LNavDataVars.XTK, type: msfsSdk.SimVarValueType.NM }],
                ['lnavdata_next_dtk_true', { name: exports.GarminLNavDataVars.NextDTKTrue, type: msfsSdk.SimVarValueType.Degree }],
                ['lnavdata_next_dtk_mag', { name: exports.GarminLNavDataVars.NextDTKMagnetic, type: msfsSdk.SimVarValueType.Degree }],
                ['lnavdata_cdi_scale', { name: msfsSdk.LNavDataVars.CDIScale, type: msfsSdk.SimVarValueType.NM }],
                ['lnavdata_cdi_scale_label', { name: exports.GarminLNavDataVars.CDIScaleLabel, type: msfsSdk.SimVarValueType.Number }],
                ['lnavdata_waypoint_bearing_true', { name: msfsSdk.LNavDataVars.WaypointBearingTrue, type: msfsSdk.SimVarValueType.Degree }],
                ['lnavdata_waypoint_bearing_mag', { name: msfsSdk.LNavDataVars.WaypointBearingMagnetic, type: msfsSdk.SimVarValueType.Degree }],
                ['lnavdata_waypoint_distance', { name: msfsSdk.LNavDataVars.WaypointDistance, type: msfsSdk.SimVarValueType.NM }],
                ['lnavdata_destination_distance', { name: msfsSdk.LNavDataVars.DestinationDistance, type: msfsSdk.SimVarValueType.NM }],
                ['lnavdata_egress_distance', { name: exports.GarminLNavDataVars.EgressDistance, type: msfsSdk.SimVarValueType.NM }],
                ['lnavdata_tofrom', { name: exports.GarminLNavDataVars.ToFrom, type: msfsSdk.SimVarValueType.Number }],
            ], pair => {
                const [topic, entry] = pair;
                const indexedEntry = {
                    name: `${entry.name}:#index#`,
                    type: entry.type,
                    indexed: true,
                    defaultIndex: null
                };
                return [
                    pair,
                    [topic, indexedEntry]
                ];
            });
            super(defs, bus);
        }
    }

    /**
     * A default implementation of {@link GpsIntegrityDataProvider}.
     */
    class DefaultGpsIntegrityDataProvider {
        /**
         * Constructor.
         * @param bus The event bus.
         * @param fmsPosIndex The index of the FMS geo-positioning system that is the source of this provider's data.
         */
        constructor(bus, fmsPosIndex) {
            this.bus = bus;
            this._fmsPosMode = msfsSdk.ConsumerSubject.create(null, exports.FmsPositionMode.None);
            /** @inheritdoc */
            this.fmsPosMode = this._fmsPosMode;
            this._isIntegritySufficient = msfsSdk.Subject.create(false);
            /** @inheritdoc */
            this.isIntegritySufficient = this._isIntegritySufficient;
            this.cdiScalingMode = msfsSdk.ConsumerSubject.create(null, exports.CDIScaleLabel.Enroute);
            this.isInit = false;
            this.isAlive = true;
            this.isPaused = false;
            this.fmsPosIndex = msfsSdk.SubscribableUtils.toSubscribable(fmsPosIndex, true);
        }
        /**
         * Initializes this data provider. Once initialized, this data provider will continuously update its data until
         * paused or destroyed.
         * @param paused Whether to initialize this data provider as paused. If `true`, this data provider will provide an
         * initial set of data but will not update the provided data until it is resumed. Defaults to `false`.
         * @throws Error if this data provider is dead.
         */
        init(paused = false) {
            if (!this.isAlive) {
                throw new Error('DefaultGpsIntegrityDataProvider: cannot initialize a dead provider');
            }
            if (this.isInit) {
                return;
            }
            this.isInit = true;
            this.isPaused = paused;
            const sub = this.bus.getSubscriber();
            this.fmsPosIndexSub = this.fmsPosIndex.sub(index => {
                this._fmsPosMode.setConsumer(sub.on(`fms_pos_mode_${index}`));
            }, true);
            this.cdiScalingMode.setConsumer(sub.on('lnavdata_cdi_scale_label'));
            const state = msfsSdk.MappedSubject.create(this._fmsPosMode, this.cdiScalingMode);
            state.pipe(this._isIntegritySufficient, ([fmsPosMode, cdiScalingMode]) => {
                switch (fmsPosMode) {
                    case exports.FmsPositionMode.DeadReckoning:
                        return cdiScalingMode === exports.CDIScaleLabel.Enroute || cdiScalingMode === exports.CDIScaleLabel.Oceanic;
                    case exports.FmsPositionMode.Gps:
                    case exports.FmsPositionMode.Dme:
                    case exports.FmsPositionMode.Hns:
                        return true;
                    default:
                        return false;
                }
            });
            if (paused) {
                this.pause();
            }
        }
        /**
         * Resumes this data provider. Once resumed, this data provider will continuously update its data until paused or
         * destroyed.
         * @throws Error if this data provider is dead.
         */
        resume() {
            if (!this.isAlive) {
                throw new Error('DefaultGpsIntegrityDataProvider: cannot resume a dead provider');
            }
            if (!this.isPaused) {
                return;
            }
            this.isPaused = false;
            this._fmsPosMode.resume();
            this.cdiScalingMode.resume();
        }
        /**
         * Pauses this data provider. Once paused, this data provider will not update its data until it is resumed.
         * @throws Error if this data provider is dead.
         */
        pause() {
            if (!this.isAlive) {
                throw new Error('DefaultGpsIntegrityDataProvider: cannot pause a dead provider');
            }
            if (this.isPaused) {
                return;
            }
            this._fmsPosMode.pause();
            this.cdiScalingMode.pause();
            this.isPaused = true;
        }
        /**
         * Destroys this data provider. Once destroyed, this data provider will no longer update its provided data, and can
         * no longer be paused or resumed.
         */
        destroy() {
            var _a;
            this.isAlive = false;
            this._fmsPosMode.destroy();
            this.cdiScalingMode.destroy();
            (_a = this.fmsPosIndexSub) === null || _a === void 0 ? void 0 : _a.destroy();
        }
    }

    /**
     * Automatically selects the best GPS receiver from a set of candidates based on the current states of all receivers.
     * Receivers that have computed a 3D position solution with differential corrections are favored over those that have
     * computed a 3D solution without corrections, and either of these are favored over those that have not computed any
     * position solution.
     */
    class GpsReceiverSelector {
        // eslint-disable-next-line jsdoc/require-jsdoc
        constructor(bus, candidateReceiverIndexes, arg3) {
            var _a;
            this.bus = bus;
            this._selectedIndex = msfsSdk.Subject.create(-1);
            this.selectedIndex = this._selectedIndex;
            this.receiverEntries = new Map();
            this.receiverOrder = [];
            this.receiverPriorityComparator = (a, b) => {
                var _a, _b;
                return ((_a = this.receiverPriorities.getValue(b)) !== null && _a !== void 0 ? _a : 0) - ((_b = this.receiverPriorities.getValue(a)) !== null && _b !== void 0 ? _b : 0);
            };
            this.needReselect = true;
            this.isAlive = true;
            this.isInit = false;
            this.candidateReceiverIndexes = 'isSubscribableSet' in candidateReceiverIndexes ? candidateReceiverIndexes : msfsSdk.SetSubject.create(candidateReceiverIndexes);
            let options;
            if (typeof arg3 === 'number' || msfsSdk.SubscribableUtils.isSubscribable(arg3)) {
                let receiverPriorities;
                if (typeof arg3 === 'number') {
                    receiverPriorities = [arg3];
                }
                else {
                    const map = receiverPriorities = msfsSdk.MapSubject.create();
                    this.preferredReceiverIndexSub = arg3.sub(index => {
                        map.clear();
                        map.setValue(index, 1);
                    }, true);
                }
                options = { receiverPriorities };
            }
            else {
                options = arg3;
            }
            if (options === null || options === void 0 ? void 0 : options.receiverPriorities) {
                if ('isSubscribableMap' in options.receiverPriorities) {
                    this.receiverPriorities = options.receiverPriorities;
                }
                else if (options.receiverPriorities instanceof Map) {
                    this.receiverPriorities = msfsSdk.MapSubject.create(options.receiverPriorities);
                }
                else {
                    this.receiverPriorities = msfsSdk.MapSubject.create(options.receiverPriorities.map((adcIndex, arrayIndex, array) => [adcIndex, array.length - arrayIndex]));
                }
            }
            else {
                this.receiverPriorities = msfsSdk.MapSubject.create();
            }
            this.desirabilityComparator = (_a = options === null || options === void 0 ? void 0 : options.desirabilityComparator) !== null && _a !== void 0 ? _a : GpsReceiverSelector.defaultDesirabilityComparator;
        }
        /**
         * Initializes this selector. Once initialized, this selector will automatically select the best GPS receiver among
         * its candidates.
         * @throws Error if this selector has been destroyed.
         */
        init() {
            if (!this.isAlive) {
                throw new Error('GpsReceiverSelector: cannot initialize a dead selector');
            }
            if (this.isInit) {
                return;
            }
            this.isInit = true;
            const sub = this.bus.getSubscriber();
            const scheduleReselect = () => { this.needReselect = true; };
            this.candidateReceiverIndexesSub = this.candidateReceiverIndexes.sub((set, type, key) => {
                const existing = this.receiverEntries.get(key);
                if (existing) {
                    for (const subject of existing.subjects) {
                        subject.destroy();
                    }
                }
                if (type === msfsSdk.SubscribableSetEventType.Added) {
                    const subjects = [
                        msfsSdk.ConsumerSubject.create(sub.on(`gps_rec_gps_system_state_changed_${key}`), msfsSdk.GPSSystemState.Acquiring),
                        msfsSdk.ConsumerSubject.create(sub.on(`gps_rec_gps_system_pdop_${key}`), -1),
                        msfsSdk.ConsumerSubject.create(sub.on(`gps_rec_gps_system_hdop_${key}`), -1),
                        msfsSdk.ConsumerSubject.create(sub.on(`gps_rec_gps_system_vdop_${key}`), -1)
                    ];
                    const entry = {
                        subjects,
                        state: msfsSdk.MappedSubject.create(...subjects)
                    };
                    this.receiverEntries.set(key, entry);
                    // When there is a change in a receiver state, we don't reselect immediately because the receiver could be in
                    // an intermediate transition state. Instead, we will schedule a reselect during the next update loop.
                    entry.state.sub(scheduleReselect);
                }
                else {
                    this.receiverEntries.delete(key);
                }
                this.needReselect = true;
            }, true);
            this.receiverPrioritiesSub = this.receiverPriorities.sub(scheduleReselect);
            this.updateSub = sub.on('realTime').handle(this.update.bind(this));
        }
        /**
         * Updates this selector.
         */
        update() {
            if (this.needReselect) {
                this.selectIndex();
                this.needReselect = false;
            }
        }
        /**
         * Selects the index of the GPS receiver with the most desirable state.
         */
        selectIndex() {
            let bestIndex;
            let bestState;
            if (this.receiverEntries.size === 0) {
                bestIndex = -1;
                bestState = undefined;
            }
            else if (this.receiverEntries.size === 1) {
                const entry = this.receiverEntries.entries().next().value;
                bestIndex = entry[0];
                bestState = entry[1].state.get();
            }
            else {
                // Sort the systems in order of decreasing priority.
                this.receiverOrder.length = 0;
                for (const index of this.receiverEntries.keys()) {
                    this.receiverOrder.push(index);
                }
                this.receiverOrder.sort(this.receiverPriorityComparator);
                bestIndex = -1;
                for (let i = 0; i < this.receiverOrder.length; i++) {
                    const index = this.receiverOrder[i];
                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                    const state = this.receiverEntries.get(index).state.get();
                    if (!bestState || this.desirabilityComparator(state, bestState) < 0) {
                        bestIndex = index;
                        bestState = state;
                    }
                }
            }
            this._selectedIndex.set(bestIndex);
        }
        /**
         * Destroys this selector.
         */
        destroy() {
            var _a, _b, _c, _d;
            this.isAlive = false;
            (_a = this.updateSub) === null || _a === void 0 ? void 0 : _a.destroy();
            for (const entry of this.receiverEntries.values()) {
                for (const subject of entry.subjects) {
                    subject.destroy();
                }
            }
            (_b = this.candidateReceiverIndexesSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.preferredReceiverIndexSub) === null || _c === void 0 ? void 0 : _c.destroy();
            (_d = this.receiverPrioritiesSub) === null || _d === void 0 ? void 0 : _d.destroy();
        }
        /**
         * A function that compares the desirability of two GPS receivers using a set of default criteria. A receiver is
         * deemed more desirable than another if and only if its system state is located higher than the other's state in the
         * following hierarchy:
         * 1. `GPSSystemState.DiffSolutionAcquired`
         * 2. `GPSSystemState.SolutionAcquired`
         * 3. `GPSSystemState.Acquiring` / `GPSSystemState.Searching`
         * @param a The state of the first GPS receiver.
         * @param b The state of the second GPS receiver.
         * @returns A number representing the relative desirability of the two GPS receivers: negative if receiver `a` is
         * more desirable than `b`, positive if receiver `b` is more desirable than `a`, and `0` if both are equally
         * desirable.
         */
        static defaultDesirabilityComparator(a, b) {
            return GpsReceiverSelector.GPS_STATE_PRIORITIES[a[0]] - GpsReceiverSelector.GPS_STATE_PRIORITIES[b[0]];
        }
    }
    GpsReceiverSelector.GPS_STATE_PRIORITIES = {
        [msfsSdk.GPSSystemState.DiffSolutionAcquired]: 0,
        [msfsSdk.GPSSystemState.SolutionAcquired]: 1,
        [msfsSdk.GPSSystemState.Acquiring]: 2,
        [msfsSdk.GPSSystemState.Searching]: 2
    };

    /**
     * A manager which automatically sets flight path calculator bank angles and airplane speed modes in response to
     * autopilot Low Bank Mode and ADC/GPS data validity, respectively.
     */
    class FlightPathCalculatorManager {
        // eslint-disable-next-line jsdoc/require-jsdoc
        constructor(bus, arg2, arg3, maxBankAngle, lowBankAngle) {
            var _a, _b;
            this.bus = bus;
            this.publisher = this.bus.getPublisher();
            this.isAlive = true;
            this.isInit = false;
            let isAdcDataValid;
            let isGpsDataValid;
            if (arg2 === undefined || msfsSdk.SubscribableUtils.isSubscribable(arg2)) {
                this.calculatorId = '';
                this.maxBankAngle = maxBankAngle;
                this.lowBankAngle = lowBankAngle !== null && lowBankAngle !== void 0 ? lowBankAngle : this.maxBankAngle;
                isAdcDataValid = arg2;
                isGpsDataValid = arg3;
            }
            else {
                this.calculatorId = (_a = arg2.id) !== null && _a !== void 0 ? _a : '';
                this.maxBankAngle = arg2.maxBankAngle;
                this.lowBankAngle = (_b = arg2.lowBankAngle) !== null && _b !== void 0 ? _b : this.maxBankAngle;
                isAdcDataValid = arg2.isAdcDataValid;
                isGpsDataValid = arg2.isGpsDataValid;
            }
            this.setOptionsTopic = this.calculatorId === '' ? 'flightpath_set_options' : `flightpath_set_options_${this.calculatorId}`;
            this.speedDataState = msfsSdk.MappedSubject.create(isAdcDataValid !== null && isAdcDataValid !== void 0 ? isAdcDataValid : msfsSdk.Subject.create(undefined), isGpsDataValid !== null && isGpsDataValid !== void 0 ? isGpsDataValid : msfsSdk.Subject.create(undefined)).pause();
        }
        /**
         * Initializes this manager. Once this manager is initialized, it will automatically set the maximum bank angle and
         * airplane speed mode used by the flight path calculator.
         * @throws Error if this manager has been destroyed.
         */
        init() {
            if (!this.isAlive) {
                throw new Error('FlightPathCalculatorManager: cannot initialize a dead manager');
            }
            if (this.isInit) {
                return;
            }
            this.isInit = true;
            const sub = this.bus.getSubscriber();
            this.lowBankSub = sub.on('ap_max_bank_id').whenChanged().handle(id => { this.onLowBankChanged(id === 1); });
            this.speedDataState.resume();
            this.speedDataState.sub(this.onSpeedDataValidityChanged.bind(this), true);
        }
        /**
         * Responds to when autopilot Low Bank Mode is activated or deactivated.
         * @param isLowBank Whether autopilot Low Bank Mode is active.
         */
        onLowBankChanged(isLowBank) {
            if (this.isLowBank === isLowBank) {
                return;
            }
            this.isLowBank = isLowBank;
            this.publisher.pub(this.setOptionsTopic, { maxBankAngle: isLowBank ? this.lowBankAngle : this.maxBankAngle }, true, false);
        }
        /**
         * Responds to when the validity state of speed data changes.
         * @param state The validity state of speed data, as `[ADC, GPS]`.
         */
        onSpeedDataValidityChanged(state) {
            const [isAdcDataValid, isGpsDataValid] = state;
            let airplaneSpeedMode;
            if (isAdcDataValid !== undefined) {
                if (isAdcDataValid) {
                    airplaneSpeedMode = isGpsDataValid ? msfsSdk.FlightPathAirplaneSpeedMode.TrueAirspeedPlusWind : msfsSdk.FlightPathAirplaneSpeedMode.TrueAirspeed;
                }
                else {
                    airplaneSpeedMode = msfsSdk.FlightPathAirplaneSpeedMode.Default;
                }
            }
            else {
                airplaneSpeedMode = isGpsDataValid ? msfsSdk.FlightPathAirplaneSpeedMode.GroundSpeed : msfsSdk.FlightPathAirplaneSpeedMode.Default;
            }
            this.publisher.pub('flightpath_set_options', { airplaneSpeedMode }, true, false);
        }
        /**
         * Destroys this manager.
         */
        destroy() {
            var _a;
            this.isAlive = false;
            this.speedDataState.destroy();
            (_a = this.lowBankSub) === null || _a === void 0 ? void 0 : _a.destroy();
        }
    }

    /**
     * A manager for syncing the active flight plan to and from the sim.
     */
    class FlightPlanSimSyncManager {
        /**
         * Constructor.
         * @param bus The event bus.
         * @param fms The FMS.
         */
        constructor(bus, fms) {
            this.bus = bus;
            this.fms = fms;
            this._isAutoSyncing = false;
            this.syncDebounceTimer = new msfsSdk.DebounceTimer();
            this.isSyncQueued = false;
            this.isSyncing = false;
            const queueSync = () => {
                this.isSyncQueued = true;
                if (!this.isSyncing) {
                    this.dequeueSync();
                }
            };
            const scheduleSync = () => {
                if (!this.syncDebounceTimer.isPending()) {
                    this.syncDebounceTimer.schedule(queueSync, 0);
                }
            };
            this.autoSyncSubs = [
                this.fms.flightPlanner.onEvent('fplLoaded').handle(e => { e.planIndex === this.fms.flightPlanner.activePlanIndex && scheduleSync(); }).pause(),
                this.fms.flightPlanner.onEvent('fplCopied').handle(e => { e.targetPlanIndex === this.fms.flightPlanner.activePlanIndex && scheduleSync(); }).pause(),
                this.fms.flightPlanner.onEvent('fplSegmentChange').handle(e => { e.planIndex === this.fms.flightPlanner.activePlanIndex && scheduleSync(); }).pause(),
                this.fms.flightPlanner.onEvent('fplLegChange').handle(e => { e.planIndex === this.fms.flightPlanner.activePlanIndex && scheduleSync(); }).pause(),
                this.fms.flightPlanner.onEvent('fplOriginDestChanged').handle(e => { e.planIndex === this.fms.flightPlanner.activePlanIndex && scheduleSync(); }).pause(),
                this.fms.flightPlanner.onEvent('fplProcDetailsChanged').handle(e => { e.planIndex === this.fms.flightPlanner.activePlanIndex && scheduleSync(); }).pause(),
                this.fms.flightPlanner.onEvent('fplIndexChanged').handle(() => { scheduleSync(); }).pause(),
            ];
        }
        /**
         * Gets an instance of the flight plan sync manager.
         * @param bus The event bus.
         * @param fms The FMS.
         * @returns A Promise which will be fulfilled with an instance of the flight plan sync manager when it is ready.
         */
        static getManager(bus, fms) {
            if (FlightPlanSimSyncManager.INSTANCE !== undefined) {
                return Promise.resolve(FlightPlanSimSyncManager.INSTANCE);
            }
            return new Promise((resolve) => {
                FlightPlanSimSyncManager.queuedInitResolves.push(resolve);
                if (!FlightPlanSimSyncManager.hasStartedInit) {
                    FlightPlanSimSyncManager.hasStartedInit = true;
                    RegisterViewListener('JS_LISTENER_FLIGHTPLAN', async () => {
                        FlightPlanSimSyncManager.INSTANCE = new FlightPlanSimSyncManager(bus, fms);
                        FlightPlanSimSyncManager.queuedInitResolves.forEach(queuedResolve => queuedResolve(FlightPlanSimSyncManager.INSTANCE));
                        FlightPlanSimSyncManager.queuedInitResolves.length = 0;
                    });
                }
            });
        }
        /**
         * Checks whether this manager is automatically syncing the active flight plan to the sim.
         * @returns Whether this manager is automatically syncing the active flight plan to the sim.
         */
        isAutoSyncing() {
            return this._isAutoSyncing;
        }
        /**
         * Loads the flight plan from the sim.
         * @param flattenAirways Whether to flatten airways to their constituent legs. Defaults to `false`.
         * @returns A Promise which is fulfilled when the flight plan has been loaded.
         */
        async loadFromSim(flattenAirways = false) {
            Coherent.call('LOAD_CURRENT_ATC_FLIGHTPLAN');
            // Coherent.call('LOAD_CURRENT_GAME_FLIGHT');
            await msfsSdk.Wait.awaitDelay(3000);
            const data = await Coherent.call('GET_FLIGHTPLAN');
            const isDirectTo = data.isDirectTo;
            let lastEnrouteSegment = 1;
            // TODO: dirto
            if (!isDirectTo) {
                if (data.waypoints.length === 0) {
                    return;
                }
                // Make sure the primary flight plan is empty.
                await this.fms.emptyPrimaryFlightPlan();
                const plan = this.fms.getPrimaryFlightPlan();
                // Check if the first waypoint is an airport. If it is, set it as the origin airport and insert any departure
                // that is defined. If not, insert it as an enroute waypoint.
                let originFacilityType = undefined;
                if (msfsSdk.ICAO.isFacility(data.waypoints[0].icao)) {
                    originFacilityType = msfsSdk.ICAO.getFacilityType(data.waypoints[0].icao);
                }
                if (originFacilityType === msfsSdk.FacilityType.Airport) {
                    const originFac = await this.fms.facLoader.getFacility(msfsSdk.ICAO.getFacilityType(data.waypoints[0].icao), data.waypoints[0].icao);
                    if (originFac !== undefined) {
                        this.setDeparture(originFac, data);
                    }
                }
                else if (originFacilityType !== undefined) {
                    this.buildNonAirportOriginLeg(data, plan);
                }
                // Check if the last waypoint is an airport, and if it is, set it as the destination airport and insert any
                // arrival and approach that is defined.
                const destIndex = data.waypoints.length - 1;
                let destFacilityType = undefined;
                if (msfsSdk.ICAO.isFacility(data.waypoints[destIndex].icao)) {
                    destFacilityType = msfsSdk.ICAO.getFacilityType(data.waypoints[destIndex].icao);
                }
                if (destFacilityType === msfsSdk.FacilityType.Airport) {
                    const destFac = await this.fms.facLoader.getFacility(msfsSdk.ICAO.getFacilityType(data.waypoints[destIndex].icao), data.waypoints[destIndex].icao);
                    if (destFac !== undefined) {
                        await this.setDestination(destFac, data);
                    }
                }
                // Add enroute waypoints and airways.
                lastEnrouteSegment = this.setEnroute(data, plan, flattenAirways);
                // If the last waypoint is not an airport, insert it as an enroute waypoint.
                if (destFacilityType !== msfsSdk.FacilityType.Airport && destFacilityType !== undefined) {
                    this.buildNonAirportDestLeg(data, plan, lastEnrouteSegment);
                }
                plan.calculate(0).then(() => {
                    if (Simplane.getIsGrounded()) {
                        plan.setLateralLeg(0);
                    }
                    else {
                        this.fms.activateNearestLeg();
                    }
                });
            }
        }
        /**
         * Starts automatically syncing the active flight plan to the sim.
         */
        startAutoSync() {
            if (this._isAutoSyncing) {
                return;
            }
            this._isAutoSyncing = true;
            this.autoSyncSubs.forEach(sub => { sub.resume(); });
        }
        /**
         * Stops automatically syncing the active flight plan to the sim.
         */
        stopAutoSync() {
            if (!this._isAutoSyncing) {
                return;
            }
            this._isAutoSyncing = false;
            this.autoSyncSubs.forEach(sub => { sub.pause(); });
        }
        /**
         * Syncs the origin airport and departure of a sim flight plan to a JS flight plan.
         * @param facility The facility record for the origin airport.
         * @param data The sim flight plan data object to sync from.
         */
        setDeparture(facility, data) {
            let originOneWayRunway = undefined;
            if (data.originRunwayIndex > -1) {
                const oneWayRunways = [];
                let index = 0;
                facility.runways.forEach((runway) => {
                    for (const rw of msfsSdk.RunwayUtils.getOneWayRunways(runway, index)) {
                        oneWayRunways.push(rw);
                    }
                    index++;
                });
                oneWayRunways.sort(msfsSdk.RunwayUtils.sortRunways);
                originOneWayRunway = oneWayRunways[data.originRunwayIndex];
            }
            if (data.departureProcIndex >= 0 && data.departureProcIndex < facility.departures.length) {
                // Runway and enroute transition indexes default to 0 even when the departure has no runway or enroute
                // transitions, so we have to do the OOB checks ourselves.
                const departure = facility.departures[data.departureProcIndex];
                const runwayTransitionIndex = data.departureRunwayIndex < 0 || data.departureRunwayIndex >= departure.runwayTransitions.length
                    ? -1
                    : data.departureRunwayIndex;
                if (runwayTransitionIndex >= 0) {
                    const runwayTransition = departure.runwayTransitions[runwayTransitionIndex];
                    const runwayString = msfsSdk.RunwayUtils.getRunwayNameString(runwayTransition.runwayNumber, runwayTransition.runwayDesignation);
                    originOneWayRunway = msfsSdk.RunwayUtils.matchOneWayRunwayFromDesignation(facility, runwayString);
                }
                const enrouteTransitionIndex = data.departureEnRouteTransitionIndex < 0 || data.departureEnRouteTransitionIndex >= departure.enRouteTransitions.length
                    ? -1
                    : data.departureEnRouteTransitionIndex;
                this.fms.insertDeparture(facility, data.departureProcIndex, runwayTransitionIndex, enrouteTransitionIndex, originOneWayRunway);
            }
            else {
                this.fms.setOrigin(facility, originOneWayRunway);
            }
        }
        /**
         * Syncs the arrival, approach, and destination airport of a sim flight plan to a JS flight plan.
         * @param facility The facility record for the destination airport.
         * @param data The sim flight plan data object to sync from.
         */
        async setDestination(facility, data) {
            let destOneWayRunway = undefined;
            if (data.arrivalProcIndex >= 0 && data.arrivalProcIndex < facility.arrivals.length) {
                // Runway and enroute transition indexes default to 0 even when the departure has no runway or enroute
                // transitions, so we have to do the OOB checks ourselves.
                const arrival = facility.arrivals[data.arrivalProcIndex];
                const runwayTransitionIndex = data.arrivalRunwayIndex < 0 || data.arrivalRunwayIndex >= arrival.runwayTransitions.length
                    ? -1
                    : data.arrivalRunwayIndex;
                if (runwayTransitionIndex >= 0) {
                    const runwayTransition = arrival.runwayTransitions[runwayTransitionIndex];
                    if (runwayTransition !== undefined) {
                        const runwayString = msfsSdk.RunwayUtils.getRunwayNameString(runwayTransition.runwayNumber, runwayTransition.runwayDesignation);
                        destOneWayRunway = msfsSdk.RunwayUtils.matchOneWayRunwayFromDesignation(facility, runwayString);
                    }
                }
                const enrouteTransitionIndex = data.arrivalEnRouteTransitionIndex < 0 || data.arrivalEnRouteTransitionIndex >= arrival.enRouteTransitions.length
                    ? -1
                    : data.arrivalEnRouteTransitionIndex;
                this.fms.insertArrival(facility, data.arrivalProcIndex, runwayTransitionIndex, enrouteTransitionIndex, destOneWayRunway);
            }
            if (data.approachIndex !== -1) {
                const approachTransitionIndex = data.approachTransitionIndex === 0 &&
                    facility.approaches[data.approachIndex].transitions.length < 1 ? -1 : data.approachTransitionIndex;
                await this.fms.insertApproach(facility, data.approachIndex, approachTransitionIndex);
                // Loading an approach might result in automatic activation of the approach, creating an on-route direct-to to
                // the IAF. We don't want this to happen because it will mess with importing enroute waypoints, so we will cancel
                // any active direct-to after the approach has been loaded.
                this.fms.cancelDirectTo();
            }
            if (data.arrivalProcIndex === -1 && data.approachIndex === -1) {
                this.fms.setDestination(facility);
            }
        }
        /**
         * Syncs the enroute portion of a sim flight plan to a JS flight plan.
         * @param data The sim flight plan data object to sync from.
         * @param plan The flight plan to sync to.
         * @param flattenAirways Whether to flatten airways to their constituent legs. Defaults to `false`.
         * @returns The index of the last enroute segment that was added to the plan.
         */
        setEnroute(data, plan, flattenAirways = false) {
            // If there is no departure, start at index 1 (index 0 is always the "origin")
            // If there is a departure, start at the last departure wpt (a departure guarantees an origin, so the
            // departure itself starts at index 1 -> the last departure wpt is at [1 + departureWaypointsSize - 1]). The
            // reason we start here is to catch any airways that begin with the last departure wpt.
            const enrouteStart = data.departureWaypointsSize <= 0
                ? 1
                : data.departureWaypointsSize;
            // If there is no arrival, end with the second-to-last waypoint (we skip the last waypoint even if it is not the
            // destination airport because it will get handled elsewhere). If there is an arrival, then the last enroute
            // waypoint will be at index [length - 2 - arrivalWaypointsSize] (i.e. counting backwards from the end, we skip the
            // destination airport, then every waypoint in the arrival).
            const enrouteEnd = data.arrivalWaypointsSize <= 0
                ? -1
                : -(data.arrivalWaypointsSize + 1);
            const enroute = data.waypoints.slice(enrouteStart, enrouteEnd);
            const initialPlanLength = plan.length;
            let custIdx = 1;
            let currentSegment = 1;
            let lastDepartureLegIcao = undefined;
            let lastLegWasAirway = false;
            if (data.departureProcIndex > -1) {
                const depSegment = plan.getSegment(0);
                if (depSegment.legs.length > 1) {
                    lastDepartureLegIcao = depSegment.legs[depSegment.legs.length - 1].leg.fixIcao;
                }
            }
            for (let i = 0; i < enroute.length; i++) {
                const wpt = enroute[i];
                const segment = plan.getSegment(currentSegment);
                if (wpt.airwayIdent && !flattenAirways) {
                    if (currentSegment == 1 && lastDepartureLegIcao == wpt.icao && plan.length === initialPlanLength) ;
                    else {
                        const leg = msfsSdk.FlightPlan.createLeg({
                            type: msfsSdk.LegType.TF,
                            fixIcao: wpt.icao
                        });
                        plan.addLeg(currentSegment, leg);
                        if (!lastLegWasAirway) {
                            plan.insertSegment(currentSegment + 1, msfsSdk.FlightPlanSegmentType.Enroute, wpt.airwayIdent);
                            currentSegment += 1;
                        }
                    }
                    // Once we have entered an airway, continue adding legs to the airway segment until we encounter a wpt whose
                    // airway ident does not match the current airway -> this wpt will be the exit waypoint.
                    for (let j = i + 1; j < enroute.length; j++) {
                        i++;
                        const airwayLeg = msfsSdk.FlightPlan.createLeg({
                            type: msfsSdk.LegType.TF,
                            fixIcao: enroute[j].icao
                        });
                        plan.addLeg(currentSegment, airwayLeg);
                        if (enroute[j].airwayIdent !== wpt.airwayIdent) {
                            lastLegWasAirway = enroute[j].airwayIdent ? true : false;
                            break;
                        }
                    }
                    // Update the airway name with the correct exit waypoint.
                    plan.setAirway(currentSegment, wpt.airwayIdent + '.' + enroute[i].ident);
                    currentSegment += 1;
                    plan.insertSegment(currentSegment, msfsSdk.FlightPlanSegmentType.Enroute, lastLegWasAirway ? enroute[i].airwayIdent : undefined);
                }
                else {
                    let leg = undefined;
                    let skip = false;
                    if (currentSegment == 1 && lastDepartureLegIcao == wpt.icao && plan.length === initialPlanLength) {
                        // We are currently at the last departure wpt. We don't want to duplicate this waypoint, so skip it.
                        skip = true;
                    }
                    if (!skip && wpt.icao.trim() == '') {
                        // Skip ATC waypoints.
                        const re = /(?:[D][\d])|(?:DLast)|(?:TIMEVERT)|(?:TIMECLIMB)|(?:TIMECRUIS)|(?:TIMEDSCNT)|(?:TIMEAPPROACH)/;
                        skip = wpt.ident.match(re) !== null;
                    }
                    if (!skip && (wpt.ident === 'Custom' || wpt.icao.trim() == '')) {
                        // Convert Custom waypoints to USER facilities.
                        const userFacility = msfsSdk.UserFacilityUtils.createFromLatLon(`U      USR${custIdx.toString().padStart(2, '0')}`, wpt.lla.lat, wpt.lla.long, true, wpt.icao.trim() === '' ? wpt.ident : `Custom ${custIdx.toString().padStart(2, '0')}`);
                        this.fms.addUserFacility(userFacility);
                        leg = msfsSdk.FlightPlan.createLeg({
                            type: msfsSdk.LegType.TF,
                            fixIcao: userFacility.icao,
                            lat: wpt.lla.lat,
                            lon: wpt.lla.long
                        });
                        custIdx++;
                    }
                    else if (!skip && wpt.icao.trim() !== '') {
                        leg = msfsSdk.FlightPlan.createLeg({
                            type: msfsSdk.LegType.TF,
                            fixIcao: wpt.icao
                        });
                    }
                    if (leg) {
                        plan.addLeg(currentSegment, leg);
                        if (lastLegWasAirway) {
                            plan.setAirway(currentSegment, segment.airway + '.' + wpt.ident);
                            currentSegment += 1;
                            plan.insertSegment(currentSegment, msfsSdk.FlightPlanSegmentType.Enroute);
                        }
                        lastLegWasAirway = false;
                    }
                }
            }
            // The last enroute segment cannot be an airway.
            if (plan.getSegment(currentSegment).airway) {
                currentSegment += 1;
                plan.insertSegment(currentSegment, msfsSdk.FlightPlanSegmentType.Enroute);
            }
            return currentSegment;
        }
        /**
         * Sets the first leg of the enroute plan as the first leg in the world map plan, but as an IF leg
         * @param data is the flight plan sync data object from the world map
         * @param plan an instance of the flight plan
         */
        buildNonAirportOriginLeg(data, plan) {
            const wpt = data.waypoints[0];
            if (wpt.ident === 'CUSTD' || wpt.ident === 'CUSTA') {
                const userFacility = msfsSdk.UserFacilityUtils.createFromLatLon('U      CUSTD', wpt.lla.lat, wpt.lla.long, true, 'Custom Origin');
                this.fms.addUserFacility(userFacility);
                const leg = msfsSdk.FlightPlan.createLeg({
                    type: msfsSdk.LegType.IF,
                    fixIcao: 'U      CUSTD',
                    lat: wpt.lla.lat,
                    lon: wpt.lla.long
                });
                plan.addLeg(1, leg);
            }
            else if (wpt.icao.trim() !== '') {
                const leg = msfsSdk.FlightPlan.createLeg({
                    type: msfsSdk.LegType.IF,
                    fixIcao: wpt.icao
                });
                plan.addLeg(1, leg);
            }
        }
        /**
         * Sets the last leg of the enroute plan as the last leg in the world map plan, but as an TF leg
         * @param data is the flight plan sync data object from the world map
         * @param plan an instance of the flight plan
         * @param lastEnrouteSegment is the last enroute segment
         */
        buildNonAirportDestLeg(data, plan, lastEnrouteSegment) {
            const wpt = data.waypoints[data.waypoints.length - 1];
            if (wpt.ident === 'CUSTD' || wpt.ident === 'CUSTA') {
                const userFacility = msfsSdk.UserFacilityUtils.createFromLatLon('U      CUSTA', wpt.lla.lat, wpt.lla.long, true, 'Custom Destination');
                this.fms.addUserFacility(userFacility);
                const leg = msfsSdk.FlightPlan.createLeg({
                    type: msfsSdk.LegType.TF,
                    fixIcao: userFacility.icao,
                    lat: wpt.lla.lat,
                    lon: wpt.lla.long
                });
                plan.addLeg(lastEnrouteSegment, leg);
            }
            else if (wpt.icao.trim() !== '') {
                const leg = msfsSdk.FlightPlan.createLeg({
                    type: msfsSdk.LegType.TF,
                    fixIcao: wpt.icao
                });
                plan.addLeg(lastEnrouteSegment, leg);
            }
        }
        /**
         * Executes the pending sync operation, if one exists, and continues to execute sync operations after the previous
         * one is completed as long as one is pending.
         */
        async dequeueSync() {
            while (this.isSyncQueued) {
                this.isSyncQueued = false;
                this.isSyncing = true;
                await this.doSync();
                this.isSyncing = false;
            }
        }
        /**
         * Syncs the active flight plan to the sim.
         */
        async doSync() {
            var _a, _b;
            // We are doing this check here and not on initialization of the manager because GET_IS_BUSHTRIP will fail to
            // return anything unless a Bing map is bound (the call is tied to the JS map listener). This is also why we
            // abort the check after 1 second, so that we aren't stuck waiting on it forever.
            (_a = this.isBushTrip) !== null && _a !== void 0 ? _a : (this.isBushTrip = await Promise.race([Coherent.call('GET_IS_BUSHTRIP'), msfsSdk.Wait.awaitDelay(1000)]));
            // Do not try to sync back to the sim until we know for sure we are not in a bushtrip.
            if (this.isBushTrip !== false) {
                // If we find out we are in a bushtrip, destroy all the autosync triggers to make sure we don't try to sync again.
                if (this.isBushTrip === true) {
                    this.autoSyncSubs.forEach(sub => { sub.destroy(); });
                    this.autoSyncSubs.length = 0;
                }
                return;
            }
            // Don't sync off-route direct-tos because it just leads to weirdness on the sim side.
            if (this.fms.getDirectToState() === exports.DirectToState.TORANDOM) {
                return;
            }
            try {
                const plan = this.fms.flightPlanner.getActiveFlightPlan();
                // await Coherent.call('CREATE_NEW_FLIGHTPLAN');
                await Coherent.call('SET_CURRENT_FLIGHTPLAN_INDEX', 0, true);
                await Coherent.call('CLEAR_CURRENT_FLIGHT_PLAN');
                //PM Modif
                let hasOrigin = false;
                if (plan.originAirport !== undefined && plan.originAirport.length > 0) {
                    hasOrigin = true;
                    await Coherent.call('SET_ORIGIN', plan.originAirport, false);
                }
                //End PM Modif
                if (plan.destinationAirport !== undefined && plan.destinationAirport.length > 0) {
                    await Coherent.call('SET_DESTINATION', plan.destinationAirport, false);
                }
                if (plan.procedureDetails.originRunway) {
                    await Coherent.call('SET_ORIGIN_RUNWAY_INDEX', (_b = plan.procedureDetails.originRunway) === null || _b === void 0 ? void 0 : _b.parentRunwayIndex);
                }
                // Put in the enroute waypoints
                const enrouteSegments = plan.segmentsOfType(msfsSdk.FlightPlanSegmentType.Enroute);
                //PM Modif
                let globalIndex = hasOrigin ? 1 : 0;
                for (const segment of enrouteSegments) {
                    // get legs in segment
                    for (const leg of segment.legs) {
                        if (FlightPlanSimSyncManager.isSyncableLeg(leg.leg)) {
                            await Coherent.call('ADD_WAYPOINT', leg.leg.fixIcao, globalIndex, false);
                            globalIndex++;
                        }
                    }
                }
                //End PM Modif
                await Coherent.call('SET_DEPARTURE_RUNWAY_INDEX', plan.procedureDetails.departureRunwayIndex);
                await Coherent.call('SET_DEPARTURE_PROC_INDEX', plan.procedureDetails.departureIndex);
                await Coherent.call('SET_DEPARTURE_ENROUTE_TRANSITION_INDEX', plan.procedureDetails.departureTransitionIndex === -1 ? 0 : plan.procedureDetails.departureTransitionIndex);
                await Coherent.call('SET_ARRIVAL_RUNWAY_INDEX', plan.procedureDetails.arrivalRunwayTransitionIndex === -1 ? 0 : plan.procedureDetails.arrivalRunwayTransitionIndex);
                await Coherent.call('SET_ARRIVAL_PROC_INDEX', plan.procedureDetails.arrivalIndex);
                await Coherent.call('SET_ARRIVAL_ENROUTE_TRANSITION_INDEX', plan.procedureDetails.arrivalTransitionIndex);
                await Coherent.call('SET_APPROACH_INDEX', plan.procedureDetails.approachIndex).then(() => {
                    Coherent.call('SET_APPROACH_TRANSITION_INDEX', plan.procedureDetails.approachTransitionIndex);
                });
                if (plan.activeLateralLeg < plan.length) {
                    const activeSegment = plan.getSegment(plan.getSegmentIndex(Math.max(0, plan.activeLateralLeg)));
                    if (activeSegment.segmentType === msfsSdk.FlightPlanSegmentType.Approach) {
                        await Coherent.call('TRY_AUTOACTIVATE_APPROACH');
                    }
                }
                await Coherent.call('RECOMPUTE_ACTIVE_WAYPOINT_INDEX', 0);
            }
            catch (e) {
                if (e instanceof Error) {
                    console.warn(e);
                    console.error(e.stack);
                }
                else {
                    console.warn(`FlightPlanSimSyncManager: error when syncing flight plan to sim: ${JSON.stringify(e)}`);
                }
            }
        }
        /**
         * Checks if a leg is syncable to the stock flight plan system.
         * @param leg the leg to check
         * @returns true if the leg is syncable, false otherwise
         */
        static isSyncableLeg(leg) {
            return FlightPlanSimSyncManager.NON_SYNCABLE_LEG_TYPES.indexOf(leg.type) === -1;
        }
    }
    FlightPlanSimSyncManager.NON_SYNCABLE_LEG_TYPES = [
        msfsSdk.LegType.Discontinuity,
        msfsSdk.LegType.ThruDiscontinuity,
        msfsSdk.LegType.Unknown,
        msfsSdk.LegType.HM,
        msfsSdk.LegType.HA,
        msfsSdk.LegType.HF,
    ];
    FlightPlanSimSyncManager.hasStartedInit = false;
    FlightPlanSimSyncManager.queuedInitResolves = [];

    /**
     * Computes Garmin LNAV-related data.
     */
    class NavdataComputer {
        /**
         * Creates a new instance of the NavdataComputer.
         * @param bus The event bus to use with this instance.
         * @param flightPlanner The flight planner to use with this instance.
         * @param facilityLoader The facility loader to use with this instance.
         * @param options Options with which to configure the computer.
         */
        constructor(bus, flightPlanner, facilityLoader, options) {
            var _a, _b, _c, _d;
            this.bus = bus;
            this.flightPlanner = flightPlanner;
            this.facilityLoader = facilityLoader;
            this.geoPointCache = [new msfsSdk.GeoPoint(0, 0)];
            this.publisher = this.bus.getPublisher();
            this.isVNavIndexValid = false;
            this.vnavTopicMap = {
                'gp_gsi_scaling': 'gp_gsi_scaling'
            };
            this.planePos = new msfsSdk.GeoPoint(0, 0);
            this.obsAvailable = msfsSdk.Subject.create(false);
            this.approachDetails = {
                isLoaded: false,
                type: ApproachType.APPROACH_TYPE_UNKNOWN,
                isRnpAr: false,
                bestRnavType: msfsSdk.RnavTypeFlags.None,
                rnavTypeFlags: msfsSdk.RnavTypeFlags.None,
                isCircling: false,
                isVtf: false,
                referenceFacility: null,
                runway: null
            };
            this.gpServiceLevel = msfsSdk.ConsumerValue.create(null, exports.GlidepathServiceLevel.None);
            this.gpDistance = msfsSdk.ConsumerValue.create(null, 0);
            this.lnavData = msfsSdk.ObjectSubject.create({
                dtkTrue: 0,
                dtkMag: 0,
                xtk: 0,
                nextDtkTrue: 0,
                nextDtkMag: 0,
                cdiScale: 0,
                cdiScaleLabel: exports.CDIScaleLabel.Enroute,
                waypointBearingTrue: 0,
                waypointBearingMag: 0,
                waypointDistance: 0,
                waypointIdent: '',
                destinationIcao: '',
                destinationRunwayIcao: '',
                destinationDistance: -1,
                egressDistance: 0,
                toFrom: msfsSdk.VorToFrom.OFF
            });
            this.vnavData = msfsSdk.ObjectSubject.create({
                gpScale: 0
            });
            this.dtkVector = {
                globalLegIndex: -1,
                vectorIndex: -1
            };
            this.nextDtkVector = {
                globalLegIndex: -1,
                vectorIndex: -1
            };
            this.nominalPathCircle = { vectorIndex: -1, circle: new msfsSdk.GeoCircle(msfsSdk.Vec3Math.create(), 0) };
            this.needUpdateDestination = true;
            this.primaryPlanOriginFacilityOpId = 0;
            this.primaryPlanDestinationFacilityOpId = 0;
            this.destinationFacilityOpId = 0;
            this.lnavIndex = (_a = options === null || options === void 0 ? void 0 : options.lnavIndex) !== null && _a !== void 0 ? _a : 0;
            if (!msfsSdk.LNavUtils.isValidLNavIndex(this.lnavIndex)) {
                throw new Error(`NavdataComputer: invalid LNAV index (${this.lnavIndex}) specified (must be a non-negative integer)`);
            }
            this.useSimObsState = (_b = options === null || options === void 0 ? void 0 : options.useSimObsState) !== null && _b !== void 0 ? _b : true;
            this.vnavIndex = msfsSdk.SubscribableUtils.toSubscribable((_c = options === null || options === void 0 ? void 0 : options.vnavIndex) !== null && _c !== void 0 ? _c : 0, true);
            this.useVfrCdiScaling = msfsSdk.SubscribableUtils.toSubscribable((_d = options === null || options === void 0 ? void 0 : options.useVfrCdiScaling) !== null && _d !== void 0 ? _d : false, true);
            const simVarSuffix = this.lnavIndex === 0 ? '' : `:${this.lnavIndex}`;
            this.simVarMap = {};
            for (const simVar of [...Object.values(msfsSdk.LNavDataVars), ...Object.values(exports.GarminLNavDataVars)]) {
                this.simVarMap[simVar] = `${simVar}${simVarSuffix}`;
            }
            const lnavTopicSuffix = msfsSdk.LNavUtils.getEventBusTopicSuffix(this.lnavIndex);
            this.lnavTopicMap = {
                'lnavdata_waypoint_ident': `lnavdata_waypoint_ident${lnavTopicSuffix}`,
                'lnavdata_destination_icao': `lnavdata_destination_icao${lnavTopicSuffix}`,
                'lnavdata_destination_ident': `lnavdata_destination_ident${lnavTopicSuffix}`,
                'lnavdata_destination_runway_icao': `lnavdata_destination_runway_icao${lnavTopicSuffix}`,
                'lnavdata_dtk_vector': `lnavdata_dtk_vector${lnavTopicSuffix}`,
                'lnavdata_next_dtk_vector': `lnavdata_next_dtk_vector${lnavTopicSuffix}`,
                'obs_available': `obs_available${lnavTopicSuffix}`,
            };
            const sub = this.bus.getSubscriber();
            this.magVar = msfsSdk.ConsumerValue.create(sub.on('magvar'), 0);
            this.isObsActive = msfsSdk.ConsumerValue.create(sub.on(this.useSimObsState ? 'gps_obs_active' : `lnav_obs_active${lnavTopicSuffix}`), false);
            this.lnavIsTracking = msfsSdk.ConsumerValue.create(sub.on(`lnav_is_tracking${lnavTopicSuffix}`), false);
            this.lnavLegIndex = msfsSdk.ConsumerValue.create(sub.on(`lnav_tracked_leg_index${lnavTopicSuffix}`), 0);
            this.lnavVectorIndex = msfsSdk.ConsumerValue.create(sub.on(`lnav_tracked_vector_index${lnavTopicSuffix}`), 0);
            this.lnavTransitionMode = msfsSdk.ConsumerValue.create(sub.on(`lnav_transition_mode${lnavTopicSuffix}`), msfsSdk.LNavTransitionMode.None);
            this.lnavIsSuspended = msfsSdk.ConsumerValue.create(sub.on(`lnav_is_suspended${lnavTopicSuffix}`), false);
            this.lnavDtk = msfsSdk.ConsumerValue.create(sub.on(`lnav_dtk${lnavTopicSuffix}`), 0);
            this.lnavXtk = msfsSdk.ConsumerValue.create(sub.on(`lnav_xtk${lnavTopicSuffix}`), 0);
            this.lnavLegDistanceAlong = msfsSdk.ConsumerValue.create(sub.on(`lnav_leg_distance_along${lnavTopicSuffix}`), 0);
            this.lnavLegDistanceRemaining = msfsSdk.ConsumerValue.create(sub.on(`lnav_leg_distance_remaining${lnavTopicSuffix}`), 0);
            this.isMaprActive = msfsSdk.ConsumerValue.create(sub.on(`activate_missed_approach${lnavTopicSuffix}`), false);
            const vnavDataSub = this.vnavData.sub((obj, key, value) => {
                switch (key) {
                    case 'gpScale':
                        this.publisher.pub(this.vnavTopicMap['gp_gsi_scaling'], value, true, true);
                        break;
                }
            }, false, true);
            this.vnavIndex.sub(vnavIndex => {
                this.isVNavIndexValid = msfsSdk.VNavUtils.isValidVNavIndex(vnavIndex);
                if (this.isVNavIndexValid) {
                    const suffix = msfsSdk.VNavUtils.getEventBusTopicSuffix(vnavIndex);
                    this.gpServiceLevel.setConsumer(sub.on(`gp_service_level${suffix}`));
                    this.gpDistance.setConsumer(sub.on(`gp_distance${suffix}`));
                    this.vnavTopicMap['gp_gsi_scaling'] = `gp_gsi_scaling${suffix}`;
                    vnavDataSub.resume(true);
                }
                else {
                    vnavDataSub.pause();
                    this.gpServiceLevel.setConsumer(null);
                    this.gpDistance.setConsumer(null);
                }
            }, true);
            sub.on('gps-position').handle(lla => { this.planePos.set(lla.lat, lla.long); });
            this.flightPlanner.onEvent('fplOriginDestChanged').handle(this.onOriginDestChanged.bind(this));
            this.flightPlanner.onEvent('fplSegmentChange').handle(() => this.onSegmentChanged.bind(this));
            this.flightPlanner.onEvent('fplLegChange').handle(this.onLegChanged.bind(this));
            this.flightPlanner.onEvent('fplIndexChanged').handle(this.onActivePlanChanged.bind(this));
            this.flightPlanner.onEvent('fplActiveLegChange').handle(this.onActiveLegChanged.bind(this));
            FmsUtils.onFmsEvent(this.flightPlanner.id, sub, 'fms_approach_details').handle(d => { this.approachDetails = d; });
            sub.on('realTime').handle(() => {
                this.computeTrackingData();
                this.computeCdiScaling();
                this.computeGpScaling();
            });
            this.lnavData.sub((obj, key, value) => {
                switch (key) {
                    case 'dtkTrue':
                        SimVar.SetSimVarValue(this.simVarMap[msfsSdk.LNavDataVars.DTKTrue], msfsSdk.SimVarValueType.Degree, value);
                        break;
                    case 'dtkMag':
                        SimVar.SetSimVarValue(this.simVarMap[msfsSdk.LNavDataVars.DTKMagnetic], msfsSdk.SimVarValueType.Degree, value);
                        break;
                    case 'xtk':
                        SimVar.SetSimVarValue(this.simVarMap[msfsSdk.LNavDataVars.XTK], msfsSdk.SimVarValueType.NM, value);
                        break;
                    case 'nextDtkTrue':
                        SimVar.SetSimVarValue(this.simVarMap[exports.GarminLNavDataVars.NextDTKTrue], msfsSdk.SimVarValueType.Degree, value);
                        break;
                    case 'nextDtkMag':
                        SimVar.SetSimVarValue(this.simVarMap[exports.GarminLNavDataVars.NextDTKMagnetic], msfsSdk.SimVarValueType.Degree, value);
                        break;
                    case 'cdiScale':
                        SimVar.SetSimVarValue(this.simVarMap[msfsSdk.LNavDataVars.CDIScale], msfsSdk.SimVarValueType.NM, value);
                        break;
                    case 'cdiScaleLabel':
                        SimVar.SetSimVarValue(this.simVarMap[exports.GarminLNavDataVars.CDIScaleLabel], msfsSdk.SimVarValueType.Number, value);
                        break;
                    case 'waypointBearingTrue':
                        SimVar.SetSimVarValue(this.simVarMap[msfsSdk.LNavDataVars.WaypointBearingTrue], msfsSdk.SimVarValueType.Degree, value);
                        break;
                    case 'waypointBearingMag':
                        SimVar.SetSimVarValue(this.simVarMap[msfsSdk.LNavDataVars.WaypointBearingMagnetic], msfsSdk.SimVarValueType.Degree, value);
                        break;
                    case 'waypointDistance':
                        SimVar.SetSimVarValue(this.simVarMap[msfsSdk.LNavDataVars.WaypointDistance], msfsSdk.SimVarValueType.NM, value);
                        break;
                    case 'waypointIdent':
                        this.publisher.pub(this.lnavTopicMap['lnavdata_waypoint_ident'], value, true, true);
                        break;
                    case 'destinationIcao':
                        this.publisher.pub(this.lnavTopicMap['lnavdata_destination_icao'], value, true, true);
                        this.publisher.pub(this.lnavTopicMap['lnavdata_destination_ident'], msfsSdk.ICAO.getIdent(value), true, true);
                        break;
                    case 'destinationRunwayIcao':
                        this.publisher.pub(this.lnavTopicMap['lnavdata_destination_runway_icao'], value, true, true);
                        break;
                    case 'destinationDistance':
                        SimVar.SetSimVarValue(this.simVarMap[msfsSdk.LNavDataVars.DestinationDistance], msfsSdk.SimVarValueType.NM, value);
                        break;
                    case 'egressDistance':
                        SimVar.SetSimVarValue(this.simVarMap[exports.GarminLNavDataVars.EgressDistance], msfsSdk.SimVarValueType.NM, value);
                        break;
                    case 'toFrom':
                        SimVar.SetSimVarValue(this.simVarMap[exports.GarminLNavDataVars.ToFrom], msfsSdk.SimVarValueType.Number, value);
                        break;
                }
            }, true);
            this.obsAvailable.sub(v => {
                this.publisher.pub(this.lnavTopicMap['obs_available'], v, true, true);
            });
        }
        /**
         * Responds to when a flight plan origin or destination changes.
         * @param event The event data describing the change.
         */
        onOriginDestChanged(event) {
            if (event.planIndex !== FmsUtils.PRIMARY_PLAN_INDEX) {
                return;
            }
            if (event.airport !== undefined) {
                if (event.type === msfsSdk.OriginDestChangeType.OriginAdded) {
                    this.updatePrimaryPlanOriginFacility(event.airport);
                }
                else {
                    this.updatePrimaryPlanDestinationFacility(event.airport);
                }
            }
            else {
                if (event.type === msfsSdk.OriginDestChangeType.OriginRemoved) {
                    this.updatePrimaryPlanOriginFacility(undefined);
                }
                else {
                    this.updatePrimaryPlanDestinationFacility(undefined);
                }
            }
            if (event.type === msfsSdk.OriginDestChangeType.DestinationAdded || event.type === msfsSdk.OriginDestChangeType.DestinationRemoved) {
                this.needUpdateDestination = true;
            }
        }
        /**
         * Responds to when a flight plan segment changes.
         * @param event The event data describing the change.
         */
        onSegmentChanged(event) {
            if (event.planIndex === FmsUtils.PRIMARY_PLAN_INDEX
                || (event.planIndex === FmsUtils.DTO_RANDOM_PLAN_INDEX && this.flightPlanner.activePlanIndex === FmsUtils.DTO_RANDOM_PLAN_INDEX)) {
                this.needUpdateDestination = true;
            }
        }
        /**
         * Responds to when a flight plan leg changes.
         * @param event The event data describing the change.
         */
        onLegChanged(event) {
            if (event.planIndex === FmsUtils.PRIMARY_PLAN_INDEX
                || (event.planIndex === FmsUtils.DTO_RANDOM_PLAN_INDEX && this.flightPlanner.activePlanIndex === FmsUtils.DTO_RANDOM_PLAN_INDEX)) {
                this.needUpdateDestination = true;
            }
        }
        /**
         * Responds to when the active flight plan changes.
         */
        onActivePlanChanged() {
            this.needUpdateDestination = true;
        }
        /**
         * Responds to when a flight plan active leg changes.
         * @param event The event data describing the change.
         */
        onActiveLegChanged(event) {
            if (event.type === msfsSdk.ActiveLegType.Lateral) {
                if (event.planIndex === FmsUtils.PRIMARY_PLAN_INDEX) {
                    this.needUpdateDestination = true;
                }
            }
        }
        /**
         * Updates the primary flight plan's origin airport facility.
         * @param icao The ICAO of the origin airport facility, or `undefined` if there is no origin airport.
         */
        async updatePrimaryPlanOriginFacility(icao) {
            const opId = ++this.primaryPlanOriginFacilityOpId;
            if (icao === undefined) {
                this.primaryPlanOriginFacility = undefined;
                return;
            }
            const facility = await this.facilityLoader.getFacility(msfsSdk.FacilityType.Airport, icao);
            if (opId !== this.primaryPlanOriginFacilityOpId) {
                return;
            }
            this.primaryPlanOriginFacility = facility;
        }
        /**
         * Updates the primary flight plan's destination airport facility.
         * @param icao The ICAO of the destination airport facility, or `undefined` if there is no destination airport.
         */
        async updatePrimaryPlanDestinationFacility(icao) {
            const opId = ++this.primaryPlanDestinationFacilityOpId;
            if (icao === undefined) {
                this.primaryPlanDestinationFacility = undefined;
                return;
            }
            const facility = await this.facilityLoader.getFacility(msfsSdk.FacilityType.Airport, icao);
            if (opId !== this.primaryPlanDestinationFacilityOpId) {
                return;
            }
            this.primaryPlanDestinationFacility = facility;
        }
        /**
         * Computes the nav tracking data, such as XTK, DTK, and distance to turn.
         */
        computeTrackingData() {
            var _a, _b, _c, _d, _e;
            const magVar = this.magVar.get();
            let xtk = 0;
            let dtkLegIndex = -1;
            let dtkVectorIndex = -1;
            let dtkTrue = 0;
            let dtkMag = 0;
            let nextDtkLegIndex = -1;
            let nextDtkVectorIndex = -1;
            let nextDtkTrue = 0;
            let nextDtkMag = 0;
            let distance = 0;
            let waypointBearingTrue = 0;
            let waypointBearingMag = 0;
            let waypointIdent = '';
            let egressDistance = 0;
            let destinationDistance = -1;
            let toFrom = msfsSdk.VorToFrom.OFF;
            const activePlan = this.flightPlanner.hasActiveFlightPlan() ? this.flightPlanner.getActiveFlightPlan() : undefined;
            this.updateObsAvailable(activePlan ? activePlan.tryGetLeg(activePlan.activeLateralLeg) : null);
            if (this.needUpdateDestination) {
                this.updateDestination();
                this.needUpdateDestination = false;
            }
            if (this.lnavIsTracking.get()) {
                const isSuspended = this.lnavIsSuspended.get();
                const trackedLegIndex = this.lnavLegIndex.get();
                const nextLegIndex = trackedLegIndex + 1;
                const currentLeg = activePlan && trackedLegIndex >= 0 && trackedLegIndex < activePlan.length ? activePlan.getLeg(trackedLegIndex) : undefined;
                const nextLeg = activePlan && nextLegIndex >= 0 && nextLegIndex < activePlan.length ? activePlan.getLeg(nextLegIndex) : undefined;
                if (currentLeg === null || currentLeg === void 0 ? void 0 : currentLeg.calculated) {
                    distance = this.getActiveDistance(currentLeg, this.planePos);
                    destinationDistance = this.getDestinationDistance(trackedLegIndex, distance);
                    waypointIdent = (_a = currentLeg.name) !== null && _a !== void 0 ? _a : '';
                    if (currentLeg.calculated.endLat !== undefined && currentLeg.calculated.endLon) {
                        waypointBearingTrue = this.planePos.bearingTo(currentLeg.calculated.endLat, currentLeg.calculated.endLon);
                        waypointBearingMag = msfsSdk.MagVar.trueToMagnetic(waypointBearingTrue, magVar);
                    }
                }
                // Next DTK is only valid if we are actually going to sequence into the next leg, so we have to make sure LNAV is not suspended
                // and won't go into suspend at the end of the leg.
                if (nextLeg !== undefined
                    && ((_b = nextLeg.calculated) === null || _b === void 0 ? void 0 : _b.startLat) !== undefined && ((_c = nextLeg.calculated) === null || _c === void 0 ? void 0 : _c.startLon) !== undefined
                    && !isSuspended
                    && nextLeg.leg.type !== msfsSdk.LegType.Discontinuity
                    && (!msfsSdk.BitFlags.isAny(nextLeg.flags, msfsSdk.LegDefinitionFlags.MissedApproach) || this.isMaprActive.get())) {
                    const result = this.getNominalPathCircle(nextLeg, 0, msfsSdk.LNavTransitionMode.Ingress, this.nominalPathCircle);
                    if (result.vectorIndex >= 0) {
                        nextDtkLegIndex = nextLegIndex;
                        nextDtkVectorIndex = result.vectorIndex;
                        nextDtkTrue = result.circle.bearingAt(this.geoPointCache[0].set(nextLeg.calculated.startLat, nextLeg.calculated.startLon), Math.PI);
                        nextDtkMag = msfsSdk.MagVar.trueToMagnetic(nextDtkTrue, nextLeg.calculated.startLat, nextLeg.calculated.startLon);
                    }
                }
                if (this.isObsActive.get()) {
                    xtk = this.lnavXtk.get();
                    dtkLegIndex = trackedLegIndex;
                    dtkVectorIndex = -1;
                    dtkTrue = this.lnavDtk.get();
                    dtkMag = msfsSdk.MagVar.trueToMagnetic(dtkTrue, magVar);
                    egressDistance = this.lnavLegDistanceRemaining.get();
                    toFrom = egressDistance < 0 ? msfsSdk.VorToFrom.FROM : msfsSdk.VorToFrom.TO;
                }
                else {
                    const transitionMode = this.lnavTransitionMode.get();
                    let dtkVector;
                    let circle;
                    if (transitionMode === msfsSdk.LNavTransitionMode.Egress && ((_d = nextLeg === null || nextLeg === void 0 ? void 0 : nextLeg.calculated) === null || _d === void 0 ? void 0 : _d.flightPath.length)) {
                        const result = this.getNominalPathCircle(nextLeg, 0, msfsSdk.LNavTransitionMode.Ingress, this.nominalPathCircle);
                        if (result.vectorIndex >= 0) {
                            dtkLegIndex = nextLegIndex;
                            dtkVectorIndex = result.vectorIndex;
                            dtkVector = nextLeg.calculated.flightPath[dtkVectorIndex];
                            circle = result.circle;
                        }
                        egressDistance = msfsSdk.UnitType.METER.convertTo(nextLeg.calculated.distanceWithTransitions, msfsSdk.UnitType.NMILE) - NavdataComputer.getEgressDistance(nextLeg)
                            + this.lnavLegDistanceRemaining.get();
                    }
                    else if ((_e = currentLeg === null || currentLeg === void 0 ? void 0 : currentLeg.calculated) === null || _e === void 0 ? void 0 : _e.flightPath.length) {
                        const vectorIndex = this.lnavVectorIndex.get();
                        const result = this.getNominalPathCircle(currentLeg, vectorIndex, transitionMode, this.nominalPathCircle);
                        if (result.vectorIndex >= 0) {
                            dtkLegIndex = trackedLegIndex;
                            dtkVectorIndex = result.vectorIndex;
                            dtkVector = currentLeg.calculated.flightPath[dtkVectorIndex];
                            circle = result.circle;
                        }
                        if (msfsSdk.FlightPlanUtils.isManualDiscontinuityLeg(currentLeg.leg.type)) {
                            // MANSEQ legs aren't supposed to have an "end", so set egress distance to an arbitrarily large value.
                            egressDistance = Number.MAX_SAFE_INTEGER;
                        }
                        else {
                            egressDistance = this.lnavLegDistanceRemaining.get() - (
                            // Distance remaining published by LNAV does not include egress if suspend is active
                            isSuspended ? 0 : NavdataComputer.getEgressDistance(currentLeg));
                        }
                    }
                    if (circle !== undefined) {
                        xtk = msfsSdk.UnitType.GA_RADIAN.convertTo(circle.distance(this.planePos), msfsSdk.UnitType.NMILE);
                        dtkTrue = circle.bearingAt(this.planePos, Math.PI);
                        dtkMag = msfsSdk.MagVar.trueToMagnetic(dtkTrue, magVar);
                        const dtkLeg = dtkLegIndex === nextLegIndex ? nextLeg : currentLeg;
                        switch (dtkLeg.leg.type) {
                            case msfsSdk.LegType.AF:
                            case msfsSdk.LegType.RF:
                                toFrom = this.lnavLegDistanceRemaining.get() < 0 ? msfsSdk.VorToFrom.FROM : msfsSdk.VorToFrom.TO;
                                break;
                            default:
                                if (dtkVector && circle) {
                                    if (circle.isGreatCircle()) {
                                        const angleAlong = circle.angleAlong(this.planePos, this.geoPointCache[0].set(dtkVector.endLat, dtkVector.endLon), Math.PI);
                                        toFrom = angleAlong > Math.PI ? msfsSdk.VorToFrom.FROM : msfsSdk.VorToFrom.TO;
                                    }
                                    else {
                                        toFrom = this.lnavLegDistanceRemaining.get() < 0 ? msfsSdk.VorToFrom.FROM : msfsSdk.VorToFrom.TO;
                                    }
                                }
                        }
                    }
                }
            }
            this.lnavData.set('dtkTrue', dtkTrue);
            this.lnavData.set('dtkMag', dtkMag);
            this.lnavData.set('xtk', xtk);
            this.lnavData.set('nextDtkTrue', nextDtkTrue);
            this.lnavData.set('nextDtkMag', nextDtkMag);
            this.lnavData.set('waypointBearingTrue', waypointBearingTrue);
            this.lnavData.set('waypointBearingMag', waypointBearingMag);
            this.lnavData.set('waypointDistance', distance);
            this.lnavData.set('waypointIdent', waypointIdent);
            this.lnavData.set('destinationDistance', destinationDistance);
            this.updateDtkVector('lnavdata_dtk_vector', dtkLegIndex, dtkVectorIndex);
            this.updateDtkVector('lnavdata_next_dtk_vector', nextDtkLegIndex, nextDtkVectorIndex);
            this.lnavData.set('toFrom', toFrom);
            this.lnavData.set('egressDistance', egressDistance);
        }
        /**
         * Updates the LNAV destination airport.
         */
        updateDestination() {
            var _a;
            let destinationPlanIndex = undefined;
            let destinationIcao = undefined;
            let destinationRunwayIcao = undefined;
            let destinationLeg = undefined;
            const primaryPlan = this.flightPlanner.hasFlightPlan(FmsUtils.PRIMARY_PLAN_INDEX) ? this.flightPlanner.getFlightPlan(FmsUtils.PRIMARY_PLAN_INDEX) : undefined;
            if (primaryPlan) {
                if (primaryPlan.destinationAirport) {
                    // If the primary flight plan has a destination airport, then it is always the LNAV destination.
                    destinationPlanIndex = FmsUtils.PRIMARY_PLAN_INDEX;
                    destinationIcao = primaryPlan.destinationAirport;
                    destinationRunwayIcao = primaryPlan.procedureDetails.destinationRunway
                        ? msfsSdk.RunwayUtils.getRunwayFacilityIcao(destinationIcao, primaryPlan.procedureDetails.destinationRunway)
                        : undefined;
                    for (const leg of primaryPlan.legs(true)) {
                        if (!msfsSdk.BitFlags.isAll(leg.flags, msfsSdk.LegDefinitionFlags.MissedApproach)) {
                            destinationLeg = leg;
                            break;
                        }
                    }
                }
                else {
                    // Find the last airport in the primary flight plan that we have not yet sequenced.
                    let legIndex = primaryPlan.activeLateralLeg - 1;
                    for (const leg of primaryPlan.legs(true, undefined, primaryPlan.activeLateralLeg - 1)) {
                        if (msfsSdk.BitFlags.isAll(leg.flags, msfsSdk.LegDefinitionFlags.MissedApproach)) {
                            // Skip all missed approach legs.
                            continue;
                        }
                        else if (legIndex === 0 && ((_a = primaryPlan.getSegmentFromLeg(leg)) === null || _a === void 0 ? void 0 : _a.segmentType) === msfsSdk.FlightPlanSegmentType.Departure) {
                            // Skip the first leg of the flight plan if it is in the departure segment. This prevents us from selecting
                            // the origin airport.
                            break;
                        }
                        if (msfsSdk.ICAO.isFacility(leg.leg.fixIcao) && msfsSdk.ICAO.getFacilityType(leg.leg.fixIcao) === msfsSdk.FacilityType.Airport) {
                            destinationPlanIndex = FmsUtils.PRIMARY_PLAN_INDEX;
                            destinationIcao = leg.leg.fixIcao;
                            destinationLeg = leg;
                            break;
                        }
                        legIndex--;
                    }
                }
            }
            if (destinationIcao === undefined) {
                // If we can't find a destination in the primary flight plan, then check to see if we are on an off-route DTO
                // to an airport.
                const dtoRandomPlan = this.flightPlanner.hasFlightPlan(FmsUtils.DTO_RANDOM_PLAN_INDEX) ? this.flightPlanner.getFlightPlan(FmsUtils.DTO_RANDOM_PLAN_INDEX) : undefined;
                if (dtoRandomPlan && this.flightPlanner.activePlanIndex === FmsUtils.DTO_RANDOM_PLAN_INDEX) {
                    // The leg to the DTO target is always at the same index. Note that there may be additional legs after the leg
                    // to the target (e.g. a hold leg), but the destination leg is always considered to be the leg to the target.
                    const leg = dtoRandomPlan.tryGetLeg(FmsUtils.DTO_LEG_OFFSET - 1);
                    if (leg && msfsSdk.ICAO.isFacility(leg.leg.fixIcao) && msfsSdk.ICAO.getFacilityType(leg.leg.fixIcao) === msfsSdk.FacilityType.Airport) {
                        destinationPlanIndex = FmsUtils.DTO_RANDOM_PLAN_INDEX;
                        destinationIcao = leg.leg.fixIcao;
                        destinationLeg = leg;
                    }
                }
            }
            this.destinationPlanIndex = destinationPlanIndex;
            this.destinationIcao = destinationIcao;
            this.destinationLeg = destinationLeg;
            this.lnavData.set('destinationIcao', destinationIcao !== null && destinationIcao !== void 0 ? destinationIcao : '');
            this.lnavData.set('destinationRunwayIcao', destinationRunwayIcao !== null && destinationRunwayIcao !== void 0 ? destinationRunwayIcao : '');
            this.updateDestinationFacility(destinationIcao);
        }
        /**
         * Updates the LNAV destination airport facility.
         * @param icao The ICAO of the destination airport facility, or `undefined` if there is no destination airport.
         */
        async updateDestinationFacility(icao) {
            const opId = ++this.destinationFacilityOpId;
            if (icao === undefined) {
                this.destinationFacility = undefined;
                return;
            }
            const facility = await this.facilityLoader.getFacility(msfsSdk.FacilityType.Airport, icao);
            if (opId !== this.destinationFacilityOpId) {
                return;
            }
            this.destinationFacility = facility;
        }
        /**
         * Updates a nominal desired track vector, and publishes the data to the event bus if necessary.
         * @param topic The event bus topic associated with the vector.
         * @param globalLegIndex The global index of the leg to which the vector belongs, or `-1` if there is no vector.
         * @param vectorIndex The index of the vector in its parent leg's `flightPath` array, or `-1` if there is no vector.
         */
        updateDtkVector(topic, globalLegIndex, vectorIndex) {
            const dtkVector = topic === 'lnavdata_dtk_vector' ? this.dtkVector : this.nextDtkVector;
            const needUpdate = dtkVector.globalLegIndex !== globalLegIndex
                || dtkVector.vectorIndex !== vectorIndex;
            if (needUpdate) {
                dtkVector.globalLegIndex = globalLegIndex;
                dtkVector.vectorIndex = vectorIndex;
                this.publisher.pub(this.lnavTopicMap[topic], Object.assign({}, dtkVector), true, true);
            }
        }
        /**
         * Computes CDI scaling.
         */
        computeCdiScaling() {
            if (this.useVfrCdiScaling.get()) {
                this.computeVfrCdiScaling();
            }
            else {
                this.computeIfrCdiScaling();
            }
        }
        /**
         * Computes CDI scaling using VFR logic.
         */
        computeVfrCdiScaling() {
            let scale = 5.0;
            let scaleLabel = exports.CDIScaleLabel.VfrEnroute;
            const lnavIsTracking = this.lnavIsTracking.get();
            const flightPlan = this.flightPlanner.hasActiveFlightPlan() ? this.flightPlanner.getActiveFlightPlan() : undefined;
            const activeLegIndex = this.lnavLegIndex.get();
            if (lnavIsTracking && flightPlan && flightPlan.length > 0 && activeLegIndex < flightPlan.length) {
                const activeSegment = flightPlan.getSegment(flightPlan.getSegmentIndex(activeLegIndex));
                const approachSegment = FmsUtils.getApproachSegment(flightPlan);
                if (activeSegment.segmentType === msfsSdk.FlightPlanSegmentType.Approach) {
                    // If the active leg is part of an approach, then set CDI scale to 0.25 NM and label to VfrApproach.
                    scale = 0.25;
                    scaleLabel = exports.CDIScaleLabel.VfrApproach;
                }
                else {
                    // Find distance to closest airport in the active flight plan.
                    let distanceToAirport = Infinity;
                    for (let segmentIndex = 0; segmentIndex < flightPlan.segmentCount; segmentIndex++) {
                        const segment = flightPlan.tryGetSegment(segmentIndex);
                        if (segment) {
                            for (let segmentLegIndex = 0; segmentLegIndex < segment.legs.length; segmentLegIndex++) {
                                const leg = segment.legs[segmentLegIndex];
                                switch (leg.leg.type) {
                                    case msfsSdk.LegType.IF:
                                    case msfsSdk.LegType.TF:
                                    case msfsSdk.LegType.CF:
                                    case msfsSdk.LegType.DF:
                                    case msfsSdk.LegType.AF:
                                    case msfsSdk.LegType.RF:
                                    case msfsSdk.LegType.HF:
                                    case msfsSdk.LegType.HA:
                                    case msfsSdk.LegType.HM:
                                        if (msfsSdk.ICAO.isFacility(leg.leg.fixIcao, msfsSdk.FacilityType.Airport)
                                            && leg.calculated
                                            && leg.calculated.endLat !== undefined
                                            && leg.calculated.endLon !== undefined) {
                                            distanceToAirport = Math.min(distanceToAirport, msfsSdk.UnitType.GA_RADIAN.convertTo(this.planePos.distance(leg.calculated.endLat, leg.calculated.endLon), msfsSdk.UnitType.NMILE));
                                        }
                                        break;
                                }
                            }
                        }
                    }
                    // If distance to the closest airport is <= 31 NM, then reduce CDI scale down to 1.25 NM as distance to airport
                    // decreases from 31 NM to 30 NM. If distance is <= 30 NM, then set scale label to VfrTerminal.
                    if (distanceToAirport <= 31) {
                        scale = msfsSdk.MathUtils.lerp(distanceToAirport, 30, 31, 1.25, scale, true, true);
                        if (distanceToAirport <= 30) {
                            scaleLabel = exports.CDIScaleLabel.VfrTerminal;
                        }
                    }
                    if (approachSegment) {
                        // Find distance to faf or map.
                        let fafLeg = undefined;
                        let mapLeg = undefined;
                        for (let i = approachSegment.legs.length - 1; i >= 0 && (!fafLeg || !mapLeg); i--) {
                            const leg = approachSegment.legs[i];
                            if (msfsSdk.BitFlags.isAny(leg.flags, msfsSdk.LegDefinitionFlags.DirectTo | msfsSdk.LegDefinitionFlags.MissedApproach | msfsSdk.LegDefinitionFlags.VectorsToFinal)) {
                                continue;
                            }
                            if (!mapLeg && msfsSdk.BitFlags.isAny(leg.leg.fixTypeFlags, msfsSdk.FixTypeFlags.MAP)) {
                                mapLeg = leg;
                            }
                            else if (!fafLeg && msfsSdk.BitFlags.isAny(leg.leg.fixTypeFlags, msfsSdk.FixTypeFlags.FAF)) {
                                fafLeg = leg;
                            }
                        }
                        let distanceToFafOrMap = Infinity;
                        if (fafLeg
                            && fafLeg.calculated
                            && fafLeg.calculated.endLat !== undefined
                            && fafLeg.calculated.endLon !== undefined) {
                            distanceToFafOrMap = Math.min(distanceToFafOrMap, msfsSdk.UnitType.GA_RADIAN.convertTo(this.planePos.distance(fafLeg.calculated.endLat, fafLeg.calculated.endLon), msfsSdk.UnitType.NMILE));
                        }
                        if (mapLeg
                            && mapLeg.calculated
                            && mapLeg.calculated.endLat !== undefined
                            && mapLeg.calculated.endLon !== undefined) {
                            distanceToFafOrMap = Math.min(distanceToFafOrMap, msfsSdk.UnitType.GA_RADIAN.convertTo(this.planePos.distance(mapLeg.calculated.endLat, mapLeg.calculated.endLon), msfsSdk.UnitType.NMILE));
                        }
                        // If distance to the faf/map is <= 3 NM, then reduce CDI scale down to 0.25 NM as distance to the fix
                        // decreases from 3 NM to 2 NM. If distance is <= 2 NM, then set scale label to VfrApproach.
                        if (distanceToFafOrMap <= 3) {
                            scale = msfsSdk.MathUtils.lerp(distanceToFafOrMap, 2, 3, 0.25, scale, true, true);
                            if (distanceToFafOrMap <= 2) {
                                scaleLabel = exports.CDIScaleLabel.VfrApproach;
                            }
                        }
                    }
                }
            }
            this.lnavData.set('cdiScale', scale);
            this.lnavData.set('cdiScaleLabel', scaleLabel);
        }
        /**
         * Computes CDI scaling using IFR logic.
         */
        computeIfrCdiScaling() {
            var _a, _b, _c, _d, _e, _f;
            let scale = 2.0;
            let scaleLabel = exports.CDIScaleLabel.Enroute;
            const flightPlan = this.flightPlanner.hasActiveFlightPlan() ? this.flightPlanner.getActiveFlightPlan() : undefined;
            const activeLegIndex = this.lnavLegIndex.get();
            if (flightPlan && flightPlan.length > 0 && activeLegIndex < flightPlan.length) {
                const activeSegment = flightPlan.getSegment(flightPlan.getSegmentIndex(activeLegIndex));
                let previousLeg;
                try {
                    previousLeg = flightPlan.getLeg(activeLegIndex - 1);
                }
                catch ( /*Do nothing*/_g) { /*Do nothing*/ }
                if (activeSegment.segmentType === msfsSdk.FlightPlanSegmentType.Departure) {
                    // We are currently in the departure segment
                    scale = 0.3;
                    scaleLabel = exports.CDIScaleLabel.Departure;
                    const prevLegType = previousLeg === null || previousLeg === void 0 ? void 0 : previousLeg.leg.type;
                    if (prevLegType && prevLegType !== msfsSdk.LegType.IF && prevLegType !== msfsSdk.LegType.CA && prevLegType !== msfsSdk.LegType.FA) {
                        scale = 1.0;
                        scaleLabel = exports.CDIScaleLabel.Terminal;
                    }
                }
                else {
                    // We are not in the departure segment
                    if (this.primaryPlanOriginFacility !== undefined) {
                        const distance = msfsSdk.UnitType.GA_RADIAN.convertTo(this.planePos.distance(this.primaryPlanOriginFacility), msfsSdk.UnitType.NMILE);
                        scale = 2.0 - msfsSdk.NavMath.clamp(31 - distance, 0, 1);
                        if (distance <= 30) {
                            scaleLabel = exports.CDIScaleLabel.Terminal;
                        }
                    }
                    if (this.primaryPlanDestinationFacility !== undefined) {
                        const distance = msfsSdk.UnitType.GA_RADIAN.convertTo(this.planePos.distance(this.primaryPlanDestinationFacility), msfsSdk.UnitType.NMILE);
                        scale = 2.0 - msfsSdk.NavMath.clamp(31 - distance, 0, 1);
                        if (distance <= 30) {
                            scaleLabel = exports.CDIScaleLabel.Terminal;
                        }
                    }
                }
                //Check for distance from arrival start
                if (activeSegment.segmentType === msfsSdk.FlightPlanSegmentType.Arrival && activeSegment.legs.length > 1) {
                    const firstArrivalLeg = activeSegment.legs[1];
                    //If we're going from the start of the arrival (i.e. the second leg)
                    if (activeLegIndex === activeSegment.offset + 1
                        && ((_a = firstArrivalLeg.calculated) === null || _a === void 0 ? void 0 : _a.startLat) !== undefined
                        && ((_b = firstArrivalLeg.calculated) === null || _b === void 0 ? void 0 : _b.startLon) !== undefined
                        && ((_c = firstArrivalLeg.calculated) === null || _c === void 0 ? void 0 : _c.endLat) !== undefined
                        && ((_d = firstArrivalLeg.calculated) === null || _d === void 0 ? void 0 : _d.endLon) !== undefined) {
                        const distance = this.lnavLegDistanceAlong.get();
                        scale = 2.0 - msfsSdk.NavMath.clamp(distance, 0, 1);
                        if (distance >= 1) {
                            scaleLabel = exports.CDIScaleLabel.Terminal;
                        }
                    }
                    else if (activeLegIndex > activeSegment.offset + 1) {
                        scale = 1.0;
                        scaleLabel = exports.CDIScaleLabel.Terminal;
                    }
                }
                else if (activeSegment.segmentType === msfsSdk.FlightPlanSegmentType.Approach) {
                    scale = 1.0;
                    scaleLabel = exports.CDIScaleLabel.Terminal;
                    const fafIndex = this.getFafIndex(activeSegment);
                    const currentLeg = activeLegIndex >= 0 && activeLegIndex < flightPlan.length ? flightPlan.getLeg(activeLegIndex) : undefined;
                    if (fafIndex !== undefined && activeLegIndex === fafIndex) {
                        const fafCalc = flightPlan.getLeg(fafIndex).calculated;
                        if ((fafCalc === null || fafCalc === void 0 ? void 0 : fafCalc.endLat) !== undefined && (fafCalc === null || fafCalc === void 0 ? void 0 : fafCalc.endLon) !== undefined) {
                            const distance = msfsSdk.UnitType.GA_RADIAN.convertTo(this.planePos.distance(fafCalc.endLat, fafCalc.endLon), msfsSdk.UnitType.NMILE);
                            scale = 1.0 - (0.7 * (msfsSdk.NavMath.clamp(2 - distance, 0, 2) / 2));
                        }
                        scaleLabel = this.getApproachCdiScale();
                    }
                    else if (((_e = currentLeg === null || currentLeg === void 0 ? void 0 : currentLeg.calculated) === null || _e === void 0 ? void 0 : _e.endLat) && ((_f = currentLeg === null || currentLeg === void 0 ? void 0 : currentLeg.calculated) === null || _f === void 0 ? void 0 : _f.endLon) && fafIndex !== undefined && activeLegIndex > fafIndex) {
                        if (currentLeg && msfsSdk.BitFlags.isAll(currentLeg.flags, msfsSdk.LegDefinitionFlags.MissedApproach)) {
                            scale = 1.0;
                            scaleLabel = exports.CDIScaleLabel.MissedApproach;
                        }
                        else {
                            // TODO: this computation is incorrect for any approach that has >1 leg between the FAF and the missed approach
                            const legLength = currentLeg.calculated.distance;
                            const distance = msfsSdk.UnitType.GA_RADIAN.convertTo(this.planePos.distance(currentLeg.calculated.endLat, currentLeg.calculated.endLon), msfsSdk.UnitType.NMILE);
                            scale = 0.3 - (0.112 * (msfsSdk.NavMath.clamp(legLength - distance, 0, legLength) / legLength));
                            scaleLabel = this.getApproachCdiScale();
                        }
                    }
                }
            }
            this.lnavData.set('cdiScale', scale);
            this.lnavData.set('cdiScaleLabel', scaleLabel);
        }
        /**
         * Computes glidepath scaling.
         */
        computeGpScaling() {
            const gpServiceLevel = this.isVNavIndexValid ? this.gpServiceLevel.get() : exports.GlidepathServiceLevel.None;
            if (gpServiceLevel !== exports.GlidepathServiceLevel.None) {
                const maxScaleFeet = 492; // 150 meters
                const minScaleFeet = gpServiceLevel === exports.GlidepathServiceLevel.Lpv ? 49 : 148; // 15/45 meters
                const scale = msfsSdk.MathUtils.clamp(NavdataComputer.GLIDEPATH_SCALE_TAN * this.gpDistance.get(), minScaleFeet, maxScaleFeet);
                this.vnavData.set('gpScale', scale);
            }
            else {
                this.vnavData.set('gpScale', 0);
            }
        }
        /**
         * Gets the index of the FAF in a segment.
         * @param segment The segment to search.
         * @returns The index of the FAF if found.
         */
        getFafIndex(segment) {
            let fafLeg = segment.legs[segment.legs.length - 2];
            let fafIndex = segment.legs.length - 2;
            for (let i = 0; i < segment.legs.length; i++) {
                const leg = segment.legs[i];
                if (leg.leg.fixTypeFlags & msfsSdk.FixTypeFlags.FAF) {
                    fafLeg = leg;
                    fafIndex = i;
                    break;
                }
            }
            if (fafLeg !== undefined) {
                return segment.offset + fafIndex;
            }
            return undefined;
        }
        /**
         * Gets the geo circle describing the nominal path tracked by LNAV.
         * @param leg The flight plan leg currently tracked by LNAV.
         * @param vectorIndex The index of the vector currently tracked by LNAV.
         * @param transitionMode The current LNAV transition mode.
         * @param out The object to which to write the result.
         * @param out.vectorIndex The index of the flight path vector associated with the geo circle.
         * @param out.circle The geo circle.
         * @returns The geo circle describing the initial path of a flight plan leg.
         */
        getNominalPathCircle(leg, vectorIndex, transitionMode, 
        // eslint-disable-next-line jsdoc/require-jsdoc
        out) {
            var _a, _b;
            out.vectorIndex = -1;
            if (!leg.calculated) {
                return out;
            }
            const legCalc = leg.calculated;
            // Fallback resolution paths are equivalent to DF legs.
            if (!legCalc.endsInFallback && msfsSdk.BitFlags.isAll((_b = (_a = legCalc.flightPath[0]) === null || _a === void 0 ? void 0 : _a.flags) !== null && _b !== void 0 ? _b : 0, msfsSdk.FlightPathVectorFlags.Fallback | msfsSdk.FlightPathVectorFlags.Direct)) {
                return this.getNominalPathCircleForEndCourseLeg(legCalc, out);
            }
            switch (leg.leg.type) {
                case msfsSdk.LegType.FA:
                case msfsSdk.LegType.CA:
                case msfsSdk.LegType.VA:
                case msfsSdk.LegType.FM:
                case msfsSdk.LegType.VM:
                case msfsSdk.LegType.DF:
                case msfsSdk.LegType.CD:
                case msfsSdk.LegType.VD:
                case msfsSdk.LegType.CR:
                case msfsSdk.LegType.VR:
                case msfsSdk.LegType.CI:
                case msfsSdk.LegType.VI:
                    return this.getNominalPathCircleForEndCourseLeg(legCalc, out);
                case msfsSdk.LegType.HM:
                case msfsSdk.LegType.HF:
                case msfsSdk.LegType.HA:
                    return this.getNominalPathCircleForHoldLeg(legCalc, out);
                default: {
                    let nominalVectorIndex;
                    switch (transitionMode) {
                        case msfsSdk.LNavTransitionMode.Ingress:
                            nominalVectorIndex = 0;
                            break;
                        case msfsSdk.LNavTransitionMode.Egress:
                            nominalVectorIndex = legCalc.flightPath.length - 1;
                            break;
                        default:
                            nominalVectorIndex = vectorIndex;
                    }
                    const vector = legCalc.flightPath[nominalVectorIndex];
                    if (vector !== undefined) {
                        out.vectorIndex = nominalVectorIndex;
                        msfsSdk.FlightPathUtils.setGeoCircleFromVector(vector, out.circle);
                    }
                }
            }
            return out;
        }
        /**
         * Gets the geo circle describing the nominal path tracked by LNAV for a flight plan leg whose nominal path is
         * defined by the course at the end of the leg.
         * @param legCalc The calculations for the flight plan leg.
         * @param out The object to which to write the result.
         * @param out.vectorIndex The index of the flight path vector associated with the geo circle.
         * @param out.circle The geo circle.
         * @returns The geo circle describing the initial path of a flight plan leg.
         */
        getNominalPathCircleForEndCourseLeg(legCalc, 
        // eslint-disable-next-line jsdoc/require-jsdoc
        out) {
            out.vectorIndex = -1;
            const nominalVectorIndex = legCalc.flightPath.length - 1;
            const vector = legCalc.flightPath[nominalVectorIndex];
            if (!vector) {
                return out;
            }
            if (msfsSdk.FlightPathUtils.isVectorGreatCircle(vector)) {
                out.vectorIndex = nominalVectorIndex;
                msfsSdk.FlightPathUtils.setGeoCircleFromVector(vector, out.circle);
            }
            else {
                const turn = msfsSdk.FlightPathUtils.setGeoCircleFromVector(vector, out.circle);
                const turnEnd = this.geoPointCache[0].set(vector.endLat, vector.endLon);
                const bearingAtEnd = turn.bearingAt(turnEnd, Math.PI);
                if (!isNaN(bearingAtEnd)) {
                    out.vectorIndex = nominalVectorIndex;
                    out.circle.setAsGreatCircle(turnEnd, bearingAtEnd);
                }
            }
            return out;
        }
        /**
         * Gets the geo circle describing the nominal path tracked by LNAV for a hold leg.
         * @param legCalc The calculations for the hold leg.
         * @param out The object to which to write the result.
         * @param out.vectorIndex The index of the flight path vector associated with the geo circle.
         * @param out.circle The geo circle.
         * @returns The geo circle describing the initial path of a flight plan leg.
         */
        getNominalPathCircleForHoldLeg(legCalc, 
        // eslint-disable-next-line jsdoc/require-jsdoc
        out) {
            out.vectorIndex = -1;
            // The last base flight path vector for hold legs should always be the inbound leg
            if (legCalc.flightPath.length > 0) {
                out.vectorIndex = legCalc.flightPath.length - 1;
                msfsSdk.FlightPathUtils.setGeoCircleFromVector(legCalc.flightPath[out.vectorIndex], out.circle);
            }
            return out;
        }
        /**
         * Gets the active distance from the plane position to the leg end.
         * @param leg The leg to get the distance for.
         * @param pos The current plane position.
         * @returns The distance, in nautical miles.
         */
        getActiveDistance(leg, pos) {
            var _a;
            const finalVector = (_a = leg.calculated) === null || _a === void 0 ? void 0 : _a.flightPath[leg.calculated.flightPath.length - 1];
            if (finalVector !== undefined) {
                return msfsSdk.UnitType.GA_RADIAN.convertTo(pos.distance(finalVector.endLat, finalVector.endLon), msfsSdk.UnitType.NMILE);
            }
            return 0;
        }
        /**
         * Gets the distance remaining, in nautical miles, to the LNAV destination.
         * @param activeLegIndex The global leg index of the active flight plan leg.
         * @param activeLegDistance The distance from the airplane's current position to the end of the active leg, in
         * nautical miles.
         * @returns The distance remaining, in nautical miles, to the LNAV destination, or `-1` if the distance cannot be
         * calculated.
         */
        getDestinationDistance(activeLegIndex, activeLegDistance) {
            var _a, _b, _c, _d, _e;
            if (this.destinationPlanIndex === undefined || this.destinationIcao === undefined) {
                return -1;
            }
            const destinationPlan = this.flightPlanner.hasFlightPlan(this.destinationPlanIndex) ? this.flightPlanner.getFlightPlan(this.destinationPlanIndex) : undefined;
            if (!destinationPlan) {
                return -1;
            }
            if (this.flightPlanner.activePlanIndex === this.destinationPlanIndex) {
                // The flight plan containing the destination leg is the active flight plan. In this case, the distance to
                // destination should be calculated as the along-track distance from the airplane to the destination (with one
                // exception if we have already sequenced the destination leg - see the case below).
                const activeLegCumDistance = (_b = (_a = destinationPlan.tryGetLeg(activeLegIndex)) === null || _a === void 0 ? void 0 : _a.calculated) === null || _b === void 0 ? void 0 : _b.cumulativeDistanceWithTransitions;
                const destinationLegCumDistance = (_d = (_c = this.destinationLeg) === null || _c === void 0 ? void 0 : _c.calculated) === null || _d === void 0 ? void 0 : _d.cumulativeDistanceWithTransitions;
                if (activeLegCumDistance === undefined || destinationLegCumDistance === undefined) {
                    return -1;
                }
                else if (destinationLegCumDistance - activeLegCumDistance < 0) {
                    // The destination leg cumulative distance is less than the active leg cumulative distance. This means we have
                    // sequenced past the destination leg. In this case, we want to revert to a great-circle distance calculation
                    // if and only if the LNAV destination is the primary flight plan's destination airport or the LNAV destination
                    // is the off-route direct-to target. Therefore, if either of these conditions is met, then we will let the
                    // code fall through to the default case below. If neither is met, then the chosen destination is invalid, so
                    // we will return -1.
                    if (this.destinationPlanIndex === FmsUtils.PRIMARY_PLAN_INDEX && destinationPlan.destinationAirport !== this.destinationIcao) {
                        return -1;
                    }
                }
                else {
                    return msfsSdk.UnitType.METER.convertTo(destinationLegCumDistance - activeLegCumDistance, msfsSdk.UnitType.NMILE) + activeLegDistance;
                }
            }
            // If we have reached this point, then calculate the distance to destination as the great-circle distance from the
            // airplane to the destination.
            if (((_e = this.destinationLeg) === null || _e === void 0 ? void 0 : _e.calculated) && this.destinationLeg.calculated.endLat && this.destinationLeg.calculated.endLon) {
                return msfsSdk.UnitType.GA_RADIAN.convertTo(this.planePos.distance(this.destinationLeg.calculated.endLat, this.destinationLeg.calculated.endLon), msfsSdk.UnitType.NMILE);
            }
            else if (this.destinationFacility) {
                return msfsSdk.UnitType.GA_RADIAN.convertTo(this.planePos.distance(this.destinationFacility), msfsSdk.UnitType.NMILE);
            }
            else {
                return -1;
            }
        }
        /**
         * Updates whether OBS is available based on the current active flight plan leg, and sends a control event if OBS
         * availability has changed since the last update.
         * @param activeLeg The active flight plan leg, or `null` if none exists.
         */
        updateObsAvailable(activeLeg) {
            let newObsAvailable = false;
            if (activeLeg) {
                switch (activeLeg.leg.type) {
                    case msfsSdk.LegType.AF:
                    case msfsSdk.LegType.CD:
                    case msfsSdk.LegType.CF:
                    case msfsSdk.LegType.CR:
                    case msfsSdk.LegType.DF:
                    case msfsSdk.LegType.IF:
                    case msfsSdk.LegType.RF:
                    case msfsSdk.LegType.TF:
                        newObsAvailable = true;
                        break;
                }
            }
            this.obsAvailable.set(newObsAvailable);
        }
        /**
         * Checks and returns the CDI Scale when in an approach.
         * @returns The CDIScaleLabel appropriate for the approach.
         */
        getApproachCdiScale() {
            switch (this.approachDetails.type) {
                case ApproachType.APPROACH_TYPE_GPS:
                case ApproachType.APPROACH_TYPE_RNAV:
                    if (this.approachDetails.isRnpAr) {
                        return exports.CDIScaleLabel.RNP;
                    }
                    switch (this.isVNavIndexValid ? this.gpServiceLevel.get() : exports.GlidepathServiceLevel.None) {
                        case exports.GlidepathServiceLevel.LNavPlusV:
                        case exports.GlidepathServiceLevel.LNavPlusVBaro:
                            return exports.CDIScaleLabel.LNavPlusV;
                        case exports.GlidepathServiceLevel.LNavVNav:
                        case exports.GlidepathServiceLevel.LNavVNavBaro:
                            return exports.CDIScaleLabel.LNavVNav;
                        case exports.GlidepathServiceLevel.LpPlusV:
                            return exports.CDIScaleLabel.LPPlusV;
                        case exports.GlidepathServiceLevel.Lpv:
                            return exports.CDIScaleLabel.LPV;
                        default:
                            return exports.CDIScaleLabel.LNav;
                    }
                case msfsSdk.AdditionalApproachType.APPROACH_TYPE_VISUAL:
                    return exports.CDIScaleLabel.Visual;
            }
            return exports.CDIScaleLabel.Terminal;
        }
        /**
         * Gets the total distance of the egress transition of a flight plan leg, in nautical miles.
         * @param leg The leg to get the distance for.
         * @returns The total distance distance of the egress transition of the specified flight plan leg, in nautical miles.
         */
        static getEgressDistance(leg) {
            if (leg.calculated === undefined) {
                return 0;
            }
            let distance = 0;
            for (let i = 0; i < leg.calculated.egress.length; i++) {
                distance += leg.calculated.egress[i].distance;
            }
            return msfsSdk.UnitType.METER.convertTo(distance, msfsSdk.UnitType.NMILE);
        }
    }
    NavdataComputer.GLIDEPATH_ANGULAR_SCALE = 0.8; // degrees
    NavdataComputer.GLIDEPATH_SCALE_TAN = Math.tan(NavdataComputer.GLIDEPATH_ANGULAR_SCALE * Avionics.Utils.DEG2RAD);

    /**
     * Publishes select {@link NavEvents} topics in lieu of NavProcessor.
     */
    class NavEventsPublisher {
        /**
         * Creates an instance of NavEventPublisher.
         * @param bus The event bus.
         */
        constructor(bus) {
            this.bus = bus;
            this.republisher = new msfsSdk.EventRepublisher(this.bus);
            this._isPublishing = false;
        }
        /**
         * Start publishing.
         */
        startPublish() {
            if (this._isPublishing) {
                return;
            }
            this._isPublishing = true;
            this.republisher.startRepublish('gps_obs_active_simvar', 'gps_obs_active', false, true);
            this.republisher.startRepublish('gps_obs_value_simvar', 'gps_obs_value', false, true);
            this.republisher.startRepublish('marker_beacon_state', 'mkr_bcn_state', false, true);
        }
        /**
         * Stop publishing.
         */
        stopPublish() {
            if (!this._isPublishing) {
                return;
            }
            this._isPublishing = false;
            this.republisher.clearRepublishes();
        }
        /**
         * A callback called when this publisher receives an update cycle.
         */
        onUpdate() {
            return;
        }
    }

    /**
     * LNAV OBS/suspend modes.
     */
    exports.ObsSuspModes = void 0;
    (function (ObsSuspModes) {
        ObsSuspModes[ObsSuspModes["NONE"] = 0] = "NONE";
        ObsSuspModes[ObsSuspModes["SUSP"] = 1] = "SUSP";
        ObsSuspModes[ObsSuspModes["OBS"] = 2] = "OBS";
    })(exports.ObsSuspModes || (exports.ObsSuspModes = {}));
    /**
     * A default implementation of {@link ObsSuspDataProvider}.
     */
    class DefaultObsSuspDataProvider {
        /**
         * Creates a new instance of DefaultObsSuspDataProvider.
         * @param bus The event bus.
         * @param options Options with which to configure the data provider.
         */
        constructor(bus, options) {
            var _a;
            this.bus = bus;
            this.isLNavIndexValid = msfsSdk.Subject.create(false);
            this.isLNavSuspended = msfsSdk.ConsumerSubject.create(null, false).pause();
            this.isObsActive = msfsSdk.ConsumerSubject.create(null, false).pause();
            /** @inheritDoc */
            this.mode = msfsSdk.MappedSubject.create(([isLNavIndexValid, isLNavSuspended, isObsActive]) => {
                if (!isLNavIndexValid) {
                    return exports.ObsSuspModes.NONE;
                }
                return isObsActive
                    ? exports.ObsSuspModes.OBS
                    : isLNavSuspended ? exports.ObsSuspModes.SUSP : exports.ObsSuspModes.NONE;
            }, this.isLNavIndexValid, this.isLNavSuspended, this.isObsActive);
            this._isObsAvailable = msfsSdk.ConsumerSubject.create(null, false).pause();
            /** @inheritDoc */
            this.isObsAvailable = this._isObsAvailable;
            this._obsCourse = msfsSdk.ConsumerSubject.create(null, 0).pause();
            /** @inheritDoc */
            this.obsCourse = this._obsCourse;
            this.isAlive = true;
            this.isInit = false;
            this.isPaused = true;
            this.lnavIndex = msfsSdk.SubscribableUtils.toSubscribable((_a = options === null || options === void 0 ? void 0 : options.lnavIndex) !== null && _a !== void 0 ? _a : 0, true);
        }
        /**
         * Initializes this data provider. Once initialized, this data provider will continuously update its data until
         * paused or destroyed.
         * @param paused Whether to initialize this data provider as paused. If `true`, this data provider will provide an
         * initial set of data but will not update the provided data until it is resumed. Defaults to `false`.
         * @throws Error if this data provider is dead.
         */
        init(paused = false) {
            if (!this.isAlive) {
                throw new Error('DefaultObsSuspDataProvider: cannot initialize a dead provider');
            }
            if (this.isInit) {
                return;
            }
            this.isInit = true;
            const sub = this.bus.getSubscriber();
            this.lnavIndexSub = this.lnavIndex.sub(index => {
                if (Number.isInteger(index) && index >= 0) {
                    const suffix = msfsSdk.LNavUtils.getEventBusTopicSuffix(index);
                    this.isLNavSuspended.setConsumer(sub.on(`lnav_is_suspended${suffix}`));
                    this._isObsAvailable.setConsumer(sub.on(`obs_available${suffix}`));
                    this.isObsActive.setConsumer(sub.on(`lnav_obs_active${suffix}`));
                    this._obsCourse.setConsumer(sub.on(`lnav_obs_course${suffix}`));
                    this.isLNavIndexValid.set(true);
                }
                else {
                    this.isLNavIndexValid.set(false);
                    this.isLNavSuspended.setConsumer(null);
                    this._isObsAvailable.setConsumer(null);
                    this.isObsActive.setConsumer(null);
                    this._obsCourse.setConsumer(null);
                }
            }, true);
            if (!paused) {
                this.resume();
            }
        }
        /**
         * Resumes this data provider. Once resumed, this data provider will continuously update its data until paused or
         * destroyed.
         * @throws Error if this data provider is dead.
         */
        resume() {
            if (!this.isAlive) {
                throw new Error('DefaultObsSuspDataProvider: cannot resume a dead provider');
            }
            if (!this.isInit || !this.isPaused) {
                return;
            }
            this.isPaused = false;
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            this.lnavIndexSub.resume(true);
            this.isLNavSuspended.resume();
            this.isObsActive.resume();
            this._isObsAvailable.resume();
            this._obsCourse.resume();
        }
        /**
         * Pauses this data provider. Once paused, this data provider will not update its data until it is resumed.
         * @throws Error if this data provider is dead.
         */
        pause() {
            if (!this.isAlive) {
                throw new Error('DefaultObsSuspDataProvider: cannot pause a dead provider');
            }
            if (!this.isInit || this.isPaused) {
                return;
            }
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            this.lnavIndexSub.pause();
            this.isLNavSuspended.pause();
            this.isObsActive.pause();
            this._isObsAvailable.resume();
            this._obsCourse.pause();
            this.isPaused = true;
        }
        /**
         * Destroys this data provider. Once destroyed, this data provider will no longer update its provided data, and can
         * no longer be paused or resumed.
         */
        destroy() {
            var _a;
            this.isAlive = false;
            this.isLNavSuspended.destroy();
            this.isObsActive.destroy();
            this._isObsAvailable.destroy();
            this._obsCourse.destroy();
            (_a = this.lnavIndexSub) === null || _a === void 0 ? void 0 : _a.destroy();
        }
    }

    exports.NavSensitivity = void 0;
    (function (NavSensitivity) {
        NavSensitivity["DPRT"] = "DPRT";
        NavSensitivity["TERM"] = "TERM";
        NavSensitivity["ENR"] = "ENR";
        NavSensitivity["OCN"] = "OCN";
        NavSensitivity["LNAV"] = "LNAV";
        NavSensitivity["LNAVplusV"] = "LNAV+V";
        NavSensitivity["VIS"] = "VISUAL";
        NavSensitivity["LVNAV"] = "L/VNAV";
        NavSensitivity["LPV"] = "LPV";
        NavSensitivity["LP"] = "LP";
        NavSensitivity["LPplusV"] = "LP+V";
        NavSensitivity["MAPR"] = "MAPR";
        NavSensitivity["VOR"] = "VOR";
        NavSensitivity["ILS"] = "ILS";
    })(exports.NavSensitivity || (exports.NavSensitivity = {}));
    exports.VNavDisplayMode = void 0;
    (function (VNavDisplayMode) {
        VNavDisplayMode[VNavDisplayMode["NONE"] = 0] = "NONE";
        VNavDisplayMode[VNavDisplayMode["PATH"] = 1] = "PATH";
    })(exports.VNavDisplayMode || (exports.VNavDisplayMode = {}));
    exports.GPDisplayMode = void 0;
    (function (GPDisplayMode) {
        GPDisplayMode[GPDisplayMode["NONE"] = 0] = "NONE";
        GPDisplayMode[GPDisplayMode["PREVIEW"] = 1] = "PREVIEW";
        GPDisplayMode[GPDisplayMode["ACTIVE"] = 2] = "ACTIVE";
    })(exports.GPDisplayMode || (exports.GPDisplayMode = {}));
    /**
     * Encapsulation of the logic for an nav source.
     */
    class HsiSource {
        /**
         * Create an HSI Source
         * @param id The navsourceid.
         */
        constructor(id) {
            this.valid = false;
            this.bearing = null;
            this.distance = null;
            this.deviation = null;
            this.deviationScale = 1.0;
            this.deviationScaleLabel = null;
            this.toFrom = msfsSdk.VorToFrom.OFF;
            this.dtk_obs = null;
            this.isLocalizer = false;
            this.hasSignal = false;
            this.hasLocalizer = false;
            this.localizerCourse = null;
            this.hasGlideslope = false;
            this.gsDeviation = null;
            this.altDeviation = null;
            this.hasDme = false;
            this.frequency = null;
            this.source = id;
            if (this.source.type === msfsSdk.NavSourceType.Nav) {
                this.dtk_obs = 0;
            }
        }
    }
    /**
     * A NavIndicatorController to control what nav sources are being indicated on the panel.
     */
    class NavIndicatorController {
        /**
         * Initialize an instance of the NavIndicatorController.
         * @param bus is the event bus
         * @param fms is the fms
         */
        constructor(bus, fms) {
            this.fms = fms;
            this.navStates = [];
            this.activeSensitivity = exports.NavSensitivity.VOR;
            this.activeSourceIndex = 0;
            this.hsiMapActive = false;
            this.courseNeedleRefs = { hsiRose: undefined, hsiMap: undefined };
            this.hsiRefs = { hsiRose: undefined, hsiMap: undefined };
            this.hsiMapDeviationRef = undefined;
            this.vdi = undefined;
            this.bearingPointerStatus = [false, false];
            this.bearingPointerAdf = [false, false];
            this.bearingPointerDirection = [null, null];
            this.bearingPointerSourceIdxs = [-1, -1];
            this.bearingValidity = [false, false];
            this.firstRun = true;
            this.obsSuspMode = exports.ObsSuspModes.NONE;
            this.missedApproachActive = false;
            this.lnavLegType = msfsSdk.LegType.Discontinuity;
            this.currentSpeed = 30;
            this.currentHeading = 0;
            this.currentAltitude = 0;
            this.currentVNavTodDistance = -1;
            this.currentVNavBodDistance = -1;
            this.vnavPathInRange = false;
            this.currentVNavTargetAltitude = -1;
            this.currentVNavConstraintAltitude = -1;
            this.currentVNavFpa = 0;
            this.vnavState = msfsSdk.VNavState.Enabled_Inactive;
            this.currentVNavPathMode = msfsSdk.VNavPathMode.None;
            this.vnavDisplayMode = msfsSdk.Subject.create(exports.VNavDisplayMode.NONE);
            this.gpDisplayMode = msfsSdk.Subject.create(exports.GPDisplayMode.NONE);
            this.currentVnavApproachMode = msfsSdk.ApproachGuidanceMode.None;
            this.currentLpvDeviation = Number.POSITIVE_INFINITY;
            this.currentLpvDistance = Number.POSITIVE_INFINITY;
            this.dmeSourceIndex = msfsSdk.Subject.create(0);
            this.dmeDistanceSubject = msfsSdk.Subject.create(-1);
            this.isLnavCalculating = msfsSdk.Subject.create(false);
            this.shouldDisplayPathMode = msfsSdk.Subject.create(false);
            this.approachDetails = {
                isLoaded: false,
                type: ApproachType.APPROACH_TYPE_UNKNOWN,
                isRnpAr: false,
                bestRnavType: msfsSdk.RnavTypeFlags.None,
                rnavTypeFlags: msfsSdk.RnavTypeFlags.None,
                isCircling: false,
                isVtf: false,
                referenceFacility: null,
                runway: null
            };
            this.flightPhase = {
                isApproachActive: false,
                isToFaf: false,
                isPastFaf: false,
                isInMissedApproach: false
            };
            /**
             * A callback called when the CDI Source Changes.
             * @param source The current selected CDI Source.
             */
            this.onUpdateCdiSelect = (source) => {
                if (source.type !== this.navStates[this.activeSourceIndex].source.type
                    || source.index !== this.navStates[this.activeSourceIndex].source.index) {
                    switch (source.type) {
                        case msfsSdk.NavSourceType.Nav:
                            if (source.index == 1) {
                                this.activeSourceIndex = 0;
                            }
                            else {
                                this.activeSourceIndex = 1;
                            }
                            if (this.navStates[this.activeSourceIndex].isLocalizer && this.navStates[this.activeSourceIndex].hasLocalizer) {
                                this.slewObs();
                            }
                            break;
                        case msfsSdk.NavSourceType.Gps:
                            this.activeSourceIndex = 2;
                            break;
                    }
                    this.updateSensitivity();
                    this.updateVNavDisplayMode();
                }
            };
            /**
             * A callback called when the obs updates from the event bus.
             * @param obs The current obs/dtk value.
             */
            this.onUpdateDtk = (obs) => {
                if (obs.source.type === msfsSdk.NavSourceType.Nav) {
                    switch (obs.source.index) {
                        case 1:
                            this.navStates[0].dtk_obs = obs.heading;
                            break;
                        case 2:
                            this.navStates[1].dtk_obs = obs.heading;
                            break;
                    }
                    this.updateComponentsData(obs.source);
                }
            };
            /**
             * A callback called when the lnav dtk updates from the event bus.
             * @param dtk The current lnav dtk value.
             */
            this.onUpdateLnavDtk = (dtk) => {
                if (!this.isLnavCalculating.get()) {
                    this.navStates[2].dtk_obs = this.currentHeading;
                }
                else if (dtk !== this.navStates[2].dtk_obs) {
                    this.navStates[2].dtk_obs = dtk;
                }
                if (this.activeSourceIndex == 2) {
                    this.setLnavToFrom();
                    this.updateComponentsData(this.navStates[2].source);
                }
            };
            /**
             * A callback called when the lnav xtk updates from the event bus.
             * @param xtk The current lnav xtk value.
             */
            this.onUpdateLnavXtk = (xtk) => {
                // Check for both a full or direct to flight plan.
                if (!this.isLnavCalculating.get()) {
                    if (this.navStates[2].toFrom !== msfsSdk.VorToFrom.OFF) {
                        this.navStates[2].toFrom = msfsSdk.VorToFrom.OFF;
                        this.updateComponentsDisplay(this.navStates[2].source);
                    }
                }
                else if (this.navStates[2].deviation === null || -xtk !== (this.navStates[2].deviation * this.navStates[2].deviationScale)) {
                    this.navStates[2].deviation = (-xtk / this.navStates[2].deviationScale);
                    if (this.navStates[2].toFrom === msfsSdk.VorToFrom.OFF) {
                        this.setLnavToFrom();
                        this.updateComponentsDisplay(this.navStates[2].source);
                    }
                }
                if (this.activeSourceIndex == 2) {
                    this.updateComponentsData();
                }
            };
            /**
             * A callback called when the bearing to an lnav fix updates across the event bus to set the to/from flag for GPS.
             * @param brg The current bearing to the current fix.
             */
            this.onUpdateLnavBrg = (brg) => {
                this.navStates[2].bearing = brg;
                this.setLnavToFrom() && this.updateComponentsDisplay(this.navStates[2].source);
            };
            /**
             * A callback called when the cdi deviation updates from the event bus.
             * @param deviation The current deviation value.
             */
            this.onUpdateCdiDeviation = (deviation) => {
                if (deviation.source.type !== msfsSdk.NavSourceType.Nav) {
                    return;
                }
                switch (deviation.source.index) {
                    case this.navStates[0].source.index:
                        this.navStates[0].deviation = deviation.deviation !== null ? deviation.deviation / 127 : -100;
                        break;
                    case this.navStates[1].source.index:
                        this.navStates[1].deviation = deviation.deviation !== null ? deviation.deviation / 127 : -100;
                        break;
                }
                this.updateComponentsData(deviation.source);
            };
            /**
             * A callback called when the vor to/from updates from the event bus.
             * @param toFrom The current to/from value.
             */
            this.onUpdateToFrom = (toFrom) => {
                if (toFrom.source.type !== msfsSdk.NavSourceType.Nav) {
                    return;
                }
                switch (toFrom.source.index) {
                    case this.navStates[0].source.index:
                        this.navStates[0].toFrom = toFrom.toFrom;
                        break;
                    case this.navStates[1].source.index:
                        this.navStates[1].toFrom = toFrom.toFrom;
                        break;
                }
                this.updateComponentsDisplay(toFrom.source);
            };
            /**
             * A callback called when the dme updates from the event bus.
             * @param dme The current deviation value.
             */
            this.onUpdateDme = (dme) => {
                if (dme.source.type !== msfsSdk.NavSourceType.Nav) {
                    return;
                }
                switch (dme.source.index) {
                    case this.navStates[0].source.index:
                        this.navStates[0].hasDme = dme.hasDme;
                        this.navStates[0].distance = dme.dmeDistance;
                        break;
                    case this.navStates[1].source.index:
                        this.navStates[1].hasDme = dme.hasDme;
                        this.navStates[1].distance = dme.dmeDistance;
                        break;
                }
                const dmeSource = this.dmeSourceIndex.get();
                const dmeDistance = this.navStates[dmeSource].distance;
                if (this.navStates[dmeSource].hasDme && dmeDistance !== null && dmeDistance > 0) {
                    this.dmeDistanceSubject.set(dmeDistance);
                }
                else {
                    this.dmeDistanceSubject.set(-1);
                }
            };
            /**
             * A callback called when the localizer data updates from the event bus.
             * @param localizer The current localizer data.
             */
            this.onUpdateLocalizer = (localizer) => {
                if (localizer.source.type !== msfsSdk.NavSourceType.Nav) {
                    return;
                }
                switch (localizer.source.index) {
                    case this.navStates[0].source.index:
                        this.navStates[0].hasLocalizer = localizer.isValid;
                        if (localizer.isValid) {
                            this.navStates[0].localizerCourse = localizer.course;
                        }
                        break;
                    case this.navStates[1].source.index:
                        this.navStates[1].hasLocalizer = localizer.isValid;
                        if (localizer.isValid) {
                            this.navStates[1].localizerCourse = localizer.course;
                        }
                        break;
                }
                this.slewObs();
                this.updateSensitivity(localizer.source);
                this.updateVNavDisplayMode();
            };
            /**
             * A callback called when the glideslope data updates from the event bus.
             * @param glideslope The current glideslope data.
             */
            this.onUpdateGlideslope = (glideslope) => {
                var _a, _b;
                if (glideslope.source.type !== msfsSdk.NavSourceType.Nav) {
                    return;
                }
                switch (glideslope.source.index) {
                    case this.navStates[0].source.index:
                        if (glideslope.isValid == this.navStates[0].hasGlideslope && glideslope.isValid) {
                            this.navStates[0].gsDeviation = glideslope.deviation;
                            (_a = this.vdi) === null || _a === void 0 ? void 0 : _a.updateDeviation();
                            return;
                        }
                        else {
                            this.navStates[0].hasGlideslope = glideslope.isValid;
                            if (glideslope.isValid) {
                                this.navStates[0].gsDeviation = glideslope.deviation;
                            }
                            this.updateVNavDisplayMode();
                        }
                        break;
                    case this.navStates[1].source.index:
                        if (glideslope.isValid == this.navStates[1].hasGlideslope && glideslope.isValid) {
                            this.navStates[1].gsDeviation = glideslope.deviation;
                            (_b = this.vdi) === null || _b === void 0 ? void 0 : _b.updateDeviation();
                            return;
                        }
                        else {
                            this.navStates[1].hasGlideslope = glideslope.isValid;
                            if (glideslope.isValid) {
                                this.navStates[1].gsDeviation = glideslope.deviation;
                            }
                            this.updateVNavDisplayMode();
                        }
                        break;
                }
            };
            /**
             * A callback called when isLoc value updates from the event bus.
             * @param isLoc The current isLoc value.
             */
            this.onUpdateIsLocFreq = (isLoc) => {
                if (isLoc.source.type !== msfsSdk.NavSourceType.Nav) {
                    return;
                }
                switch (isLoc.source.index) {
                    case this.navStates[0].source.index:
                        this.navStates[0].isLocalizer = isLoc.isLocalizer;
                        break;
                    case this.navStates[1].source.index:
                        this.navStates[1].isLocalizer = isLoc.isLocalizer;
                        break;
                }
                this.updateComponentsDisplay(isLoc.source);
            };
            /**
             * Update the source of a bearing pointer.
             * @param data The new bearing source info.
             */
            this.updateBearingSrc = (data) => {
                var _a;
                if (data.source === null || ((_a = data.source) === null || _a === void 0 ? void 0 : _a.type) === undefined) {
                    this.bearingPointerStatus[data.index] = false;
                    this.bearingPointerAdf[data.index] = false;
                    this.bearingPointerSourceIdxs[data.index] = -1;
                }
                else {
                    this.bearingPointerSourceIdxs[data.index] = this.getNavSourceIndex(data.source);
                    this.bearingPointerStatus[data.index] = true;
                    if (data.source.type === msfsSdk.NavSourceType.Adf) {
                        this.bearingPointerAdf[data.index] = true;
                    }
                    else {
                        this.bearingPointerAdf[data.index] = false;
                    }
                }
                if (this.bearingPointerDirection[data.index] !== null) {
                    this.updateBearingDir({ index: data.index, direction: this.bearingPointerDirection[data.index] });
                }
                if (this.bearingPointerStatus[0] == true || this.bearingPointerStatus[1] == true) {
                    this.hsiRefs.hsiRose.instance.compassRoseComponent.instance.setCircleVisible(true);
                }
                else {
                    this.hsiRefs.hsiRose.instance.compassRoseComponent.instance.setCircleVisible(false);
                }
                this.updateBearingPointers(data.index, (element) => {
                    if (element !== null && element.instance !== null) {
                        if (data.source === null) {
                            element.instance.style.display = 'none';
                            return;
                        }
                        const source = data.source;
                        if (source.type !== msfsSdk.NavSourceType.Nav && source.type !== msfsSdk.NavSourceType.Gps && source.type !== msfsSdk.NavSourceType.Adf) {
                            element.instance.style.display = 'none';
                            this.bearingPointerStatus[data.index] = false;
                        }
                        else if (!this.bearingValidity[data.index] || (source.type == msfsSdk.NavSourceType.Nav && this.navStates[source.index - 1].isLocalizer)) {
                            element.instance.style.display = 'none';
                        }
                        else {
                            element.instance.style.display = '';
                        }
                    }
                });
            };
            /**
             * Update the validity of a bearing source.
             * @param data The validity event.
             */
            this.updateBearingValidity = (data) => {
                this.bearingValidity[data.index] = data.valid;
                this.updateBearingPointers(data.index, (element) => {
                    if (element !== null && element.instance !== null) {
                        if (data.valid && !this.navStates[this.bearingPointerSourceIdxs[data.index]].isLocalizer) {
                            element.instance.style.display = '';
                        }
                        else {
                            element.instance.style.display = 'none';
                        }
                    }
                });
            };
            /**
             * Update the heading of a bearing pointer.
             * @param data The BearingDirection message.
             */
            this.updateBearingDir = (data) => {
                let direction = data.direction;
                this.bearingPointerDirection[data.index] = direction;
                if (this.bearingPointerAdf[data.index] && data.direction !== null) {
                    direction = msfsSdk.NavMath.normalizeHeading(data.direction + this.currentHeading);
                }
                this.updateBearingPointers(data.index, (element) => {
                    if (element !== null && element.instance !== null && direction !== null) {
                        const newDirection = Math.round(direction * 100) / 100;
                        element.instance.style.transform = `rotate3d(0, 0, 1, ${newDirection}deg)`;
                    }
                    // We had previously hidden the pointer if the direction was null, but that causes initialization
                    // issues which can cause the pointer to stay masked at startup, and there should always be a
                    // direction if the signal is valid.  Pointer hiding is taken care of by the invalid-signal
                    // handling and doesn't need to be done here.
                });
            };
            this.bus = bus;
            for (let i = 0; i < 3; i++) {
                const type = i < 2 ? msfsSdk.NavSourceType.Nav : msfsSdk.NavSourceType.Gps;
                const index = i == 1 ? 2 : 1;
                const sourceId = { type: type, index: index };
                const source = new HsiSource(sourceId);
                source.toFrom = msfsSdk.VorToFrom.OFF;
                this.navStates.push(source);
            }
            this.monitorEvents();
            this.shouldDisplayPathMode.sub((v) => {
                this.bus.getPublisher().pub('vnav_path_display', v, true);
            });
        }
        /**
         * Method to monitor nav processor events to keep track of HSI-related data.
         */
        monitorEvents() {
            const fms = this.bus.getSubscriber();
            fms.on('fms_approach_details').handle(details => {
                this.approachDetails = details;
                this.updateSensitivity();
                this.onUpdateLpv(this.currentLpvDeviation, this.currentLpvDistance);
            });
            fms.on('fms_flight_phase').handle(phase => {
                this.flightPhase = phase;
                this.updateSensitivity();
                this.onUpdateLpv(this.currentLpvDeviation, this.currentLpvDistance);
            });
            this.bus.getSubscriber().on('ground_speed').handle(speed => this.currentSpeed = speed);
            const adahrs = this.bus.getSubscriber();
            adahrs.on('hdg_deg').withPrecision(1).handle(hdg => this.currentHeading = hdg);
            adahrs.on('indicated_alt').atFrequency(1).handle(alt => this.currentAltitude = alt);
            const navcom = this.bus.getSubscriber();
            navcom.on('set_frequency').handle((setFrequency) => {
                if (setFrequency.radio.radioType === msfsSdk.RadioType.Nav && setFrequency.bank == msfsSdk.FrequencyBank.Active) {
                    this.navStates[setFrequency.radio.index - 1].frequency = setFrequency.frequency;
                    if (this.getNavSourceIndex({ type: msfsSdk.NavSourceType.Nav, index: setFrequency.radio.index }) === this.activeSourceIndex) {
                        this.updateSensitivity();
                    }
                }
            });
            const nav = this.bus.getSubscriber();
            nav.on('cdi_select').handle(this.onUpdateCdiSelect);
            nav.on('obs_set').handle(this.onUpdateDtk);
            nav.on('cdi_deviation').handle(this.onUpdateCdiDeviation);
            nav.on('vor_to_from').handle(this.onUpdateToFrom);
            nav.on('localizer').handle(this.onUpdateLocalizer);
            nav.on('glideslope').handle(this.onUpdateGlideslope);
            nav.on('is_localizer_frequency').handle(this.onUpdateIsLocFreq);
            nav.on('brg_source').handle(this.updateBearingSrc);
            nav.on('brg_direction').handle(this.updateBearingDir);
            nav.on('dme_state').handle(this.onUpdateDme);
            nav.on('brg_validity').handle(this.updateBearingValidity);
            nav.on('gps_obs_active').handle(obsActive => {
                if (obsActive) {
                    this.obsSuspMode = exports.ObsSuspModes.OBS;
                }
                else {
                    this.obsSuspMode = exports.ObsSuspModes.NONE;
                }
                if (this.onUpdateDtkBox !== undefined) {
                    this.onUpdateDtkBox();
                }
                this.updateSensitivity();
            });
            const lnavEvents = this.bus.getSubscriber();
            lnavEvents.on('lnavdata_dtk_mag').handle(this.onUpdateLnavDtk);
            lnavEvents.on('lnavdata_xtk').handle(this.onUpdateLnavXtk);
            lnavEvents.on('lnavdata_waypoint_bearing_mag').whenChangedBy(5).handle(this.onUpdateLnavBrg);
            lnavEvents.on('lnavdata_cdi_scale').handle(scale => {
                this.navStates[2].deviationScale = scale;
            });
            lnavEvents.on('lnavdata_cdi_scale_label').handle(label => {
                this.navStates[2].deviationScaleLabel = label;
                this.updateSensitivity();
            });
            lnavEvents.on('lnav_is_suspended').whenChanged().handle(isSuspended => {
                if (isSuspended) {
                    this.obsSuspMode = exports.ObsSuspModes.SUSP;
                }
                else {
                    this.obsSuspMode = exports.ObsSuspModes.NONE;
                }
                this.updateSensitivity();
            });
            lnavEvents.on('lnav_tracked_leg_index').whenChanged().handle(this.getActiveLegType.bind(this));
            const vnav = this.bus.getSubscriber();
            vnav.on('vnav_vertical_deviation').withPrecision(0).handle(deviation => this.onUpdateVnav(deviation));
            vnav.on('gp_vertical_deviation').withPrecision(0).handle(deviation => this.onUpdateLpv(deviation, this.currentLpvDistance));
            vnav.on('gp_distance').withPrecision(0).handle(distance => this.onUpdateLpv(this.currentLpvDeviation, distance));
            vnav.on('gp_approach_mode').whenChanged().handle((mode) => {
                this.currentVnavApproachMode = mode;
                this.updateVNavDisplayMode();
            });
            vnav.on('vnav_path_mode').whenChanged().handle(mode => {
                this.currentVNavPathMode = mode;
                this.updateVNavDisplayMode();
            });
            vnav.on('vnav_tod_distance').atFrequency(1).handle(distance => {
                this.currentVNavTodDistance = distance;
            });
            vnav.on('vnav_bod_distance').atFrequency(1).handle(distance => {
                this.currentVNavBodDistance = distance;
                this.checkIfVnavPathInRange();
            });
            vnav.on('vnav_target_altitude').whenChanged().handle(alt => {
                if (alt > 45000 || alt <= 0) {
                    this.currentVNavTargetAltitude = -1;
                }
                else {
                    this.currentVNavTargetAltitude = alt;
                }
                this.updateVNavDisplayMode();
            });
            vnav.on('vnav_constraint_altitude').whenChanged().handle(alt => {
                if (alt > 45000 || alt <= 0) {
                    this.currentVNavConstraintAltitude = -1;
                }
                else {
                    this.currentVNavConstraintAltitude = alt;
                }
                this.updateVNavDisplayMode();
            });
            vnav.on('vnav_fpa').whenChanged().handle(fpa => {
                this.currentVNavFpa = fpa;
                this.updateVNavDisplayMode();
            });
            vnav.on('vnav_state').whenChanged().handle(state => {
                this.vnavState = state;
                this.updateVNavDisplayMode();
            });
            const fpl = this.bus.getSubscriber();
            fpl.on('fplLegChange').handle((e) => {
                if (e.planIndex === this.fms.flightPlanner.activePlanIndex) {
                    this.onFplChange();
                }
            });
            fpl.on('fplIndexChanged').handle(() => this.onFplChange());
            fpl.on('fplLoaded').handle(() => this.onFplChange());
            this.dmeSourceIndex.sub((v) => {
                const dmeSource = v;
                const dmeDistance = this.navStates[dmeSource].distance;
                if (this.navStates[dmeSource].hasDme && dmeDistance !== null && dmeDistance > 0) {
                    this.dmeDistanceSubject.set(dmeDistance);
                }
                else {
                    this.dmeDistanceSubject.set(-1);
                }
            });
            this.isLnavCalculating.sub((v) => {
                if (!v) {
                    this.onUpdateLnavXtk(0);
                }
                else {
                    this.updateComponentsDisplay(this.navStates[2].source);
                }
            });
        }
        /**
         * A method to check if the VNAV Path is in a displayable range.
         */
        checkIfVnavPathInRange() {
            let vnavPathInRange = false;
            if (this.currentVNavBodDistance > 0
                && this.currentSpeed > 30
                && this.navStates[2].altDeviation !== null
                && this.currentVNavTargetAltitude > 0
                && this.currentVNavConstraintAltitude > 0
                && this.currentVNavConstraintAltitude < this.currentAltitude
                && Math.abs(this.currentVNavFpa) > 0) {
                const todNM = msfsSdk.UnitType.METER.convertTo(this.currentVNavTodDistance, msfsSdk.UnitType.NMILE);
                const bodNM = msfsSdk.UnitType.METER.convertTo(this.currentVNavBodDistance, msfsSdk.UnitType.NMILE);
                if (todNM < this.currentSpeed / 60 && bodNM > 0) {
                    vnavPathInRange = true;
                }
            }
            if (vnavPathInRange !== this.vnavPathInRange) {
                this.vnavPathInRange = vnavPathInRange;
                this.updateVNavDisplayMode();
            }
        }
        /**
         * A method to update the VNAV Display Mode Subject.
         */
        updateVNavDisplayMode() {
            var _a;
            const activeSource = this.navStates[this.activeSourceIndex];
            let vnavMode = exports.VNavDisplayMode.NONE;
            let gpMode = exports.GPDisplayMode.NONE;
            if (this.currentVNavPathMode === msfsSdk.VNavPathMode.PathActive) {
                vnavMode = exports.VNavDisplayMode.PATH;
                if (activeSource.source.type === msfsSdk.NavSourceType.Gps && activeSource.hasGlideslope && !this.missedApproachActive) {
                    gpMode = exports.GPDisplayMode.PREVIEW;
                }
            }
            else if (this.currentVnavApproachMode === msfsSdk.ApproachGuidanceMode.GPActive) {
                vnavMode = exports.VNavDisplayMode.NONE;
                gpMode = exports.GPDisplayMode.ACTIVE;
            }
            else if (activeSource.source.type === msfsSdk.NavSourceType.Gps) {
                const vtfActive = this.flightPhase.isApproachActive && this.fms.isApproachVtf();
                if (this.vnavPathInRange && this.vnavState !== msfsSdk.VNavState.Disabled && !vtfActive) {
                    vnavMode = exports.VNavDisplayMode.PATH;
                }
                if (activeSource.hasGlideslope && !this.missedApproachActive) {
                    switch (this.activeSensitivity) {
                        case exports.NavSensitivity.VIS:
                        case exports.NavSensitivity.LNAVplusV:
                        case exports.NavSensitivity.LPplusV:
                        case exports.NavSensitivity.LPV:
                        case exports.NavSensitivity.LVNAV:
                            gpMode = exports.GPDisplayMode.ACTIVE;
                            break;
                        default:
                            gpMode = exports.GPDisplayMode.PREVIEW;
                    }
                }
            }
            this.vnavDisplayMode.set(vnavMode);
            this.gpDisplayMode.set(gpMode);
            (_a = this.vdi) === null || _a === void 0 ? void 0 : _a.updateSourceSensitivity();
            this.shouldDisplayPathMode.set(vnavMode === exports.VNavDisplayMode.PATH);
        }
        /**
         * A method called on flight plan changes to set whether lnav has a valid plan.
         */
        onFplChange() {
            const length = this.fms.flightPlanner.hasActiveFlightPlan() ? this.fms.flightPlanner.getActiveFlightPlan().length : 0;
            if (length < 2) {
                this.isLnavCalculating.set(false);
                this.lnavLegType = msfsSdk.LegType.Discontinuity;
            }
            else {
                this.isLnavCalculating.set(true);
                const plan = this.fms.flightPlanner.getActiveFlightPlan();
                this.getActiveLegType(plan.activeLateralLeg);
            }
        }
        /**
         * Checks the leg type of the active lateral leg.
         * @param index The Global Leg Index.
         */
        getActiveLegType(index) {
            let legType = msfsSdk.LegType.Discontinuity;
            const length = this.fms.flightPlanner.hasActiveFlightPlan() ? this.fms.flightPlanner.getActiveFlightPlan().length : 0;
            if (index > 0 && index < length) {
                const lateralPlan = this.fms.flightPlanner.getActiveFlightPlan();
                const leg = lateralPlan.getLeg(index);
                legType = leg.leg.type;
            }
            this.lnavLegType = legType;
        }
        /**
         * A method called from hsimap when the HSI format is changed.
         * @param hsiMap a bool set to true when the hsiMap should be displayed and false when the rose should be displayed.
         */
        onFormatChange(hsiMap) {
            var _a, _b, _c, _d;
            switch (hsiMap) {
                case true:
                    this.hsiMapActive = true;
                    (_a = this.hsiRefs.hsiRose) === null || _a === void 0 ? void 0 : _a.instance.setVisible(false);
                    (_b = this.hsiRefs.hsiMap) === null || _b === void 0 ? void 0 : _b.instance.setVisible(true);
                    break;
                case false:
                    this.hsiMapActive = false;
                    (_c = this.hsiRefs.hsiMap) === null || _c === void 0 ? void 0 : _c.instance.setVisible(false);
                    (_d = this.hsiRefs.hsiRose) === null || _d === void 0 ? void 0 : _d.instance.setVisible(true);
            }
            this.updateComponentsDisplay();
        }
        /**
         * A method to compare the incoming NavSourceId with the Active Nav Source.
         * @param source The current selected CDI Source.
         * @returns a bool of whether the incoming NavSourceId is the active nav source.
         */
        checkIfActive(source) {
            const type = source.type;
            const index = source.index;
            if (type === this.navStates[this.activeSourceIndex].source.type && index === this.navStates[this.activeSourceIndex].source.index) {
                return true;
            }
            else {
                return false;
            }
        }
        /**
         * A callback called to update the nav sensitivity.
         * @param updatedSource is the source that was updated
         */
        updateSensitivity(updatedSource = undefined) {
            const update = updatedSource === undefined ? true : this.checkIfActive(updatedSource);
            if (update) {
                switch (this.navStates[this.activeSourceIndex].source.type) {
                    case msfsSdk.NavSourceType.Nav:
                        if (this.navStates[this.activeSourceIndex].isLocalizer) {
                            this.activeSensitivity = exports.NavSensitivity.ILS;
                        }
                        else {
                            this.activeSensitivity = exports.NavSensitivity.VOR;
                        }
                        break;
                    case msfsSdk.NavSourceType.Gps:
                        this.setGpsSensitivity();
                        break;
                }
                this.updateComponentsDisplay();
            }
        }
        /**
         * Sets the GPS nav sentitivity value.
         */
        setGpsSensitivity() {
            const nav = this.navStates[this.activeSourceIndex];
            let missedApproachActive = false;
            switch (nav.deviationScaleLabel) {
                case exports.CDIScaleLabel.Departure:
                    this.activeSensitivity = exports.NavSensitivity.DPRT;
                    break;
                case exports.CDIScaleLabel.Terminal:
                    this.activeSensitivity = exports.NavSensitivity.TERM;
                    break;
                case exports.CDIScaleLabel.LNav:
                    this.activeSensitivity = exports.NavSensitivity.LNAV;
                    break;
                case exports.CDIScaleLabel.LNavPlusV:
                    this.activeSensitivity = exports.NavSensitivity.LNAVplusV;
                    break;
                case exports.CDIScaleLabel.LNavVNav:
                    this.activeSensitivity = exports.NavSensitivity.LVNAV;
                    break;
                case exports.CDIScaleLabel.LP:
                    this.activeSensitivity = exports.NavSensitivity.LP;
                    break;
                case exports.CDIScaleLabel.LPPlusV:
                    this.activeSensitivity = exports.NavSensitivity.LPplusV;
                    break;
                case exports.CDIScaleLabel.LPV:
                    this.activeSensitivity = exports.NavSensitivity.LPV;
                    break;
                case exports.CDIScaleLabel.Visual:
                    this.activeSensitivity = exports.NavSensitivity.VIS;
                    break;
                case exports.CDIScaleLabel.MissedApproach:
                    this.activeSensitivity = exports.NavSensitivity.MAPR;
                    missedApproachActive = true;
                    break;
                default:
                    this.activeSensitivity = exports.NavSensitivity.ENR;
            }
            if (missedApproachActive !== this.missedApproachActive) {
                this.missedApproachActive = missedApproachActive;
            }
        }
        /**
         * A method called when the bearing or dtk to/from an lnav fix updates to set the to/from flag for GPS.
         * @returns Whether the toFrom value has changed.
         */
        setLnavToFrom() {
            if (this.isLnavCalculating.get()) {
                let toFrom = msfsSdk.VorToFrom.TO;
                const dtk = this.navStates[2].dtk_obs;
                const bearing = this.navStates[2].bearing;
                if (bearing !== null && dtk !== null) {
                    if ((this.lnavLegType === msfsSdk.LegType.VM || this.lnavLegType === msfsSdk.LegType.FM) && Math.abs(msfsSdk.NavMath.diffAngle(this.currentHeading, dtk)) > 100) {
                        toFrom = msfsSdk.VorToFrom.FROM;
                    }
                    else if (!(this.lnavLegType === msfsSdk.LegType.VM || this.lnavLegType === msfsSdk.LegType.FM) && Math.abs(msfsSdk.NavMath.diffAngle(bearing, dtk)) > 120) {
                        toFrom = msfsSdk.VorToFrom.FROM;
                    }
                }
                if (toFrom !== this.navStates[2].toFrom) {
                    this.navStates[2].toFrom = toFrom;
                    return true;
                }
            }
            return false;
        }
        /**
         * A callback called when the LPV data is updated.
         * @param deviation The LPV vertical deviation.
         * @param distance The LPV lateral distance.
         */
        onUpdateLpv(deviation, distance) {
            var _a;
            this.currentLpvDeviation = deviation;
            const hasGlideslope = this.navStates[2].hasGlideslope;
            if (distance !== this.currentLpvDistance) {
                this.currentLpvDistance = distance;
                const approachType = this.approachDetails.type;
                if (this.flightPhase.isApproachActive && !this.approachDetails.isCircling && Math.abs(distance) < 182283 /* 30 nautical miles */ &&
                    (approachType === ApproachType.APPROACH_TYPE_GPS || approachType === ApproachType.APPROACH_TYPE_RNAV || approachType === msfsSdk.AdditionalApproachType.APPROACH_TYPE_VISUAL)) {
                    if (!hasGlideslope) {
                        this.navStates[2].hasGlideslope = true;
                        this.updateVNavDisplayMode();
                    }
                }
                else if (hasGlideslope) {
                    this.navStates[2].hasGlideslope = false;
                    this.updateVNavDisplayMode();
                }
            }
            else if (distance <= 0 && hasGlideslope) {
                this.navStates[2].hasGlideslope = false;
                this.updateVNavDisplayMode();
            }
            if (isFinite(deviation) && isFinite(distance) && this.navStates[2].hasGlideslope) {
                const scale = Math.tan(msfsSdk.UnitType.DEGREE.convertTo(2.0, msfsSdk.UnitType.RADIAN)) * distance;
                const scaleClamped = msfsSdk.NavMath.clamp(scale, 200, 1000) * -1;
                this.navStates[2].gsDeviation = deviation / scaleClamped;
                (_a = this.vdi) === null || _a === void 0 ? void 0 : _a.updateDeviation();
            }
        }
        /**
         * A callback called when the VNAV data is updated.
         * @param deviation The vnav vertical deviation.
         */
        onUpdateVnav(deviation) {
            var _a;
            this.navStates[2].altDeviation = deviation / -750;
            (_a = this.vdi) === null || _a === void 0 ? void 0 : _a.updateDeviation();
        }
        /**
         * A callback called to slew the obs to the ILS inbound course when an loc becomes valid.
         */
        slewObs() {
            const course = this.navStates[this.activeSourceIndex].localizerCourse;
            if (this.activeSourceIndex < 2 && this.navStates[this.activeSourceIndex].isLocalizer &&
                this.navStates[this.activeSourceIndex].hasLocalizer && course !== null) {
                SimVar.SetSimVarValue(`K:VOR${this.activeSourceIndex + 1}_SET`, 'number', Math.round(course));
            }
        }
        /**
         * A method called when xtk/dtk data updates.
         * @param updatedSource is the source that was updated
         */
        updateComponentsData(updatedSource = undefined) {
            var _a, _b, _c;
            const update = updatedSource === undefined ? true : this.checkIfActive(updatedSource);
            if (update || this.firstRun) {
                if (this.onUpdateDtkBox !== undefined) {
                    this.onUpdateDtkBox();
                }
                if (this.hsiMapActive) {
                    (_a = this.courseNeedleRefs.hsiMap) === null || _a === void 0 ? void 0 : _a.instance.updateData();
                    (_b = this.hsiMapDeviationRef) === null || _b === void 0 ? void 0 : _b.instance.updateData();
                }
                else {
                    (_c = this.courseNeedleRefs.hsiRose) === null || _c === void 0 ? void 0 : _c.instance.updateData();
                }
                if (this.firstRun) {
                    this.firstRun = false;
                }
            }
        }
        /**
         * A method called when any value updates that needs to trigger a component update.
         * @param updatedSource is the source that was updated
         */
        updateComponentsDisplay(updatedSource = undefined) {
            var _a, _b, _c, _d;
            const update = updatedSource === undefined ? true : this.checkIfActive(updatedSource);
            if (update || this.firstRun) {
                if (this.hsiMapActive) {
                    (_a = this.courseNeedleRefs.hsiMap) === null || _a === void 0 ? void 0 : _a.instance.updateSourceSensitivity();
                    (_b = this.hsiMapDeviationRef) === null || _b === void 0 ? void 0 : _b.instance.updateSourceSensitivity();
                }
                else {
                    (_c = this.courseNeedleRefs.hsiRose) === null || _c === void 0 ? void 0 : _c.instance.updateSourceSensitivity();
                    (_d = this.hsiRefs.hsiRose) === null || _d === void 0 ? void 0 : _d.instance.updateSourceSensitivity();
                }
                this.updateComponentsData(updatedSource);
            }
        }
        /**
         * Utility function to update a given bearing pointer in both the rose and map.
         * @param index The index of the bearing pointer to update.
         * @param func A function to execute on the pointer instances.
         */
        updateBearingPointers(index, func) {
            const elements = [
                index === 0 ? this.hsiRefs.hsiRose.instance.bearingPointer1Element :
                    index === 1 ? this.hsiRefs.hsiRose.instance.bearingPointer2Element : null,
                index === 0 ? this.hsiRefs.hsiMap.instance.bearingPointer1Element :
                    index === 1 ? this.hsiRefs.hsiMap.instance.bearingPointer2Element : null
            ];
            for (const element of elements) {
                func(element);
            }
        }
        /**
         * Get the index in navStates for a given nav source.  This is a bit of a hack to
         * tie together two distinct data models, but it will do the job for now.
         * @param source The NavSourceId of the desired source.
         * @returns The index of that source in navStates or -1 if not found.
         */
        getNavSourceIndex(source) {
            for (let i = 0; i < this.navStates.length; i++) {
                if (this.navStates[i].source.type == source.type && this.navStates[i].source.index == source.index) {
                    return i;
                }
            }
            return -1;
        }
    }

    /**
     * Utility class for working with ICAO region codes in a Garmin context.
     */
    class Regions {
        /**
         * Gets the Garmin display name of the region associated with a specified ICAO region code.
         * @param code - the 2 character ICAO region code.
         * @returns the Garmin display name of the region.
         */
        static getName(code) {
            const name = Regions.NAME_TABLE[code.toUpperCase()];
            return name !== null && name !== void 0 ? name : '';
        }
    }
    Regions.NAME_TABLE = {
        'AG': 'SOLOMON IS',
        'AN': 'NAURU',
        'AY': 'PAPUA N GN',
        'BG': 'GREENLAND',
        'BI': 'ICELAND',
        'BK': 'KOSOVO',
        'CY': 'CANADA',
        'DA': 'ALGERIA',
        'DB': 'BENIN',
        'DF': 'BURKINA',
        'DG': 'GHANA',
        'DI': 'IVRY COAST',
        'DN': 'NIGERIA',
        'DR': 'NIGER',
        'DT': 'TUNISIA',
        'DX': 'TOGO',
        'EB': 'BELGIUM',
        'ED': 'GERMANY',
        'EE': 'ESTONIA',
        'EF': 'FINLAND',
        'EG': 'U KINGDOM',
        'EH': 'NETHERLNDS',
        'EI': 'IRELAND',
        'EK': 'DENMARK',
        'EL': 'LUXEMBOURG',
        'EN': 'NORWAY',
        'EP': 'POLAND',
        'ES': 'SWEDEN',
        'ET': 'GERMANY',
        'EV': 'LATVIA',
        'EY': 'LITHUANIA',
        'FA': 'S AFRICA',
        'FB': 'BOTSWANA',
        'FC': 'CONGO',
        'FD': 'ESWATINI',
        'FE': 'CENTRL AFR',
        'FG': 'EQU GUINEA',
        'FH': 'ASCN/ST HL',
        'FI': 'MAURITIUS',
        'FJ': 'IND OCN TR',
        'FK': 'CAMEROON',
        'FL': 'ZAMBIA',
        'FM': 'MADAGASCAR',
        'FN': 'ANGOLA',
        'FO': 'GABON',
        'FP': 'SAO TOME',
        'FQ': 'MOZAMBIQUE',
        'FS': 'SEYCHELLES',
        'FT': 'CHAD',
        'FV': 'ZIMBABWE',
        'FW': 'MALAWI',
        'FX': 'LESOTHO',
        'FY': 'NAMIBIA',
        'FZ': 'DEM CONGO',
        'GA': 'MALI',
        'GB': 'GAMBIA',
        'GC': 'CANARY IS',
        'GE': 'MELILLA',
        'GF': 'SIER LEONE',
        'GG': 'GUIN-BSSAU',
        'GL': 'LIBERIA',
        'GM': 'MOROCCO',
        'GO': 'SENEGAL',
        'GQ': 'MAURITANIA',
        'GS': 'W SAHARA',
        'GU': 'GUINEA',
        'GV': 'CAPE VERDE',
        'HA': 'ETHIOPIA',
        'HB': 'BURUNDI',
        'HD': 'DJIBOUTI',
        'HE': 'EGYPT',
        'HH': 'ERITREA',
        'HK': 'KENYRA',
        'HL': 'LIBYA',
        'HR': 'RWANDA',
        'HS': 'SUDAN',
        'HT': 'TANZANIA',
        'HU': 'UGANDA',
        'K1': 'NW USA',
        'K2': 'SW USA',
        'K3': 'N CEN USA',
        'K4': 'S CEN USA',
        'K5': 'GR LKS USA',
        'K6': 'NE USA',
        'K7': 'SE USA',
        'LA': 'ALBANIA',
        'LB': 'BULGARIA',
        'LC': 'CYPRUS',
        'LD': 'CROATIA',
        'LE': 'SPAIN',
        'LF': 'FRANCE',
        'LG': 'GREECE',
        'LH': 'HUNGARY',
        'LI': 'ITALY',
        'LJ': 'SLOVENIA',
        'LK': 'CZECH',
        'LL': 'ISRAEL',
        'LM': 'MALTA',
        'LO': 'AUSTRIA',
        'LP': 'PORTUGAL',
        'LQ': 'BOSNIA-HRZ',
        'LR': 'ROMANIA',
        'LS': 'SWITZRLAND',
        'LT': 'TURKEY',
        'LU': 'MOLDOVA',
        'LV': 'PALESTINE',
        'LW': 'MACEDONIA',
        'LX': 'GIBRALTAR',
        'LY': 'SERB/MONTG',
        'LZ': 'SLOVAKIA',
        'MB': 'TURKS/CAIC',
        'MD': 'DOM REPBLC',
        'MG': 'GUATEMALA',
        'MH': 'HONDURAS',
        'MK': 'JAMAICA',
        'MM': 'MEXICO',
        'MN': 'NICARAGUA',
        'MP': 'PANAMA',
        'MR': 'COSTA RICA',
        'MS': 'EL SALVDOR',
        'MT': 'HAITI',
        'MU': 'CUBA',
        'MW': 'CAYMAN IS',
        'MY': 'BAHAMAS',
        'MZ': 'BELIZE',
        'NC': 'COOK IS',
        'NF': 'FIJI/TONGA',
        'NG': 'KIRI/TUVLU',
        'NI': 'NIUE',
        'NL': 'FUTNA/WALS',
        'NS': 'AM/W SAMOA',
        'NT': 'FRNCH POLY',
        'NV': 'VANUATU',
        'NW': 'N CALEDNIA',
        'NZ': 'NEW ZEALND',
        'OA': 'AFGHNISTAN',
        'OB': 'BAHRAIN',
        'OE': 'SAUDI ARAB',
        'OI': 'IRAN',
        'OJ': 'JORDAN',
        'OK': 'KUWAIT',
        'OL': 'LEBANON',
        'OM': 'ARAB EMIRA',
        'OO': 'OMAN',
        'OP': 'PAKISTAN',
        'OR': 'IRAQ',
        'OS': 'SYRIA',
        'OT': 'QATAR',
        'OY': 'YEMEN',
        'PA': 'ALASKA',
        'PG': 'GUAM',
        'PH': 'HAWAII',
        'PJ': 'JOHNSTON ATOLL',
        'PK': 'MARSHLL IS',
        'PL': 'KIRIBATI',
        'PM': 'MIDWAY IS',
        'PO': 'ALASKA',
        'PP': 'ALASKA',
        'PT': 'MICRONESIA',
        'PW': 'WAKE IS',
        'RC': 'TAIWAN',
        'RJ': 'JAPAN',
        'RK': 'S KOREA',
        'RO': 'OKINAWA',
        'RP': 'PHILIPPINS',
        'SA': 'ARGENTINA',
        'SB': 'BRAZIL',
        'SC': 'CHILE',
        'SD': 'BRAZIL',
        'SE': 'EQUADOR',
        'SG': 'PARAGUAY',
        'SI': 'BRAZIL',
        'SJ': 'BRAZIL',
        'SK': 'COLOMBIA',
        'SL': 'BOLIVIA',
        'SM': 'SURINAME',
        'SO': 'FRN GUIANA',
        'SP': 'PERU',
        'SS': 'BRAZIL',
        'SU': 'URUGUAY',
        'SV': 'VENEZUELA',
        'SW': 'BRAZIL',
        'SY': 'GUYANA',
        'TA': 'ANTIGUA',
        'TB': 'BARBADOS',
        'TD': 'ANTIGUA',
        'TF': 'GUAD/MRTNQ',
        'TG': 'GRENADA',
        'TI': 'US VRGN IS',
        'TJ': 'PUERTO RIC',
        'TK': 'ST KTS/NEV',
        'TL': 'ST LUCIA',
        'TN': 'ARUBA',
        'TQ': 'ANGUILLA',
        'TT': 'MONTSERRAT',
        'TU': 'TRIN/TOBAG',
        'TV': 'BR VRGN IS',
        'TX': 'BERMUDA',
        'UA': 'KZKHSTN',
        'UB': 'AZERBAIJAN',
        'UC': 'KYRGYZSTAN',
        'UD': 'ARMENIA',
        'UE': 'RUSSIA',
        'UG': 'GEORGIA',
        'UH': 'RUSSIA',
        'UI': 'RUSSIA',
        'UK': 'UKRAINE',
        'UL': 'RUSSIA',
        'UM': 'RUSS/BELRS',
        'UN': 'RUSSIA',
        'UO': 'RUSSIA',
        'UR': 'RUSS/KZKST',
        'US': 'RUSSIA',
        'UT': 'UZBEK/TADZ',
        'UU': 'RUSSIA',
        'UW': 'RUSSIA',
        'VA': 'INDIA',
        'VC': 'SRI LANKA',
        'VD': 'CAMBODIA',
        'VE': 'INDIA',
        'VG': 'BANGLADESH',
        'VH': 'HONG KONG',
        'VI': 'INDIA',
        'VL': 'LAOS',
        'VM': 'MACAU',
        'VN': 'NEPAL',
        'VO': 'INDIA',
        'VR': 'MALDIVES',
        'VT': 'THAILAND',
        'VV': 'VIETNAM',
        'VY': 'MYANMAR',
        'WA': 'INDONESIA',
        'WB': 'BRUNEI',
        'WI': 'INDONESIA',
        'WM': 'MALAYSIA',
        'WR': 'INDONESIA',
        'WS': 'SINGAPORE',
        'YB': 'AUSTRALIA',
        'YM': 'AUSTRALIA',
        'ZB': 'CHINA',
        'ZG': 'CHINA',
        'ZH': 'CHINA',
        'ZK': 'N KOREA',
        'ZL': 'CHINA',
        'ZM': 'MONGOLIA',
        'ZP': 'CHINA',
        'ZS': 'CHINA',
        'ZU': 'CHINA',
        'ZW': 'CHINA',
        'ZY': 'CHINA',
    };

    /**
     * A default implementation of {@link VNavDataProvider}.
     */
    class DefaultVNavDataProvider {
        // eslint-disable-next-line jsdoc/require-jsdoc
        constructor(bus, fms, arg3) {
            this.bus = bus;
            this.isVNavIndexValid = false;
            this._phase = msfsSdk.Subject.create(null);
            /** @inheritdoc */
            this.phase = this._phase;
            this._vnavFlightPhase = msfsSdk.Subject.create(exports.GarminVNavFlightPhase.None);
            /** @inheritdoc */
            this.vnavFlightPhase = this._vnavFlightPhase;
            this._vnavTrackingPhase = msfsSdk.Subject.create(exports.GarminVNavTrackingPhase.None);
            /** @inheritdoc */
            this.vnavTrackingPhase = this._vnavTrackingPhase;
            this._isVNavDirectToActive = msfsSdk.Subject.create(false);
            /** @inheritdoc */
            this.isVNavDirectToActive = this._isVNavDirectToActive;
            this._pathMode = msfsSdk.Subject.create(msfsSdk.VNavPathMode.None);
            /** @inheritdoc */
            this.pathMode = this._pathMode;
            this._cruiseAltitude = msfsSdk.Subject.create(null);
            /** @inheritdoc */
            this.cruiseAltitude = this._cruiseAltitude;
            this._activeConstraintLeg = msfsSdk.Subject.create(null);
            /** @inheritdoc */
            this.activeConstraintLeg = this._activeConstraintLeg;
            this._targetAltitude = msfsSdk.Subject.create({ type: msfsSdk.AltitudeRestrictionType.Unused, altitude: 0 }, (a, b) => (a === null && b === null) || (a !== null && b !== null && a.type === b.type && a.altitude === b.altitude));
            this._fpa = msfsSdk.Subject.create(null);
            /** @inheritdoc */
            this.fpa = this._fpa;
            this._verticalSpeedTarget = msfsSdk.Subject.create(null);
            /** @inheritdoc */
            this.verticalSpeedTarget = this._verticalSpeedTarget;
            this._vsRequired = msfsSdk.Subject.create(null);
            /** @inheritdoc */
            this.vsRequired = this._vsRequired;
            this._verticalDeviation = msfsSdk.Subject.create(null);
            /** @inheritdoc */
            this.verticalDeviation = this._verticalDeviation;
            this._distanceToTod = msfsSdk.Subject.create(null);
            /** @inheritdoc */
            this.distanceToTod = this._distanceToTod;
            this._timeToTod = msfsSdk.Subject.create(null);
            /** @inheritdoc */
            this.timeToTod = this._timeToTod;
            this._timeToBod = msfsSdk.Subject.create(null);
            /** @inheritdoc */
            this.timeToBod = this._timeToBod;
            this._timeToToc = msfsSdk.Subject.create(null);
            /** @inheritdoc */
            this.timeToToc = this._timeToToc;
            this._timeToBoc = msfsSdk.Subject.create(null);
            /** @inheritdoc */
            this.timeToBoc = this._timeToBoc;
            this.indicatedAlt = msfsSdk.ConsumerValue.create(null, 0);
            this.groundSpeed = msfsSdk.ConsumerValue.create(null, 0);
            this.activeNavSource = msfsSdk.ConsumerValue.create(null, { type: null, index: 1 });
            this.approachDetails = msfsSdk.ConsumerValue.create(null, {
                isLoaded: false,
                type: ApproachType.APPROACH_TYPE_UNKNOWN,
                isRnpAr: false,
                bestRnavType: msfsSdk.RnavTypeFlags.None,
                rnavTypeFlags: msfsSdk.RnavTypeFlags.None,
                isCircling: false,
                isVtf: false,
                referenceFacility: null,
                runway: null
            });
            this.flightPhase = msfsSdk.ConsumerValue.create(null, {
                isApproachActive: false,
                isToFaf: false,
                isPastFaf: false,
                isInMissedApproach: false
            });
            this.vnavState = msfsSdk.ConsumerSubject.create(null, msfsSdk.VNavState.Disabled);
            this.vnavFlightPhaseSource = msfsSdk.ConsumerValue.create(null, exports.GarminVNavFlightPhase.None);
            this.vnavTrackingPhaseSource = msfsSdk.ConsumerValue.create(null, exports.GarminVNavTrackingPhase.None);
            this.vnavPathMode = msfsSdk.ConsumerValue.create(null, msfsSdk.VNavPathMode.None);
            this.vnavCruiseAltitude = msfsSdk.ConsumerValue.create(null, 0);
            this.vnavActiveConstraintLegIndex = msfsSdk.ConsumerValue.create(null, -1);
            this.vnavFpa = msfsSdk.ConsumerValue.create(null, 0);
            this.vnavVsRequired = msfsSdk.ConsumerValue.create(null, 0);
            this.vnavVerticalDeviation = msfsSdk.ConsumerValue.create(null, 0);
            this.vnavTodIndex = msfsSdk.ConsumerValue.create(null, -1);
            this.vnavTodDistance = msfsSdk.ConsumerValue.create(null, 0);
            this.vnavBodIndex = msfsSdk.ConsumerValue.create(null, -1);
            this.vnavBodDistance = msfsSdk.ConsumerValue.create(null, 0);
            this.vnavTocIndex = msfsSdk.ConsumerValue.create(null, -1);
            this.vnavTocDistance = msfsSdk.ConsumerValue.create(null, 0);
            this.vnavBocIndex = msfsSdk.ConsumerValue.create(null, -1);
            this.vnavBocDistance = msfsSdk.ConsumerValue.create(null, 0);
            this.approachGuidanceMode = msfsSdk.ConsumerValue.create(null, msfsSdk.ApproachGuidanceMode.None);
            this.vnavConstraintDetails = msfsSdk.ConsumerValue.create(null, { type: msfsSdk.AltitudeRestrictionType.Unused, altitude: 0 });
            /** @inheritdoc */
            this.targetRestriction = this._targetAltitude;
            this.isInit = false;
            this.isAlive = true;
            this.isPaused = false;
            this.pauseable = [
                this.indicatedAlt,
                this.groundSpeed,
                this.activeNavSource,
                this.approachDetails,
                this.vnavFlightPhaseSource,
                this.vnavTrackingPhaseSource,
                this.vnavPathMode,
                this.vnavCruiseAltitude,
                this.vnavActiveConstraintLegIndex,
                this.vnavFpa,
                this.vnavVsRequired,
                this.vnavVerticalDeviation,
                this.vnavTodIndex,
                this.vnavTodDistance,
                this.vnavBodIndex,
                this.vnavBodDistance,
                this.vnavTocIndex,
                this.vnavTocDistance,
                this.vnavBocIndex,
                this.vnavBocDistance,
                this.approachGuidanceMode,
                this.vnavConstraintDetails
            ];
            this.fms = msfsSdk.SubscribableUtils.toSubscribable(fms, true);
            let vnavIndex;
            let adcIndex;
            let cdiId;
            if (typeof arg3 === 'number' || msfsSdk.SubscribableUtils.isSubscribable(arg3)) {
                vnavIndex = 0;
                adcIndex = arg3;
                cdiId = '';
            }
            else {
                vnavIndex = arg3 === null || arg3 === void 0 ? void 0 : arg3.vnavIndex;
                adcIndex = arg3 === null || arg3 === void 0 ? void 0 : arg3.adcIndex;
                cdiId = arg3 === null || arg3 === void 0 ? void 0 : arg3.cdiId;
            }
            this.vnavIndex = msfsSdk.SubscribableUtils.toSubscribable(vnavIndex !== null && vnavIndex !== void 0 ? vnavIndex : 0, true);
            this.adcIndex = msfsSdk.SubscribableUtils.toSubscribable(adcIndex !== null && adcIndex !== void 0 ? adcIndex : 1, true);
            this.cdiId = msfsSdk.SubscribableUtils.toSubscribable(cdiId !== null && cdiId !== void 0 ? cdiId : '', true);
        }
        /**
         * Initializes this data provider. Once initialized, this data provider will continuously update its data until
         * paused or destroyed.
         * @param paused Whether to initialize this data provider as paused. If `true`, this data provider will provide an
         * initial set of data but will not update the provided data until it is resumed. Defaults to `false`.
         * @throws Error if this data provider is dead.
         */
        init(paused = false) {
            if (!this.isAlive) {
                throw new Error('DefaultVNavDataProvider: cannot initialize a dead provider');
            }
            if (this.isInit) {
                return;
            }
            this.isInit = true;
            const sub = this.bus.getSubscriber();
            this.cdiIdSub = this.cdiId.sub(id => {
                this.activeNavSource.setConsumer(sub.on(`cdi_select${msfsSdk.CdiUtils.getEventBusTopicSuffix(id)}`));
            }, true);
            this.adcIndexSub = this.adcIndex.sub(index => {
                this.indicatedAlt.setConsumer(sub.on(`adc_indicated_alt_${index}`));
            }, true);
            this.groundSpeed.setConsumer(sub.on('ground_speed'));
            this.fmsSub = this.fms.sub(fms => {
                this.approachDetails.setConsumer(fms.onEvent('fms_approach_details'));
                this.flightPhase.setConsumer(fms.onEvent('fms_flight_phase'));
            }, true);
            const clockSub = this.clockSub = sub.on('realTime').handle(this.update.bind(this), true);
            this.vnavStateSub = this.vnavState.sub(state => {
                if (state === msfsSdk.VNavState.Disabled) {
                    for (const pauseable of this.pauseable) {
                        pauseable.pause();
                    }
                    clockSub.pause();
                    this.clearData();
                }
                else {
                    for (const pauseable of this.pauseable) {
                        pauseable.resume();
                    }
                    clockSub.resume(true);
                }
            }, false, true);
            this.approachGuidanceMode.setConsumer(sub.on('gp_approach_mode'));
            this.vnavIndexSub = this.vnavIndex.sub(index => {
                this.isVNavIndexValid = msfsSdk.VNavUtils.isValidVNavIndex(index);
                if (this.isVNavIndexValid) {
                    const suffix = msfsSdk.VNavUtils.getEventBusTopicSuffix(index);
                    this.vnavState.setConsumer(sub.on(`vnav_state${suffix}`));
                    this.vnavFlightPhaseSource.setConsumer(sub.on(`vnav_flight_phase${suffix}`));
                    this.vnavTrackingPhaseSource.setConsumer(sub.on(`vnav_tracking_phase${suffix}`));
                    this.vnavPathMode.setConsumer(sub.on(`vnav_path_mode${suffix}`));
                    this.vnavCruiseAltitude.setConsumer(sub.on(`vnav_cruise_altitude${suffix}`));
                    this.vnavActiveConstraintLegIndex.setConsumer(sub.on(`vnav_active_constraint_global_leg_index${suffix}`));
                    this.vnavFpa.setConsumer(sub.on(`vnav_fpa${suffix}`));
                    this.vnavVsRequired.setConsumer(sub.on(`vnav_required_vs${suffix}`));
                    this.vnavVerticalDeviation.setConsumer(sub.on(`vnav_vertical_deviation${suffix}`));
                    this.vnavTodIndex.setConsumer(sub.on(`vnav_tod_global_leg_index${suffix}`));
                    this.vnavTodDistance.setConsumer(sub.on(`vnav_tod_distance${suffix}`));
                    this.vnavBodIndex.setConsumer(sub.on(`vnav_bod_global_leg_index${suffix}`));
                    this.vnavBodDistance.setConsumer(sub.on(`vnav_bod_distance${suffix}`));
                    this.vnavTocIndex.setConsumer(sub.on(`vnav_toc_global_leg_index${suffix}`));
                    this.vnavTocDistance.setConsumer(sub.on(`vnav_toc_distance${suffix}`));
                    this.vnavBocIndex.setConsumer(sub.on(`vnav_boc_global_leg_index${suffix}`));
                    this.vnavBocDistance.setConsumer(sub.on(`vnav_boc_distance${suffix}`));
                    this.vnavConstraintDetails.setConsumer(sub.on(`vnav_altitude_constraint_details${suffix}`));
                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                    this.vnavStateSub.resume(true);
                }
                else {
                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                    this.vnavStateSub.pause();
                    for (const pauseable of this.pauseable) {
                        pauseable.pause();
                    }
                    this.vnavState.setConsumer(null);
                    this.vnavFlightPhaseSource.setConsumer(null);
                    this.vnavTrackingPhaseSource.setConsumer(null);
                    this.vnavPathMode.setConsumer(null);
                    this.vnavCruiseAltitude.setConsumer(null);
                    this.vnavActiveConstraintLegIndex.setConsumer(null);
                    this.vnavFpa.setConsumer(null);
                    this.vnavVsRequired.setConsumer(null);
                    this.vnavVerticalDeviation.setConsumer(null);
                    this.vnavTodIndex.setConsumer(null);
                    this.vnavTodDistance.setConsumer(null);
                    this.vnavBodIndex.setConsumer(null);
                    this.vnavBodDistance.setConsumer(null);
                    this.vnavTocIndex.setConsumer(null);
                    this.vnavTocDistance.setConsumer(null);
                    this.vnavBocIndex.setConsumer(null);
                    this.vnavBocDistance.setConsumer(null);
                    this.approachGuidanceMode.setConsumer(null);
                    this.vnavConstraintDetails.setConsumer(null);
                }
            }, true);
            if (paused) {
                this.pause();
            }
        }
        /**
         * Updates this provider's data.
         */
        update() {
            // TODO: Support VNAV for off-route DTOs
            const fms = this.fms.get();
            const verticalPathCalculator = fms.verticalPathCalculator;
            const plan = fms.hasPrimaryFlightPlan() ? fms.getPrimaryFlightPlan() : undefined;
            const verticalPlan = verticalPathCalculator === undefined || plan === undefined
                ? undefined
                : verticalPathCalculator.getVerticalFlightPlan(Fms.PRIMARY_PLAN_INDEX);
            const vnavState = this.vnavState.get();
            const vnavTrackingPhase = this.vnavTrackingPhaseSource.get();
            if (!this.isVNavIndexValid
                || verticalPathCalculator === undefined
                || plan === undefined
                || verticalPlan === undefined
                || vnavState === msfsSdk.VNavState.Disabled
                || vnavTrackingPhase === exports.GarminVNavTrackingPhase.None) {
                this.clearData();
                return;
            }
            const vnavActiveConstraintLegIndex = this.vnavActiveConstraintLegIndex.get();
            const activeConstraint = vnavActiveConstraintLegIndex < 0
                ? undefined
                : msfsSdk.VNavUtils.getConstraintFromLegIndex(verticalPlan, vnavActiveConstraintLegIndex);
            const indicatedAlt = this.indicatedAlt.get();
            const groundSpeed = this.groundSpeed.get();
            const activeNavSource = this.activeNavSource.get();
            const vnavPathMode = this.vnavPathMode.get();
            const approachGuidanceMode = this.approachGuidanceMode.get();
            const isTrackingPhaseClimb = vnavTrackingPhase === exports.GarminVNavTrackingPhase.Climb || vnavTrackingPhase === exports.GarminVNavTrackingPhase.MissedApproach;
            const vnavTargetAltitude = this.vnavConstraintDetails.get();
            const vnavFpa = this.vnavFpa.get();
            const vnavConstraintLeg = plan.tryGetLeg(vnavActiveConstraintLegIndex);
            const vsr = this.vnavVsRequired.get();
            const todIndex = this.vnavTodIndex.get();
            const todDistance = msfsSdk.UnitType.METER.convertTo(this.vnavTodDistance.get(), msfsSdk.UnitType.NMILE);
            const bodDistance = msfsSdk.UnitType.METER.convertTo(this.vnavBodDistance.get(), msfsSdk.UnitType.NMILE);
            let showVsr = false;
            let showVDev = false;
            let showTargetAltitude = false;
            let showTime = false;
            if (groundSpeed >= 30) {
                showTime = true;
                if (vnavPathMode === msfsSdk.VNavPathMode.PathActive) {
                    showVsr = true;
                    showVDev = true;
                    showTargetAltitude = true;
                }
                else if (approachGuidanceMode !== msfsSdk.ApproachGuidanceMode.GPActive
                    && activeNavSource.type === msfsSdk.NavSourceType.Gps) {
                    if (isTrackingPhaseClimb) {
                        showTargetAltitude = vnavTargetAltitude.type !== msfsSdk.AltitudeRestrictionType.Unused;
                        showVsr = showTargetAltitude && activeConstraint !== undefined && isFinite(activeConstraint.minAltitude);
                    }
                    else {
                        if (
                        // VNAV target altitude is valid
                        vnavTargetAltitude.type !== msfsSdk.AltitudeRestrictionType.Unused
                            // Not yet passed BOD
                            && bodDistance > 0
                            // TOD exists
                            && todIndex >= 0
                            // Within one minute of TOD
                            && todDistance / groundSpeed < DefaultVNavDataProvider.PATH_TRACKING_LOOKAHEAD
                            // Above 250 feet below the VNAV target altitude
                            && indicatedAlt >= vnavTargetAltitude.altitude - 250) {
                            // negative FPA = downward
                            const requiredFpa = msfsSdk.VNavUtils.getFpaFromVerticalSpeed(vsr, groundSpeed);
                            const isRequiredFpaValid = requiredFpa >= -verticalPathCalculator.maxFlightPathAngle;
                            showTargetAltitude = true;
                            showVsr = isRequiredFpaValid;
                            showVDev = isRequiredFpaValid;
                        }
                    }
                }
            }
            this._phase.set(activeConstraint === undefined
                ? null
                : activeConstraint.type === 'climb' || activeConstraint.type === 'missed' ? msfsSdk.VerticalFlightPhase.Climb : msfsSdk.VerticalFlightPhase.Descent);
            this._vnavFlightPhase.set(this.vnavFlightPhaseSource.get());
            this._vnavTrackingPhase.set(vnavTrackingPhase);
            this._isVNavDirectToActive.set(activeConstraint !== undefined && activeConstraint.type === 'direct');
            this._pathMode.set(vnavPathMode);
            this._cruiseAltitude.set(this.vnavCruiseAltitude.get());
            this._activeConstraintLeg.set(vnavConstraintLeg);
            this._fpa.set(vnavFpa);
            this._verticalSpeedTarget.set(groundSpeed < 30 ? null : msfsSdk.VNavUtils.getVerticalSpeedFromFpa(-vnavFpa, groundSpeed));
            this._distanceToTod.set(isTrackingPhaseClimb || todIndex < 0 ? null : todDistance);
            if (showTime) {
                if (isTrackingPhaseClimb) {
                    const tocIndex = this.vnavTocIndex.get();
                    const tocDistance = msfsSdk.UnitType.METER.convertTo(this.vnavTocDistance.get(), msfsSdk.UnitType.NMILE);
                    const bocIndex = this.vnavBocIndex.get();
                    const bocDistance = msfsSdk.UnitType.METER.convertTo(this.vnavBocDistance.get(), msfsSdk.UnitType.NMILE);
                    this._timeToBod.set(null);
                    this._timeToTod.set(null);
                    this._timeToToc.set(tocIndex < 0 ? null : msfsSdk.UnitType.HOUR.convertTo(tocDistance / groundSpeed, msfsSdk.UnitType.SECOND));
                    this._timeToBoc.set(bocIndex < 0 ? null : msfsSdk.UnitType.HOUR.convertTo(bocDistance / groundSpeed, msfsSdk.UnitType.SECOND));
                }
                else {
                    const bodIndex = this.vnavBodIndex.get();
                    this._timeToBoc.set(null);
                    this._timeToToc.set(null);
                    this._timeToTod.set(todIndex < 0 ? null : msfsSdk.UnitType.HOUR.convertTo(todDistance / groundSpeed, msfsSdk.UnitType.SECOND));
                    this._timeToBod.set(bodIndex < 0 ? null : msfsSdk.UnitType.HOUR.convertTo(bodDistance / groundSpeed, msfsSdk.UnitType.SECOND));
                }
            }
            else {
                this._timeToBod.set(null);
                this._timeToTod.set(null);
                this._timeToBoc.set(null);
                this._timeToToc.set(null);
            }
            this._vsRequired.set(showVsr && vsr !== 0 ? vsr : null);
            this._verticalDeviation.set(showVDev ? -this.vnavVerticalDeviation.get() : null);
            this._targetAltitude.set(showTargetAltitude ? vnavTargetAltitude : null);
        }
        /**
         * Clears this provider's data.
         */
        clearData() {
            this._phase.set(null);
            this._vnavFlightPhase.set(exports.GarminVNavFlightPhase.None);
            this._vnavTrackingPhase.set(exports.GarminVNavTrackingPhase.None);
            this._isVNavDirectToActive.set(false);
            this._pathMode.set(msfsSdk.VNavPathMode.None);
            this._cruiseAltitude.set(null);
            this._activeConstraintLeg.set(null);
            this._targetAltitude.set(null);
            this._fpa.set(null);
            this._distanceToTod.set(null);
            this._timeToBod.set(null);
            this._timeToTod.set(null);
            this._timeToBoc.set(null);
            this._timeToToc.set(null);
            this._vsRequired.set(null);
            this._verticalDeviation.set(null);
            this._verticalSpeedTarget.set(null);
        }
        /**
         * Resumes this data provider. Once resumed, this data provider will continuously update its data until paused or
         * destroyed.
         * @throws Error if this data provider is dead.
         */
        resume() {
            var _a;
            if (!this.isAlive) {
                throw new Error('DefaultVNavDataProvider: cannot resume a dead provider');
            }
            if (!this.isPaused || !this.isInit) {
                return;
            }
            this.isPaused = false;
            (_a = this.vnavStateSub) === null || _a === void 0 ? void 0 : _a.resume(true);
        }
        /**
         * Pauses this data provider. Once paused, this data provider will not update its data until it is resumed.
         * @throws Error if this data provider is dead.
         */
        pause() {
            var _a, _b;
            if (!this.isAlive) {
                throw new Error('DefaultVNavDataProvider: cannot pause a dead provider');
            }
            if (this.isPaused || !this.isInit) {
                return;
            }
            for (const pauseable of this.pauseable) {
                pauseable.pause();
            }
            (_a = this.clockSub) === null || _a === void 0 ? void 0 : _a.pause();
            (_b = this.vnavStateSub) === null || _b === void 0 ? void 0 : _b.pause();
            this.isPaused = true;
        }
        /**
         * Destroys this data provider. Once destroyed, this data provider will no longer update its provided data, and can
         * no longer be paused or resumed.
         */
        destroy() {
            var _a, _b, _c, _d, _e, _f;
            this.isAlive = false;
            this.indicatedAlt.destroy();
            this.groundSpeed.destroy();
            this.activeNavSource.destroy();
            this.approachDetails.destroy();
            this.vnavState.destroy();
            this.vnavFlightPhaseSource.destroy();
            this.vnavTrackingPhaseSource.destroy();
            this.vnavPathMode.destroy();
            this.vnavCruiseAltitude.destroy();
            this.vnavActiveConstraintLegIndex.destroy();
            this.vnavFpa.destroy();
            this.vnavVsRequired.destroy();
            this.vnavVerticalDeviation.destroy();
            this.vnavTodIndex.destroy();
            this.vnavTodDistance.destroy();
            this.vnavBodIndex.destroy();
            this.vnavBodDistance.destroy();
            this.vnavTocIndex.destroy();
            this.vnavTocDistance.destroy();
            this.vnavBocIndex.destroy();
            this.vnavBocDistance.destroy();
            this.approachGuidanceMode.destroy();
            (_a = this.fmsSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.vnavIndexSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.adcIndexSub) === null || _c === void 0 ? void 0 : _c.destroy();
            (_d = this.cdiIdSub) === null || _d === void 0 ? void 0 : _d.destroy();
            (_e = this.clockSub) === null || _e === void 0 ? void 0 : _e.destroy();
            (_f = this.vnavStateSub) === null || _f === void 0 ? void 0 : _f.destroy();
        }
    }
    /** The amount of time before reaching TOD/BOC when VNAV path tracking data becomes valid, in hours. */
    DefaultVNavDataProvider.PATH_TRACKING_LOOKAHEAD = 1 / 60;

    /**
     * A store for commonly used waypoint info.
     */
    class WaypointInfoStore {
        // eslint-disable-next-line jsdoc/require-returns
        /** The location of this store's current waypoint. */
        get location() {
            return this._location;
        }
        // eslint-disable-next-line jsdoc/require-returns
        /** The distance from the airplane to this store's current waypoint. */
        get distance() {
            return this._distance;
        }
        // eslint-disable-next-line jsdoc/require-returns
        /** The true bearing, in degrees, from the airplane to this store's current waypoint. */
        get bearing() {
            return this._bearing;
        }
        // eslint-disable-next-line jsdoc/require-returns
        /** The radial relative to true north, in degrees, from this store's current waypoint along which the airplane lies. */
        get radial() {
            return this._radial;
        }
        /**
         * Constructor.
         * @param waypoint A subscribable which provides this store's waypoint, or an initial value to set this store's
         * waypoint. If not defined, this store's waypoint can still be set via its `.waypoint` property.
         * @param planePos A subscribable which provides the current airplane position for this store. If not defined, then
         * this store will not provide distance- or bearing-to-waypoint information.
         * @param options Options with which to configure the store.
         */
        constructor(waypoint, planePos, options) {
            var _a;
            this.planePos = planePos;
            /** This store's current waypoint. */
            this.waypoint = msfsSdk.Subject.create(null);
            this._facility = msfsSdk.Subject.create(null);
            /** The facility associated with this store's current waypoint. */
            this.facility = this._facility;
            this._location = msfsSdk.GeoPointSubject.create(WaypointInfoStore.NULL_LOCATION.copy());
            /** The name of this store's current waypoint, or `undefined` if there is no such value. */
            this.name = this._facility.map(facility => {
                if (facility === null || facility === void 0 ? void 0 : facility.name) {
                    return Utils.Translate(facility.name);
                }
                return undefined;
            });
            /** The city associated with this store's current waypoint, or `undefined` if there is no such value. */
            this.city = this._facility.map(facility => {
                if (facility === null || facility === void 0 ? void 0 : facility.city) {
                    return facility.city.split(', ').map(name => Utils.Translate(name)).join(', ');
                }
                return undefined;
            });
            this._distance = msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.NMILE.createNumber(NaN));
            this._bearing = msfsSdk.BasicNavAngleSubject.create(msfsSdk.BasicNavAngleUnit.create(false).createNumber(NaN));
            this._radial = msfsSdk.BasicNavAngleSubject.create(msfsSdk.BasicNavAngleUnit.create(false).createNumber(NaN));
            this.region = this._facility.map(this.getFacilityRegion.bind(this, (_a = options === null || options === void 0 ? void 0 : options.useRegionFallbackForAirport) !== null && _a !== void 0 ? _a : true));
            if (msfsSdk.SubscribableUtils.isSubscribable(waypoint)) {
                this.waypointPipe = waypoint.pipe(this.waypoint);
            }
            else {
                this.waypoint.set(waypoint !== null && waypoint !== void 0 ? waypoint : null);
            }
            this.waypointSub = this.waypoint.sub(this.onWaypointChanged.bind(this), true);
            this.pposSub = planePos === null || planePos === void 0 ? void 0 : planePos.sub(this.onPlanePosChanged.bind(this), true);
        }
        /**
         * Gets the region text for a facility.
         * @param useFallbackForAirport Whether to fall back
         * @param facility The facility for which to get region text.
         * @returns The region text for the specified facility, or `undefined` if the region text could not be retrieved.
         */
        getFacilityRegion(useFallbackForAirport, facility) {
            if (facility === null) {
                return undefined;
            }
            if (msfsSdk.FacilityUtils.isFacilityType(facility, msfsSdk.FacilityType.Airport)) {
                let text = msfsSdk.AirportUtils.tryGetRegionCode(facility);
                if (text !== undefined) {
                    text = Regions.getName(text);
                }
                else if (useFallbackForAirport && facility.city !== '') {
                    // If we've failed to get a region code for the airport, we will fall back to using the city name if available.
                    text = facility.city.split(', ').map(name => Utils.Translate(name)).join(', ');
                }
                return text;
            }
            else {
                const region = Regions.getName(msfsSdk.ICAO.getRegionCode(facility.icao));
                return region === '' ? undefined : region;
            }
        }
        /**
         * A callback which is called when this store's waypoint changes.
         * @param waypoint The new waypoint.
         */
        onWaypointChanged(waypoint) {
            var _a, _b, _c, _d;
            (_a = this.locationSub) === null || _a === void 0 ? void 0 : _a.destroy();
            this.locationSub = undefined;
            (_b = this.facilityPipe) === null || _b === void 0 ? void 0 : _b.destroy();
            this.facilityPipe = undefined;
            const planePos = (_d = (_c = this.planePos) === null || _c === void 0 ? void 0 : _c.get()) !== null && _d !== void 0 ? _d : WaypointInfoStore.NULL_LOCATION;
            this.updateLocation(waypoint);
            this.updateDistance(waypoint, planePos);
            this.updateBearingRadial(waypoint, planePos);
            if (waypoint !== null) {
                this.locationSub = waypoint.location.sub(() => {
                    var _a, _b;
                    const planePos2 = (_b = (_a = this.planePos) === null || _a === void 0 ? void 0 : _a.get()) !== null && _b !== void 0 ? _b : WaypointInfoStore.NULL_LOCATION;
                    this.updateLocation(waypoint);
                    this.updateDistance(waypoint, planePos2);
                    this.updateBearingRadial(waypoint, planePos2);
                });
                if (msfsSdk.FacilityWaypointUtils.isFacilityWaypoint(waypoint)) {
                    this.facilityPipe = waypoint.facility.pipe(this._facility);
                }
                else {
                    this._facility.set(null);
                }
            }
            else {
                this._facility.set(null);
            }
        }
        /**
         * A callback which is called when this store's plane position changes.
         * @param planePos The new plane position.
         */
        onPlanePosChanged(planePos) {
            const waypoint = this.waypoint.get();
            if (waypoint) {
                this.updateDistance(waypoint, planePos);
                this.updateBearingRadial(waypoint, planePos);
            }
        }
        /**
         * Updates this store's location information.
         * @param waypoint The store's current waypoint.
         */
        updateLocation(waypoint) {
            var _a;
            this._location.set((_a = waypoint === null || waypoint === void 0 ? void 0 : waypoint.location.get()) !== null && _a !== void 0 ? _a : WaypointInfoStore.NULL_LOCATION);
        }
        /**
         * Updates this store's distance-to-waypoint information.
         * @param waypoint The store's current waypoint.
         * @param planePos The current position of the airplane.
         */
        updateDistance(waypoint, planePos) {
            if (!waypoint || isNaN(planePos.lat) || isNaN(planePos.lon)) {
                this._distance.set(NaN);
                return;
            }
            this._distance.set(waypoint.location.get().distance(planePos), msfsSdk.UnitType.GA_RADIAN);
        }
        /**
         * Updates this store's bearing-to-waypoint information.
         * @param waypoint The store's current waypoint.
         * @param planePos The current position of the airplane.
         */
        updateBearingRadial(waypoint, planePos) {
            if (!waypoint || isNaN(planePos.lat) || isNaN(planePos.lon)) {
                this._bearing.set(NaN);
                this._radial.set(NaN);
                return;
            }
            const waypointPos = waypoint.location.get();
            this._bearing.set(planePos.bearingTo(waypointPos), planePos.lat, planePos.lon);
            this._radial.set(waypointPos.bearingTo(planePos), waypointPos.lat, waypointPos.lon);
        }
        /**
         * Destroys this store.
         */
        destroy() {
            var _a, _b, _c;
            (_a = this.waypointPipe) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.waypointSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.pposSub) === null || _c === void 0 ? void 0 : _c.destroy();
        }
    }
    WaypointInfoStore.NULL_LOCATION = new msfsSdk.GeoPoint(NaN, NaN);

    /**
     * A enumeration of waypoint alerting states.
     */
    exports.WaypointAlertingState = void 0;
    (function (WaypointAlertingState) {
        WaypointAlertingState["None"] = "None";
        WaypointAlertingState["ArrivingAtWaypoint"] = "ArrivingAtWaypoint";
        WaypointAlertingState["CourseInSeconds"] = "DtKinSeconds";
        WaypointAlertingState["CourseNow"] = "DtkNow";
        WaypointAlertingState["HoldDirect"] = "HoldDirect";
        WaypointAlertingState["HoldParallel"] = "HoldParallel";
        WaypointAlertingState["HoldTeardrop"] = "HoldTeardrop";
        WaypointAlertingState["LeftTurnInSeconds"] = "LeftTurnInSeconds";
        WaypointAlertingState["LeftTurnNow"] = "LeftTurnNow";
        WaypointAlertingState["ParallelTrackEnd"] = "ParallelTrackEnd";
        WaypointAlertingState["RightTurnInSeconds"] = "RightTurnInSeconds";
        WaypointAlertingState["RightTurnNow"] = "RightTurnNow";
    })(exports.WaypointAlertingState || (exports.WaypointAlertingState = {}));
    /**
     * The type of course guidance in a waypoint alerting state, if any.
     */
    exports.WaypointAlertCourseType = void 0;
    (function (WaypointAlertCourseType) {
        WaypointAlertCourseType["DesiredTrack"] = "DesiredTrack";
        WaypointAlertCourseType["Heading"] = "Heading";
    })(exports.WaypointAlertCourseType || (exports.WaypointAlertCourseType = {}));
    /**
     * A class that computes the current waypoint alert state for consumers to use for waypoint alert displays.
     */
    class WaypointAlertComputer {
        // eslint-disable-next-line jsdoc/require-jsdoc
        constructor(bus, arg2, alertLookaheadTime, nowAlertTime) {
            this.bus = bus;
            this.simTime = msfsSdk.ConsumerSubject.create(null, 0);
            this.groundSpeed = msfsSdk.ConsumerSubject.create(null, 0);
            this.ppos = new msfsSdk.GeoPoint(NaN, NaN);
            this.alongTrackSpeed = msfsSdk.ConsumerSubject.create(null, 0);
            this.distanceRemaining = msfsSdk.ConsumerSubject.create(null, 0);
            this.currentDtk = msfsSdk.ConsumerSubject.create(null, 0);
            this.nextDtk = msfsSdk.ConsumerSubject.create(null, 0);
            this.nextDtkMag = msfsSdk.ConsumerSubject.create(null, 0);
            this.nextDtkVector = msfsSdk.ConsumerSubject.create(null, { globalLegIndex: -1, vectorIndex: -1 });
            this.lnavTrackingState = msfsSdk.ConsumerSubject.create(null, {
                isTracking: false,
                globalLegIndex: 0,
                transitionMode: msfsSdk.LNavTransitionMode.None,
                vectorIndex: 0,
                isSuspended: false
            }, msfsSdk.LNavUtils.lnavTrackingStateEquals);
            this._state = msfsSdk.Subject.create({
                previousState: exports.WaypointAlertingState.None,
                newState: exports.WaypointAlertingState.None,
                course: undefined,
                courseType: undefined
            }, (a, b) => {
                return a.previousState === b.previousState
                    && a.newState === b.newState
                    && a.course === b.course
                    && a.courseType === b.courseType;
            });
            /** The current alert state. */
            this.state = this._state;
            /** An event which fires every time the alert state changes. */
            this.onStateChanged = new msfsSdk.SubEvent();
            /** The time remaining for the current alert state, or `NaN` if an alert is not active. */
            this.timeRemaining = msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.SECOND.createNumber(NaN));
            this.canUpdate = false;
            this.previousState = exports.WaypointAlertingState.None;
            this.currentState = exports.WaypointAlertingState.None;
            this.armedNowState = exports.WaypointAlertingState.None;
            this.armedNowLegIndex = -1;
            this.armedNowVectorIndex = -1;
            this.armedNowCourse = NaN;
            this.armedNowCourseMag = NaN;
            this.armedNowCourseType = exports.WaypointAlertCourseType.DesiredTrack;
            this.nowStateTimeStamp = 0;
            this.stateSubject = msfsSdk.Subject.create(exports.WaypointAlertingState.None);
            this.currentCourse = NaN;
            this.currentCourseMag = NaN;
            this.currentCourseType = exports.WaypointAlertCourseType.DesiredTrack;
            this.geoCircleCache = [new msfsSdk.GeoCircle(new Float64Array([0, 0]), 1), new msfsSdk.GeoCircle(new Float64Array([0, 0]), 1)];
            this.geoPointCache = [new msfsSdk.GeoPoint(0, 0), new msfsSdk.GeoPoint(0, 0)];
            let flightPlanner;
            let lnavIndex;
            if (arg2 instanceof msfsSdk.FlightPlanner) {
                flightPlanner = arg2;
            }
            else {
                ({ flightPlanner, lnavIndex, alertLookaheadTime, nowAlertTime } = arg2);
            }
            this.flightPlanner = msfsSdk.SubscribableUtils.toSubscribable(flightPlanner, true);
            this.lnavIndex = msfsSdk.SubscribableUtils.toSubscribable(lnavIndex !== null && lnavIndex !== void 0 ? lnavIndex : 0, true);
            this.alertLookaheadTime = alertLookaheadTime;
            this.nowAlertTime = nowAlertTime !== null && nowAlertTime !== void 0 ? nowAlertTime : WaypointAlertComputer.DEFAULT_NOW_ALERT_TIME;
            const sub = this.bus.getSubscriber();
            this.simTime.setConsumer(sub.on('simTime'));
            this.groundSpeed.setConsumer(sub.on('ground_speed'));
            sub.on('gps-position').handle(pos => this.ppos.set(pos.lat, pos.long));
            this.flightPlanner.sub(this.reset.bind(this));
            this.lnavIndex.sub(this.onLNavIndexChanged.bind(this, sub), true);
            this.stateSubject.sub(this.handleStateChanged.bind(this));
            this.lnavTrackingState.sub(this.handleTrackedIndicesChanged.bind(this));
        }
        /**
         * Responds to when this computer's LNAV index changes.
         * @param lnavEvents An event subscriber for LNAV events.
         * @param index The new LNAV index.
         */
        onLNavIndexChanged(lnavEvents, index) {
            this.reset();
            if (msfsSdk.LNavUtils.isValidLNavIndex(index)) {
                const lnavTopicSuffix = msfsSdk.LNavUtils.getEventBusTopicSuffix(index);
                this.alongTrackSpeed.setConsumer(lnavEvents.on(`lnav_along_track_speed${lnavTopicSuffix}`));
                this.distanceRemaining.setConsumer(lnavEvents.on(`lnavdata_egress_distance${lnavTopicSuffix}`));
                this.currentDtk.setConsumer(lnavEvents.on(`lnavdata_dtk_true${lnavTopicSuffix}`));
                this.nextDtk.setConsumer(lnavEvents.on(`lnavdata_next_dtk_true${lnavTopicSuffix}`));
                this.nextDtkMag.setConsumer(lnavEvents.on(`lnavdata_next_dtk_mag${lnavTopicSuffix}`));
                this.nextDtkVector.setConsumer(lnavEvents.on(`lnavdata_next_dtk_vector${lnavTopicSuffix}`));
                this.lnavTrackingState.setConsumer(lnavEvents.on(`lnav_tracking_state${lnavTopicSuffix}`));
                this.canUpdate = true;
            }
            else {
                this.alongTrackSpeed.setConsumer(null);
                this.distanceRemaining.setConsumer(null);
                this.currentDtk.setConsumer(null);
                this.nextDtk.setConsumer(null);
                this.nextDtkMag.setConsumer(null);
                this.nextDtkVector.setConsumer(null);
                this.lnavTrackingState.setConsumer(null);
                this.canUpdate = false;
            }
        }
        /**
         * Handles when the internal state has changed.
         * @param state The new alerting state.
         */
        handleStateChanged(state) {
            this.previousState = this.currentState;
            this.currentState = state;
            let stateEvent;
            switch (state) {
                case exports.WaypointAlertingState.None:
                case exports.WaypointAlertingState.ArrivingAtWaypoint:
                case exports.WaypointAlertingState.HoldDirect:
                case exports.WaypointAlertingState.HoldParallel:
                case exports.WaypointAlertingState.HoldTeardrop:
                case exports.WaypointAlertingState.ParallelTrackEnd:
                    stateEvent = { previousState: this.previousState, newState: state };
                    break;
                default:
                    stateEvent = { previousState: this.previousState, newState: state, course: this.currentCourse, courseMag: this.currentCourseMag, courseType: this.currentCourseType };
                    break;
            }
            this._state.set(stateEvent);
            this.onStateChanged.notify(this, stateEvent);
        }
        /**
         * Handles when the LNAV tracking state changes.
         * @param state The new LNAV tracking state.
         */
        handleTrackedIndicesChanged(state) {
            if (this.armedNowState !== exports.WaypointAlertingState.None
                && this.armedNowVectorIndex === state.vectorIndex
                && this.armedNowLegIndex === state.globalLegIndex) {
                this.currentCourse = this.armedNowCourse;
                this.currentCourseMag = this.armedNowCourseMag;
                this.currentCourseType = this.armedNowCourseType;
                this.stateSubject.set(this.armedNowState);
                this.nowStateTimeStamp = this.simTime.get();
                this.armedNowState = exports.WaypointAlertingState.None;
                this.armedNowCourse = NaN;
                this.armedNowCourseType = exports.WaypointAlertCourseType.DesiredTrack;
                this.armedNowVectorIndex = -1;
                this.armedNowLegIndex = -1;
            }
        }
        /**
         * Resets this computer's internal state.
         */
        reset() {
            this.previousState = exports.WaypointAlertingState.None;
            this.currentState = exports.WaypointAlertingState.None;
            this.armedNowState = exports.WaypointAlertingState.None;
            this.armedNowLegIndex = -1;
            this.armedNowVectorIndex = -1;
            this.armedNowCourse = NaN;
            this.armedNowCourseMag = NaN;
            this.armedNowCourseType = exports.WaypointAlertCourseType.DesiredTrack;
            this.nowStateTimeStamp = 0;
            this.currentCourse = NaN;
            this.currentCourseMag = NaN;
            this.currentCourseType = exports.WaypointAlertCourseType.DesiredTrack;
            this.stateSubject.set(exports.WaypointAlertingState.None);
            this.timeRemaining.set(NaN);
        }
        /**
         * Updates the WaypointAlertComputer.
         */
        update() {
            if (!this.canUpdate) {
                return;
            }
            const flightPlanner = this.flightPlanner.get();
            const trackingState = this.lnavTrackingState.get();
            if (flightPlanner.hasActiveFlightPlan() && trackingState.isTracking && this.groundSpeed.get() >= 30) {
                const plan = flightPlanner.getActiveFlightPlan();
                // LNAV tracking indexes can lag behind flight plan updates, so we need to be careful when getting the tracked
                // leg because it could have been removed from the flight plan.
                const currentLegDef = plan.tryGetLeg(trackingState.globalLegIndex);
                const nextLegDef = plan.tryGetLeg(trackingState.globalLegIndex + 1);
                if (currentLegDef) {
                    if (this.isInNowState()) {
                        const secondsRemaining = ((this.nowStateTimeStamp + (this.nowAlertTime * 1000)) - this.simTime.get()) / 1000;
                        if (secondsRemaining <= 0 || secondsRemaining > this.nowAlertTime) {
                            this.stateSubject.set(exports.WaypointAlertingState.None);
                            this.timeRemaining.set(NaN);
                        }
                        else {
                            this.timeRemaining.set(secondsRemaining);
                            return;
                        }
                    }
                    switch (currentLegDef.leg.type) {
                        case msfsSdk.LegType.HA:
                        case msfsSdk.LegType.HF:
                        case msfsSdk.LegType.HM:
                        case msfsSdk.LegType.PI:
                            this.handleHoldAndPILegs(currentLegDef, nextLegDef);
                            break;
                        default:
                            this.handleDefaultLegs(currentLegDef, nextLegDef);
                            break;
                    }
                    return;
                }
            }
            this.stateSubject.set(exports.WaypointAlertingState.None);
            this.timeRemaining.set(NaN);
            this.armedNowState = exports.WaypointAlertingState.None;
            this.nowStateTimeStamp = 0;
            this.armedNowVectorIndex = -1;
        }
        /**
         * Handles the default legs cases.
         * @param currentLegDef The definition of the current leg.
         * @param nextLegDef The next leg definition.
         */
        handleDefaultLegs(currentLegDef, nextLegDef) {
            const alongTrackSpeed = this.alongTrackSpeed.get();
            const distanceRemaining = this.distanceRemaining.get();
            const secondsRemaining = alongTrackSpeed !== 0 ? (distanceRemaining / alongTrackSpeed) * 3600 : Infinity;
            if (this.nextDtkVector.get().globalLegIndex === -1) {
                this.handleArrivingAtWaypoint(secondsRemaining);
            }
            else {
                const enteredHold = this.tryEnterHoldState(nextLegDef, secondsRemaining);
                if (!enteredHold && nextLegDef !== null && nextLegDef.calculated !== undefined && currentLegDef.calculated !== undefined) {
                    const withinAlertDistance = secondsRemaining >= 0 && secondsRemaining <= this.alertLookaheadTime;
                    let nextDtk = this.nextDtk.get();
                    let nextDtkMag = this.nextDtkMag.get();
                    if (nextLegDef.calculated !== undefined && this.legIsHold(nextLegDef)) {
                        nextDtk = this.getInitialHoldDtk(nextLegDef.calculated);
                        // use magvar at the hold fix (which is always located at the end of the hold leg)
                        nextDtkMag = nextLegDef.calculated.endLat !== undefined && nextLegDef.calculated.endLon !== undefined
                            ? msfsSdk.MagVar.trueToMagnetic(nextDtk, nextLegDef.calculated.endLat, nextLegDef.calculated.endLon)
                            : nextDtk;
                    }
                    else if (nextLegDef.calculated !== undefined && nextLegDef.leg.type === msfsSdk.LegType.PI) {
                        nextDtk = this.getInitialPIDtk(nextLegDef.calculated);
                        // use magvar at the leg origin
                        nextDtkMag = nextLegDef.calculated.startLat !== undefined && nextLegDef.calculated.startLon !== undefined
                            ? msfsSdk.MagVar.trueToMagnetic(nextDtk, nextLegDef.calculated.startLat, nextLegDef.calculated.startLon)
                            : nextDtk;
                    }
                    const egressVector = currentLegDef.calculated.egress[0];
                    let turnDirection = undefined;
                    if (egressVector !== undefined) {
                        turnDirection = msfsSdk.FlightPathUtils.getTurnDirectionFromCircle(msfsSdk.FlightPathUtils.setGeoCircleFromVector(currentLegDef.calculated.egress[0], this.geoCircleCache[0]));
                    }
                    const isNextLegHeading = nextLegDef.leg.type === msfsSdk.LegType.VA || nextLegDef.leg.type === msfsSdk.LegType.VD
                        || nextLegDef.leg.type === msfsSdk.LegType.VI || nextLegDef.leg.type === msfsSdk.LegType.VR;
                    this.currentCourse = nextDtk;
                    this.currentCourseMag = nextDtkMag;
                    this.currentCourseType = isNextLegHeading ? exports.WaypointAlertCourseType.Heading : exports.WaypointAlertCourseType.DesiredTrack;
                    if (withinAlertDistance) {
                        const currentLegSupportsTurn = this.doesLegTypeSupportTurn(currentLegDef.leg.type, false);
                        const nextLegSupportsTurn = this.doesLegTypeSupportTurn(nextLegDef.leg.type, true);
                        const turnIsLargerThan10Degrees = currentLegSupportsTurn && nextLegSupportsTurn && Math.abs(msfsSdk.NavMath.diffAngle(nextDtk, this.currentDtk.get())) >= 10;
                        if (turnDirection !== undefined && currentLegSupportsTurn && nextLegSupportsTurn && turnIsLargerThan10Degrees) {
                            this.stateSubject.set(turnDirection === 'left' ? exports.WaypointAlertingState.LeftTurnInSeconds : exports.WaypointAlertingState.RightTurnInSeconds);
                            this.armedNowState = turnDirection === 'left' ? exports.WaypointAlertingState.LeftTurnNow : exports.WaypointAlertingState.RightTurnNow;
                        }
                        else {
                            this.stateSubject.set(exports.WaypointAlertingState.CourseInSeconds);
                            this.armedNowState = exports.WaypointAlertingState.CourseNow;
                        }
                        this.armedNowCourse = this.currentCourse;
                        this.armedNowCourseMag = this.currentCourseMag;
                        this.armedNowCourseType = this.currentCourseType;
                        this.armedNowVectorIndex = 0;
                        const trackingState = this.lnavTrackingState.get();
                        if (currentLegDef.calculated.egress.length > 0) {
                            this.armedNowLegIndex = trackingState.globalLegIndex;
                        }
                        else {
                            this.armedNowLegIndex = trackingState.globalLegIndex + 1;
                        }
                        this.timeRemaining.set(secondsRemaining);
                    }
                    else if (!this.isInNowState()) {
                        this.stateSubject.set(exports.WaypointAlertingState.None);
                        this.timeRemaining.set(NaN);
                    }
                }
            }
        }
        /**
         * Checks whether the provided leg type can support a WaypointAlertingState.LeftTurnNow/RightTurnNow
         * instead of a WaypointAlertingState.Course message.
         * @param legType The leg type.
         * @param isNextLeg Whether this leg type is the next leg (true) or the current leg (false).
         * @returns Whether or not the leg supports a LeftTurn/RightTurn WaypointAlertingState.
         */
        doesLegTypeSupportTurn(legType, isNextLeg) {
            switch (legType) {
                case msfsSdk.LegType.FM:
                case msfsSdk.LegType.VM:
                case msfsSdk.LegType.CA:
                case msfsSdk.LegType.FA:
                case msfsSdk.LegType.VA:
                case msfsSdk.LegType.FC:
                case msfsSdk.LegType.CD:
                case msfsSdk.LegType.FD:
                case msfsSdk.LegType.VD:
                case msfsSdk.LegType.CR:
                case msfsSdk.LegType.VR:
                case msfsSdk.LegType.CI:
                case msfsSdk.LegType.VI:
                case msfsSdk.LegType.PI:
                    return isNextLeg ? true : false;
                case msfsSdk.LegType.HA:
                case msfsSdk.LegType.HF:
                case msfsSdk.LegType.HM:
                    return false;
                default:
                    return true;
            }
        }
        /**
         * Checks whether or not the current state is a now type state.
         * @returns True if in a now type state, false otherwise.
         */
        isInNowState() {
            const currentState = this.stateSubject.get();
            return currentState === exports.WaypointAlertingState.CourseNow || currentState === exports.WaypointAlertingState.LeftTurnNow || currentState === exports.WaypointAlertingState.RightTurnNow;
        }
        /**
         * Gets the initial DTK for a hold leg.
         * @param nextLegCalcs The next leg in the flight plan.
         * @returns The DTK in degrees true, or undefined if one could not be determined.
         */
        getInitialHoldDtk(nextLegCalcs) {
            const firstVector = nextLegCalcs.ingress[0];
            const entryFlags = msfsSdk.BitFlags.union(msfsSdk.FlightPathVectorFlags.HoldParallelEntry, msfsSdk.FlightPathVectorFlags.HoldTeardropEntry);
            const isParallelOrTeardrop = firstVector && msfsSdk.BitFlags.isAny(firstVector.flags, entryFlags);
            if (isParallelOrTeardrop) {
                if (msfsSdk.FlightPathUtils.isVectorGreatCircle(firstVector)) {
                    return msfsSdk.FlightPathUtils.getVectorInitialCourse(firstVector);
                }
                else {
                    //Since start is exclusive, use -1 instead of 0 here
                    const turnEndVector = this.getNextTurnIndex(-1, nextLegCalcs.ingress, true);
                    return msfsSdk.FlightPathUtils.getVectorFinalCourse(nextLegCalcs.ingress[turnEndVector]);
                }
            }
            else {
                return msfsSdk.FlightPathUtils.getVectorInitialCourse(nextLegCalcs.flightPath[1]);
            }
        }
        /**
         * Gets the initial DTK for a PI leg.
         * @param nextLegCalcs The leg calculations for the next leg in the flight plan.
         * @returns The DTK in degrees true.
         */
        getInitialPIDtk(nextLegCalcs) {
            return msfsSdk.FlightPathUtils.getVectorFinalCourse(nextLegCalcs.flightPath[0]);
        }
        /**
         * Handles alerting while on hold and procedure turn legs.
         * @param currentLegDef The definition of the current leg.
         * @param nextLegDef The definition of the next leg.
         */
        handleHoldAndPILegs(currentLegDef, nextLegDef) {
            var _a, _b, _c;
            const trackingState = this.lnavTrackingState.get();
            const transitionMode = trackingState.transitionMode;
            const flightPath = transitionMode === msfsSdk.LNavTransitionMode.Ingress ? (_a = currentLegDef.calculated) === null || _a === void 0 ? void 0 : _a.ingress : (_b = currentLegDef.calculated) === null || _b === void 0 ? void 0 : _b.flightPath;
            const alongTrackSpeed = this.alongTrackSpeed.get();
            let turnVectorIndex = -1;
            if (flightPath !== undefined && !msfsSdk.FlightPathUtils.isVectorGreatCircle(flightPath[trackingState.vectorIndex])) {
                const currentTurnEnd = this.getNextTurnIndex(trackingState.vectorIndex - 1, flightPath, true);
                if (currentTurnEnd !== -1) {
                    turnVectorIndex = this.getNextTurnIndex(currentTurnEnd, flightPath, false);
                }
            }
            else {
                turnVectorIndex = this.getNextTurnIndex(trackingState.vectorIndex, flightPath, false);
            }
            if (this.legIsHold(currentLegDef) && turnVectorIndex === -1 && currentLegDef.calculated !== undefined && transitionMode === msfsSdk.LNavTransitionMode.None) {
                if (trackingState.isSuspended) {
                    turnVectorIndex = currentLegDef.calculated.flightPath.length - 4;
                }
                else {
                    this.handleDefaultLegs(currentLegDef, nextLegDef);
                    return;
                }
            }
            const vector = flightPath !== undefined ? flightPath[turnVectorIndex] : undefined;
            if (turnVectorIndex !== -1 && vector !== undefined) {
                const distanceRemaining = this.getSegmentDistanceRemaining(trackingState.vectorIndex, turnVectorIndex, flightPath);
                const secondsRemaining = alongTrackSpeed !== 0 ? (distanceRemaining / alongTrackSpeed) * 3600 : Infinity;
                const withinAlertDistance = secondsRemaining >= 0 && secondsRemaining <= this.alertLookaheadTime;
                const turnEndVectorIndex = this.getNextTurnIndex(trackingState.vectorIndex, flightPath, true);
                let turnEndVector = flightPath !== undefined ? flightPath[turnEndVectorIndex] : undefined;
                let nextDtk = msfsSdk.FlightPathUtils.getVectorFinalCourse(vector);
                let nextDtkMag = msfsSdk.MagVar.trueToMagnetic(nextDtk, vector.endLat, vector.endLon);
                //If we're at the end of a hold entry, the real DTK is the end course of the first racetrack leg
                if (flightPath !== undefined && turnEndVectorIndex === flightPath.length - 1 && transitionMode === msfsSdk.LNavTransitionMode.Ingress) {
                    turnEndVector = (_c = currentLegDef.calculated) === null || _c === void 0 ? void 0 : _c.flightPath[0];
                }
                if (turnEndVector !== undefined) {
                    nextDtk = msfsSdk.FlightPathUtils.getVectorFinalCourse(turnEndVector);
                    nextDtkMag = msfsSdk.MagVar.trueToMagnetic(nextDtk, turnEndVector.endLat, turnEndVector.endLon);
                }
                this.currentCourse = nextDtk;
                this.currentCourseMag = nextDtkMag;
                this.currentCourseType = exports.WaypointAlertCourseType.DesiredTrack;
                const turnDirection = msfsSdk.FlightPathUtils.getTurnDirectionFromCircle(msfsSdk.FlightPathUtils.setGeoCircleFromVector(vector, this.geoCircleCache[0]));
                if (withinAlertDistance) {
                    this.stateSubject.set(turnDirection === 'left' ? exports.WaypointAlertingState.LeftTurnInSeconds : exports.WaypointAlertingState.RightTurnInSeconds);
                    this.armedNowState = turnDirection === 'left' ? exports.WaypointAlertingState.LeftTurnNow : exports.WaypointAlertingState.RightTurnNow;
                    this.armedNowVectorIndex = turnVectorIndex;
                    this.armedNowLegIndex = trackingState.globalLegIndex;
                    this.armedNowCourse = this.currentCourse;
                    this.armedNowCourseMag = this.currentCourseMag;
                    this.armedNowCourseType = this.currentCourseType;
                    this.timeRemaining.set(secondsRemaining);
                }
                else if (!this.isInNowState()) {
                    this.stateSubject.set(exports.WaypointAlertingState.None);
                    this.timeRemaining.set(NaN);
                }
            }
            else if (!this.isInNowState()) {
                this.stateSubject.set(exports.WaypointAlertingState.None);
                this.timeRemaining.set(NaN);
            }
        }
        /**
         * Determines if a leg is a hold leg.
         * @param legDef The leg definition to check.
         * @returns True if a hold, false otherwise.
         */
        legIsHold(legDef) {
            return legDef.leg.type === msfsSdk.LegType.HA || legDef.leg.type === msfsSdk.LegType.HF || legDef.leg.type === msfsSdk.LegType.HM;
        }
        /**
         * Gets the next turn vector index in a set of leg calculations.
         * @param startIndex The index, non-inclusive, to start searching at.
         * @param flightPath The set of leg vectors to pull from.
         * @param includeContinuous Whether or not to include turns that are continous to the next turn.
         * @returns The next turn vector index, or -1 if none found.
         */
        getNextTurnIndex(startIndex, flightPath, includeContinuous) {
            let foundVectorIndex = -1;
            if (flightPath !== undefined) {
                let currentVector = undefined;
                for (let i = startIndex + 1; i < flightPath.length; i++) {
                    if (currentVector !== undefined) {
                        if (this.areTurnsContinuous(currentVector, flightPath[i])) {
                            currentVector = flightPath[i];
                            foundVectorIndex = i;
                        }
                        else {
                            return foundVectorIndex;
                        }
                    }
                    if (!msfsSdk.FlightPathUtils.isVectorGreatCircle(flightPath[i])) {
                        currentVector = flightPath[i];
                        foundVectorIndex = i;
                        if (!includeContinuous) {
                            return foundVectorIndex;
                        }
                    }
                }
            }
            return foundVectorIndex;
        }
        /**
         * Checks to see if two turn vectors are effectively continous (a continuation of the same circle).
         * @param a The first vector.
         * @param b The second vector.
         * @returns Whether or not the two vectors are continuous.
         */
        areTurnsContinuous(a, b) {
            //If we're on a turn already, then we only need to check radii
            if (Math.abs(a.radius - b.radius) <= msfsSdk.GeoCircle.ANGULAR_TOLERANCE) {
                return true;
            }
            return false;
        }
        /**
         * Gets the amount of distance, in nautical miles, remaining before the end of a portion of a leg.
         * @param startIndex The start vector index, inclusive.
         * @param endIndex The end vector index, exclusive.
         * @param flightPath The set of leg vectors to pull from.
         * @returns The amount of distance remaining.
         */
        getSegmentDistanceRemaining(startIndex, endIndex, flightPath) {
            if (flightPath !== undefined) {
                let distance = 0;
                //For holds, our end index outbound turn is vector 0, so fake this in order to
                //at least count the one inbound vector's remaining distance
                if (endIndex < startIndex) {
                    endIndex = flightPath.length;
                }
                for (let i = startIndex; i < endIndex; i++) {
                    const vector = flightPath[i];
                    if (i === startIndex) {
                        const circle = msfsSdk.FlightPathUtils.setGeoCircleFromVector(vector, this.geoCircleCache[0]);
                        const start = this.geoPointCache[0].set(vector.startLat, vector.startLon);
                        const end = this.geoPointCache[1].set(vector.endLat, vector.endLon);
                        const arcDistance = msfsSdk.FlightPathUtils.getAlongArcSignedDistance(circle, start, end, this.ppos);
                        distance += msfsSdk.UnitType.METER.convertTo(vector.distance, msfsSdk.UnitType.NMILE) - msfsSdk.UnitType.GA_RADIAN.convertTo(arcDistance, msfsSdk.UnitType.NMILE);
                    }
                    else {
                        distance += msfsSdk.UnitType.METER.convertTo(vector.distance, msfsSdk.UnitType.NMILE);
                    }
                }
                return distance;
            }
            return NaN;
        }
        /**
         * Attempts to enter the hold state if applicable.
         * @param nextLegDef The next leg in the plan.
         * @param secondsRemaining The number of seconds remaining before the waypoint.
         * @returns True if entered a hold state, false otherwise.
         */
        tryEnterHoldState(nextLegDef, secondsRemaining) {
            const isNextLegHold = nextLegDef !== null && this.legIsHold(nextLegDef);
            if (isNextLegHold && nextLegDef.calculated !== undefined && secondsRemaining >= 0) {
                let holdState = exports.WaypointAlertingState.HoldDirect;
                if (nextLegDef.calculated.ingress.length > 0) {
                    if (msfsSdk.BitFlags.isAny(nextLegDef.calculated.ingress[0].flags, msfsSdk.FlightPathVectorFlags.HoldParallelEntry)) {
                        holdState = exports.WaypointAlertingState.HoldParallel;
                    }
                    else if (msfsSdk.BitFlags.isAny(nextLegDef.calculated.ingress[0].flags, msfsSdk.FlightPathVectorFlags.HoldTeardropEntry)) {
                        holdState = exports.WaypointAlertingState.HoldTeardrop;
                    }
                }
                const holdAlertMinimum = this.alertLookaheadTime;
                const holdAlertMaximum = holdAlertMinimum + WaypointAlertComputer.HOLD_ALERT_TIME;
                const withinAlertDistance = secondsRemaining <= holdAlertMaximum && secondsRemaining > holdAlertMinimum;
                if (withinAlertDistance) {
                    this.stateSubject.set(holdState);
                    this.timeRemaining.set(secondsRemaining - this.alertLookaheadTime);
                    return true;
                }
            }
            return false;
        }
        /**
         * Handles when the computer is potentially in the ArrivingAtWaypoint state.
         * @param secondsRemaining The number of seconds remaining until the waypoint.
         */
        handleArrivingAtWaypoint(secondsRemaining) {
            if (secondsRemaining < 0) {
                this.stateSubject.set(exports.WaypointAlertingState.None);
                this.timeRemaining.set(NaN);
            }
            else {
                const withinAlertDistance = secondsRemaining <= this.alertLookaheadTime;
                this.stateSubject.set(withinAlertDistance ? exports.WaypointAlertingState.ArrivingAtWaypoint : exports.WaypointAlertingState.None);
                this.timeRemaining.set(withinAlertDistance ? secondsRemaining : NaN);
            }
        }
    }
    WaypointAlertComputer.DEFAULT_NOW_ALERT_TIME = 5;
    WaypointAlertComputer.HOLD_ALERT_TIME = 10;

    /**
     * Controls the target and range of a waypoint map based on the selected waypoint.
     */
    class WaypointMapRTRController extends msfsSdk.MapSystemController {
        /**
         * Constructor.
         * @param context This controller's map context.
         * @param supportAirportAutoRange Whether this controller automatically adjusts the map range when an airport is
         * the highlighted waypoint to give an appropriate view of the selected runway, or all runways if there is no
         * selected runway.
         * @param defaultAirportRangeIndex A subscribable which provides the default map range index to apply when a range
         * cannot be automatically selected for an airport. Ignored if `supportAirportAutoRange` is `false`. If not defined,
         * the map range will not be reset when targeting an airport and a range cannot be automatically selected.
         * @param airportAutoRangeMargins The nominal margins (relative to the map's dead zone boundaries), to respect when
         * calculating the map range for airports, as `[left, top, right, bottom]` in pixels. Ignored if
         * `supportAirportAutoRange` is `false`. Defaults to `[0, 0, 0, 0]`.
         */
        constructor(context, supportAirportAutoRange, defaultAirportRangeIndex, airportAutoRangeMargins) {
            super(context);
            this.supportAirportAutoRange = supportAirportAutoRange;
            this.defaultAirportRangeIndex = defaultAirportRangeIndex;
            this.rangeModule = this.context.model.getModule(GarminMapKeys.Range);
            this.waypointSelectModule = this.context.model.getModule(GarminMapKeys.WaypointSelection);
            this.targetParams = {
                target: new msfsSdk.GeoPoint(0, 0)
            };
            this.targetControl = this.context[msfsSdk.MapSystemKeys.TargetControl];
            this.hasTargetControl = msfsSdk.Subject.create(this.targetControl === undefined);
            this.targetControlConsumer = {
                priority: MapResourcePriority.WAYPOINT_HIGHLIGHT,
                onAcquired: () => { this.hasTargetControl.set(true); },
                onCeded: () => { this.hasTargetControl.set(false); }
            };
            this.rangeControl = this.context[msfsSdk.MapSystemKeys.RangeControl];
            this.hasRangeControl = msfsSdk.Subject.create(this.rangeControl === undefined);
            this.rangeControlConsumer = {
                priority: MapResourcePriority.WAYPOINT_HIGHLIGHT,
                onAcquired: () => { this.hasRangeControl.set(true); },
                onCeded: () => { this.hasRangeControl.set(false); }
            };
            this.canTargetWaypoint = msfsSdk.MappedSubject.create(([hasTargetControl, hasRangeControl]) => {
                return hasTargetControl && hasRangeControl;
            }, this.hasTargetControl, this.hasRangeControl);
            this.pointHeap = new msfsSdk.ResourceHeap(() => new msfsSdk.GeoPoint(0, 0), () => { });
            this.margins = msfsSdk.VecNSubject.create(msfsSdk.VecNMath.create(4));
            this.fovCalculator = new msfsSdk.MapFieldOfViewCalculator();
            this.fov = {
                target: new msfsSdk.GeoPoint(NaN, NaN),
                range: NaN
            };
            this.waypointState = msfsSdk.MappedSubject.create(this.waypointSelectModule.waypoint, this.waypointSelectModule.runway);
            this.nominalMargins = airportAutoRangeMargins !== null && airportAutoRangeMargins !== void 0 ? airportAutoRangeMargins : msfsSdk.Subject.create(msfsSdk.VecNMath.create(4));
            if (supportAirportAutoRange) {
                this.marginState = msfsSdk.MappedSubject.create(this.nominalMargins, this.context.deadZone);
            }
        }
        /** @inheritdoc */
        onAfterMapRender() {
            var _a;
            this.canTargetWaypoint.sub((canTarget) => {
                if (canTarget) {
                    this.tryTargetWaypoint(false);
                }
            });
            (_a = this.marginState) === null || _a === void 0 ? void 0 : _a.sub(([boundsOffset, deadZone]) => {
                this.margins.set(deadZone[0] + boundsOffset[0], deadZone[1] + boundsOffset[1], deadZone[2] + boundsOffset[2], deadZone[3] + boundsOffset[3]);
            }, true);
            this.waypointState.sub(([waypoint]) => {
                var _a, _b, _c, _d, _e;
                (_a = this.waypointLocationSub) === null || _a === void 0 ? void 0 : _a.destroy();
                this.waypointLocationSub = undefined;
                if (waypoint === null) {
                    (_b = this.targetControl) === null || _b === void 0 ? void 0 : _b.forfeit(this.targetControlConsumer);
                    (_c = this.rangeControl) === null || _c === void 0 ? void 0 : _c.forfeit(this.rangeControlConsumer);
                }
                else {
                    (_d = this.targetControl) === null || _d === void 0 ? void 0 : _d.claim(this.targetControlConsumer);
                    (_e = this.rangeControl) === null || _e === void 0 ? void 0 : _e.claim(this.rangeControlConsumer);
                    this.waypointLocationSub = waypoint.location.sub(() => { this.tryTargetWaypoint(true); });
                }
                this.tryTargetWaypoint(true);
            }, true);
            this.margins.sub(() => { this.tryTargetWaypoint(false); });
        }
        /** @inheritdoc */
        onMapProjectionChanged(mapProjection, changeFlags) {
            if (msfsSdk.BitFlags.isAll(changeFlags, msfsSdk.MapProjectionChangeType.ProjectedSize) && this.supportAirportAutoRange) {
                this.tryTargetWaypoint(false);
            }
        }
        /**
         * Attempts to target the map to the selected waypoint. If there is no selected waypoint or this controller does not
         * have map target or range control privileges, this method does nothing.
         * @param resetRange Whether to reset the map range if the selected waypoint is an airport and airport auto-range is
         * supported.
         * @returns Whether the map was successfully targeted.
         */
        tryTargetWaypoint(resetRange) {
            if (!this.canTargetWaypoint.get()) {
                return false;
            }
            const waypoint = this.waypointSelectModule.waypoint.get();
            const runway = this.waypointSelectModule.runway.get();
            if (waypoint === null) {
                return false;
            }
            let target = waypoint.location.get();
            let rangeIndex = -1;
            if (this.supportAirportAutoRange && waypoint instanceof AirportWaypoint) {
                const fov = this.calculateFovForAirport(waypoint, runway, this.fov);
                if (isNaN(fov.target.lat) || isNaN(fov.target.lon)) {
                    // A field of view could not be calculated -> keep the default target of the airport waypoint and if
                    // necessary, set the default range index if one exists.
                    if (resetRange && this.defaultAirportRangeIndex !== undefined) {
                        rangeIndex = this.defaultAirportRangeIndex.get();
                    }
                }
                else {
                    target = fov.target;
                    if (resetRange) {
                        const ranges = this.rangeModule.nominalRanges.get();
                        rangeIndex = ranges.findIndex(range => range.compare(fov.range, msfsSdk.UnitType.GA_RADIAN) >= 0);
                        if (rangeIndex < 0) {
                            // All map ranges were smaller than the desired range, so we set the largest range possible.
                            rangeIndex = ranges.length - 1;
                        }
                    }
                }
            }
            this.targetParams.target.set(target);
            this.context.projection.setQueued(this.targetParams);
            if (rangeIndex >= 0) {
                this.context.getController(GarminMapKeys.Range).setRangeIndex(rangeIndex);
            }
            return true;
        }
        /**
         * Calculates the field of view for a selected airport and optional selected runway.
         * @param airport The selected airport.
         * @param runway The selected runway.
         * @param out The object to which to write the results.
         * @returns The field of view for the selected airport and optional selected runway.
         */
        calculateFovForAirport(airport, runway, out) {
            out.target.set(NaN, NaN);
            out.range = NaN;
            const runways = airport.facility.get().runways;
            if (runways.length === 0) {
                return out;
            }
            const focus = [];
            if (runway === null) {
                for (let i = 0; i < runways.length; i++) {
                    const airportRunway = runways[i];
                    const runwayHalfLength = msfsSdk.UnitType.METER.convertTo(airportRunway.length / 2, msfsSdk.UnitType.GA_RADIAN);
                    const runwayStart = this.pointHeap.allocate().set(airportRunway.latitude, airportRunway.longitude).offset(airportRunway.direction + 180, runwayHalfLength);
                    const runwayEnd = this.pointHeap.allocate().set(airportRunway.latitude, airportRunway.longitude).offset(airportRunway.direction, runwayHalfLength);
                    focus.push(runwayStart, runwayEnd);
                }
            }
            else {
                const runwayHalfLength = msfsSdk.UnitType.METER.convertTo(runway.length / 2, msfsSdk.UnitType.GA_RADIAN);
                const runwayStart = this.pointHeap.allocate().set(runway.latitude, runway.longitude).offset(runway.direction + 180, runwayHalfLength);
                const runwayEnd = this.pointHeap.allocate().set(runway.latitude, runway.longitude).offset(runway.direction, runwayHalfLength);
                focus.push(runwayStart, runwayEnd);
            }
            this.fovCalculator.calculateFov(this.context.projection, focus, this.margins.get(), out);
            for (let i = 0; i < focus.length; i++) {
                this.pointHeap.free(focus[i]);
            }
            return out;
        }
        /** @inheritdoc */
        onMapDestroyed() {
            this.destroy();
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c, _d;
            (_a = this.targetControl) === null || _a === void 0 ? void 0 : _a.forfeit(this.targetControlConsumer);
            (_b = this.rangeControl) === null || _b === void 0 ? void 0 : _b.forfeit(this.rangeControlConsumer);
            this.waypointState.destroy();
            (_c = this.marginState) === null || _c === void 0 ? void 0 : _c.destroy();
            (_d = this.waypointLocationSub) === null || _d === void 0 ? void 0 : _d.destroy();
            super.destroy();
        }
    }

    /**
     * Setting modes for weather map orientation.
     */
    exports.WeatherMapOrientationSettingMode = void 0;
    (function (WeatherMapOrientationSettingMode) {
        WeatherMapOrientationSettingMode["NorthUp"] = "NorthUp";
        WeatherMapOrientationSettingMode["TrackUp"] = "TrackUp";
        WeatherMapOrientationSettingMode["HeadingUp"] = "HeadingUp";
        WeatherMapOrientationSettingMode["DtkUp"] = "DtkUp";
        WeatherMapOrientationSettingMode["SyncToNavMap"] = "SyncToNavMap";
    })(exports.WeatherMapOrientationSettingMode || (exports.WeatherMapOrientationSettingMode = {}));
    /**
     * A utility class for working with weather map user settings.
     */
    class WeatherMapUserSettingsUtils {
    }
    /** An array of all generic weather map user setting names. */
    WeatherMapUserSettingsUtils.SETTING_NAMES = [
        'weatherMapRangeIndex',
        'weatherMapOrientation'
    ];
    /** An array of all Connext weather map user setting names. */
    WeatherMapUserSettingsUtils.CONNEXT_SETTING_NAMES = [
        'connextMapRadarOverlayShow',
        'connextMapRadarOverlayRangeIndex'
    ];

    /**
     * Controls the orientation of a weather map based on user settings.
     * @deprecated New, preferred logic for controlling weather map orientation based on user settings is available using
     * `WeatherMapOrientationSettingsController`.
     */
    class WeatherMapOrientationController extends msfsSdk.MapSystemController {
        /**
         * Constructor.
         * @param context This controller's map context.
         * @param settingManager The setting manager used by this controller.
         */
        constructor(context, settingManager) {
            super(context);
            this.settingManager = settingManager;
            this.rangeModule = this.context.model.getModule(GarminMapKeys.Range);
            this.orientationModule = this.context.model.getModule(GarminMapKeys.Orientation);
            this.pointerModule = this.context.model.getModule(GarminMapKeys.Pointer);
            this.orientationControl = this.context[GarminMapKeys.OrientationControl];
            this.orientationControlConsumer = {
                priority: MapResourcePriority.ORIENTATION,
                onAcquired: () => {
                    var _a;
                    // If pointer is active, preserve the initial orientation mode when we regain control, but pass through any
                    // further changes in the orientation setting to the model. This ensures that if some other controller was
                    // manually setting the orientation mode but forfeited control, we don't force a change in orientation as control
                    // passes back to us
                    (_a = this.orientationSub) === null || _a === void 0 ? void 0 : _a.resume(this.pointerModule === undefined || !this.pointerModule.isActive.get());
                },
                onCeded: () => {
                    var _a;
                    (_a = this.orientationSub) === null || _a === void 0 ? void 0 : _a.pause();
                }
            };
        }
        /** @inheritdoc */
        onAfterMapRender() {
            var _a, _b, _c;
            const weatherMapOrientationSetting = this.settingManager.tryGetSetting('weatherMapOrientation');
            if (weatherMapOrientationSetting !== undefined) {
                const orientationSetting = this.settingManager.tryGetSetting('mapOrientation');
                if (orientationSetting === undefined) {
                    this.orientation = weatherMapOrientationSetting.map(weatherOrientation => { var _a; return (_a = WeatherMapOrientationController.WEATHER_MODE_MAP[weatherOrientation]) !== null && _a !== void 0 ? _a : exports.MapOrientation.HeadingUp; });
                }
                else {
                    this.orientation = msfsSdk.MappedSubject.create(([weatherOrientation, orientation, isAutoNorthUpActive, autoNorthUpRangeIndex, rangeIndex]) => {
                        var _a, _b;
                        return weatherOrientation === exports.WeatherMapOrientationSettingMode.SyncToNavMap
                            ? isAutoNorthUpActive && rangeIndex > autoNorthUpRangeIndex
                                ? exports.MapOrientation.NorthUp
                                : (_a = WeatherMapOrientationController.MODE_MAP[orientation]) !== null && _a !== void 0 ? _a : exports.MapOrientation.NorthUp
                            : (_b = WeatherMapOrientationController.WEATHER_MODE_MAP[weatherOrientation]) !== null && _b !== void 0 ? _b : exports.MapOrientation.HeadingUp;
                    }, weatherMapOrientationSetting, orientationSetting, (_a = this.settingManager.tryGetSetting('mapAutoNorthUpActive')) !== null && _a !== void 0 ? _a : msfsSdk.Subject.create(false), (_b = this.settingManager.tryGetSetting('mapAutoNorthUpRangeIndex')) !== null && _b !== void 0 ? _b : msfsSdk.Subject.create(0), this.rangeModule.nominalRangeIndex);
                }
                const orientationSub = this.orientationSub = this.orientation.sub(orientation => { this.orientationModule.orientation.set(orientation); }, false, true);
                this.isPointerActiveSub = (_c = this.pointerModule) === null || _c === void 0 ? void 0 : _c.isActive.sub(isActive => {
                    if (!isActive && !orientationSub.isPaused) {
                        // If pointer deactivates while we have control, sync orientation with setting in case we inhibited
                        // the sync when control passed to us.
                        orientationSub.pause();
                        orientationSub.resume(true);
                    }
                });
                this.orientationControl.claim(this.orientationControlConsumer);
            }
        }
        /** @inheritdoc */
        onMapDestroyed() {
            this.destroy();
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c;
            super.destroy();
            this.orientationControl.forfeit(this.orientationControlConsumer);
            (_a = this.orientation) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.orientationSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.isPointerActiveSub) === null || _c === void 0 ? void 0 : _c.destroy();
        }
    }
    WeatherMapOrientationController.MODE_MAP = {
        [exports.MapOrientationSettingMode.NorthUp]: exports.MapOrientation.NorthUp,
        [exports.MapOrientationSettingMode.HeadingUp]: exports.MapOrientation.HeadingUp,
        [exports.MapOrientationSettingMode.TrackUp]: exports.MapOrientation.TrackUp,
        [exports.MapOrientationSettingMode.DtkUp]: exports.MapOrientation.DtkUp
    };
    WeatherMapOrientationController.WEATHER_MODE_MAP = {
        [exports.WeatherMapOrientationSettingMode.NorthUp]: exports.MapOrientation.NorthUp,
        [exports.WeatherMapOrientationSettingMode.HeadingUp]: exports.MapOrientation.HeadingUp,
        [exports.WeatherMapOrientationSettingMode.TrackUp]: exports.MapOrientation.TrackUp,
        [exports.WeatherMapOrientationSettingMode.DtkUp]: exports.MapOrientation.DtkUp
    };

    /**
     * Controls the orientation of a weather map based on user settings.
     */
    class WeatherMapOrientationSettingsController extends msfsSdk.MapSystemController {
        /**
         * Creates a new instance of WeatherMapOrientationSettingsController.
         * @param context This controller's map context.
         * @param settingManager The setting manager used by this controller.
         */
        constructor(context, settingManager) {
            super(context);
            this.settingManager = settingManager;
            this.orientationModule = this.context.model.getModule(GarminMapKeys.Orientation);
            this.subs = [];
        }
        /** @inheritdoc */
        onAfterMapRender() {
            const weatherMapOrientationSetting = this.settingManager.tryGetSetting('weatherMapOrientation');
            if (weatherMapOrientationSetting) {
                const orientationSetting = this.settingManager.tryGetSetting('mapOrientation');
                if (orientationSetting) {
                    const desiredOrientation = msfsSdk.MappedSubject.create(([weatherOrientation, orientation]) => {
                        var _a, _b;
                        return weatherOrientation === exports.WeatherMapOrientationSettingMode.SyncToNavMap
                            ? (_a = WeatherMapOrientationSettingsController.MODE_MAP[orientation]) !== null && _a !== void 0 ? _a : exports.MapOrientation.NorthUp
                            : (_b = WeatherMapOrientationSettingsController.WEATHER_MODE_MAP[weatherOrientation]) !== null && _b !== void 0 ? _b : exports.MapOrientation.NorthUp;
                    }, weatherMapOrientationSetting, orientationSetting);
                    this.subs.push(desiredOrientation, desiredOrientation.pipe(this.orientationModule.commandedOrientation));
                }
                else {
                    this.subs.push(weatherMapOrientationSetting.pipe(this.orientationModule.commandedOrientation, setting => {
                        var _a;
                        return (_a = WeatherMapOrientationSettingsController.WEATHER_MODE_MAP[setting]) !== null && _a !== void 0 ? _a : exports.MapOrientation.NorthUp;
                    }));
                }
                const northUpAboveActiveSetting = this.settingManager.tryGetSetting('mapAutoNorthUpActive');
                if (northUpAboveActiveSetting) {
                    const weatherNorthUpAboveActive = msfsSdk.MappedSubject.create(([weatherOrientation, northUpAboveActive]) => {
                        return weatherOrientation === exports.WeatherMapOrientationSettingMode.SyncToNavMap
                            ? northUpAboveActive === true
                            : false;
                    }, weatherMapOrientationSetting, northUpAboveActiveSetting);
                    this.subs.push(weatherNorthUpAboveActive, weatherNorthUpAboveActive.pipe(this.orientationModule.northUpAboveActive));
                }
                const northUpAboveRangeIndexSetting = this.settingManager.tryGetSetting('mapAutoNorthUpRangeIndex');
                if (northUpAboveRangeIndexSetting) {
                    const weatherNorthUpAboveRangeIndex = msfsSdk.MappedSubject.create(([weatherOrientation, northUpAboveRangeIndex]) => {
                        return weatherOrientation === exports.WeatherMapOrientationSettingMode.SyncToNavMap && typeof northUpAboveRangeIndex === 'number'
                            ? northUpAboveRangeIndex
                            : Infinity;
                    }, weatherMapOrientationSetting, northUpAboveRangeIndexSetting);
                    this.subs.push(weatherNorthUpAboveRangeIndex, weatherNorthUpAboveRangeIndex.pipe(this.orientationModule.northUpAboveRangeIndex));
                }
            }
        }
        /** @inheritdoc */
        onMapDestroyed() {
            this.destroy();
        }
        /** @inheritdoc */
        destroy() {
            for (const sub of this.subs) {
                sub.destroy();
            }
            super.destroy();
        }
    }
    WeatherMapOrientationSettingsController.MODE_MAP = {
        [exports.MapOrientationSettingMode.NorthUp]: exports.MapOrientation.NorthUp,
        [exports.MapOrientationSettingMode.HeadingUp]: exports.MapOrientation.HeadingUp,
        [exports.MapOrientationSettingMode.TrackUp]: exports.MapOrientation.TrackUp,
        [exports.MapOrientationSettingMode.DtkUp]: exports.MapOrientation.DtkUp
    };
    WeatherMapOrientationSettingsController.WEATHER_MODE_MAP = {
        [exports.WeatherMapOrientationSettingMode.NorthUp]: exports.MapOrientation.NorthUp,
        [exports.WeatherMapOrientationSettingMode.HeadingUp]: exports.MapOrientation.HeadingUp,
        [exports.WeatherMapOrientationSettingMode.TrackUp]: exports.MapOrientation.TrackUp,
        [exports.WeatherMapOrientationSettingMode.DtkUp]: exports.MapOrientation.DtkUp
    };

    /**
     * A map layer which displays a crosshair at the projected position of the map target.
     */
    class MapCrosshairLayer extends msfsSdk.MapLayer {
        constructor() {
            super(...arguments);
            this.crosshairRef = msfsSdk.FSComponent.createRef();
            this.needReposition = true;
        }
        /** @inheritdoc */
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        onVisibilityChanged(isVisible) {
            this.crosshairRef.getOrDefault() && this.updateFromVisibility();
        }
        /**
         * Updates this layer according to its current visibility.
         */
        updateFromVisibility() {
            this.crosshairRef.instance.style.display = this.isVisible() ? '' : 'none';
        }
        /** @inheritdoc */
        onAfterRender() {
            this.props.model.getModule(GarminMapKeys.Crosshair).show.sub(show => { this.setVisible(show); }, true);
            this.updateFromVisibility();
        }
        /** @inheritdoc */
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        onMapProjectionChanged(mapProjection, changeFlags) {
            this.needReposition || (this.needReposition = msfsSdk.BitFlags.isAny(changeFlags, msfsSdk.MapProjectionChangeType.TargetProjected));
        }
        /** @inheritdoc */
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        onUpdated(time, elapsed) {
            if (!this.needReposition || !this.isVisible()) {
                return;
            }
            this.repositionCrosshair();
            this.needReposition = false;
        }
        /**
         * Repositions this layer's crosshair.
         */
        repositionCrosshair() {
            const position = this.props.mapProjection.getTargetProjected();
            this.crosshairRef.instance.style.transform = `translate(-50%, -50%) translate3d(${position[0]}px, ${position[1]}px, 0)`;
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("svg", { ref: this.crosshairRef, class: 'map-crosshair', viewBox: '0 0 100 100', style: 'position: absolute; left: 0; top: 0; transform: translate(-50%, -50%) translate3d(0, 0, 0);' },
                msfsSdk.FSComponent.buildComponent("line", { class: 'map-crosshair-outline', x1: '50', y1: '0', x2: '50', y2: '100' }),
                msfsSdk.FSComponent.buildComponent("line", { class: 'map-crosshair-outline', x1: '0', y1: '50', x2: '100', y2: '50' }),
                msfsSdk.FSComponent.buildComponent("line", { class: 'map-crosshair-stroke', x1: '50', y1: '0', x2: '50', y2: '100' }),
                msfsSdk.FSComponent.buildComponent("line", { class: 'map-crosshair-stroke', x1: '0', y1: '50', x2: '100', y2: '50' })));
        }
    }

    /**
     * A map layer which displays a dead reckoning annunciation at the position of the airplane icon.
     */
    class MapDeadReckoningLayer extends msfsSdk.MapLayer {
        constructor() {
            super(...arguments);
            this.style = msfsSdk.ObjectSubject.create({
                display: '',
                position: 'absolute',
                left: '0px',
                top: '0px',
                transform: 'translate3d(0, 0, 0)',
                'transform-origin': '0% 0%'
            });
            this.ownAirplanePropsModule = this.props.model.getModule(msfsSdk.MapSystemKeys.OwnAirplaneProps);
            this.ownAirplaneIconModule = this.props.model.getModule(msfsSdk.MapSystemKeys.OwnAirplaneIcon);
            this.dataIntegrityModule = this.props.model.getModule(msfsSdk.MapSystemKeys.DataIntegrity);
            this.iconSize = msfsSdk.SubscribableUtils.toSubscribable(this.props.airplaneIconSize, true);
            this.iconAnchor = msfsSdk.SubscribableUtils.toSubscribable(this.props.airplaneIconAnchor, true);
            this.iconCenterOffset = msfsSdk.Vec2Math.create();
            this.visibilityBounds = msfsSdk.VecNMath.create(4);
            this.isInsideVisibilityBounds = msfsSdk.Subject.create(true);
            this.show = msfsSdk.MappedSubject.create(([showIcon, isDr]) => showIcon && isDr, this.ownAirplaneIconModule.show, this.dataIntegrityModule.isDeadReckoning);
            this.visibility = msfsSdk.MappedSubject.create(([show, isInsideBounds]) => show && isInsideBounds, this.show, this.isInsideVisibilityBounds);
            this.needUpdatePosition = false;
        }
        /** @inheritdoc */
        onVisibilityChanged(isVisible) {
            this.style.set('display', isVisible ? '' : 'none');
        }
        /** @inheritdoc */
        onAttached() {
            this.show.sub(show => { this.needUpdatePosition = show; });
            this.visibility.sub(isVisible => { this.setVisible(isVisible); }, true);
            this.positionSub = this.ownAirplanePropsModule.position.sub(() => { this.needUpdatePosition = this.show.get(); });
            this.headingSub = this.ownAirplanePropsModule.hdgTrue.sub(() => { this.needUpdatePosition = this.show.get(); });
            this.iconSizeSub = this.iconSize.sub(() => {
                this.updateOffset();
            });
            this.iconAnchorSub = this.iconAnchor.sub(() => {
                this.updateOffset();
            });
            this.updateOffset();
            this.needUpdatePosition = true;
        }
        /**
         * Updates the annunciation's offset from the projected position of the airplane.
         */
        updateOffset() {
            // Since the annunciation is positioned at the center of the airplane icon, we need to calculate the offset of
            // the airplane icon's center.
            const anchor = this.iconAnchor.get();
            const size = this.iconSize.get();
            this.iconCenterOffset[0] = size * (0.5 - anchor[0]);
            this.iconCenterOffset[1] = size * (0.5 - anchor[1]);
            this.updateVisibilityBounds();
        }
        /**
         * Updates the boundaries within the map's projected window that define a region such that if the airplane's
         * projected position falls outside of it, the annunciation is not visible and therefore does not need to be updated.
         */
        updateVisibilityBounds() {
            const halfSize = this.iconSize.get() / 2;
            // Because the annunciation is supposed to be positioned at the center of the airplane icon, if the airplane icon is
            // out of bounds, so will the annunciation. Therefore, we can use the airplane icon as a proxy for visibility bounds
            // calculations.
            // Find the maximum possible protrusion of the airplane icon from its anchor point, defined as the distance from the
            // anchor point to the farthest point within the bounds of the icon. This farthest point is always one of the
            // four corners of the icon.
            const maxProtrusion = Math.max(Math.hypot(this.iconCenterOffset[0] - halfSize, this.iconCenterOffset[1] - halfSize), // top left corner
            Math.hypot(this.iconCenterOffset[0] + halfSize, this.iconCenterOffset[1] - halfSize), // top right corner
            Math.hypot(this.iconCenterOffset[0] + halfSize, this.iconCenterOffset[1] + halfSize), // bottom right corner
            Math.hypot(this.iconCenterOffset[0] - halfSize, this.iconCenterOffset[1] + halfSize));
            const boundsOffset = maxProtrusion + 50; // Add some additional buffer
            const projectedSize = this.props.mapProjection.getProjectedSize();
            this.visibilityBounds[0] = -boundsOffset;
            this.visibilityBounds[1] = -boundsOffset;
            this.visibilityBounds[2] = projectedSize[0] + boundsOffset;
            this.visibilityBounds[3] = projectedSize[1] + boundsOffset;
            this.needUpdatePosition = this.show.get();
        }
        /** @inheritdoc */
        onMapProjectionChanged(mapProjection, changeFlags) {
            if (msfsSdk.BitFlags.isAll(changeFlags, msfsSdk.MapProjectionChangeType.ProjectedSize)) {
                this.updateVisibilityBounds();
            }
            this.needUpdatePosition = this.show.get();
        }
        /** @inheritdoc */
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        onUpdated(time, elapsed) {
            if (this.needUpdatePosition) {
                this.updatePosition();
                this.needUpdatePosition = false;
            }
        }
        /**
         * Updates the annunciation's projected position.
         */
        updatePosition() {
            const projected = this.props.mapProjection.project(this.ownAirplanePropsModule.position.get(), MapDeadReckoningLayer.vec2Cache[0]);
            this.isInsideVisibilityBounds.set(this.props.mapProjection.isInProjectedBounds(projected, this.visibilityBounds));
            // Only update position if actually visible.
            if (this.visibility.get()) {
                const rotation = msfsSdk.MathUtils.round(this.ownAirplanePropsModule.hdgTrue.get() + this.props.mapProjection.getRotation() * Avionics.Utils.RAD2DEG, 0.1);
                this.style.set('transform', `translate3d(${projected[0].toFixed(1)}px, ${projected[1].toFixed(1)}px, 0px) rotate(${rotation}deg) translate(${this.iconCenterOffset[0]}px, ${this.iconCenterOffset[1]}px) rotate(${-rotation}deg) translate(-50%, -50%)`);
            }
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: 'map-deadreckoning', style: this.style }, "DR"));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c, _d;
            this.show.destroy();
            (_a = this.positionSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.headingSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.iconSizeSub) === null || _c === void 0 ? void 0 : _c.destroy();
            (_d = this.iconAnchorSub) === null || _d === void 0 ? void 0 : _d.destroy();
            super.destroy();
        }
    }
    MapDeadReckoningLayer.vec2Cache = [msfsSdk.Vec2Math.create()];

    /**
     * Render roles for MapWaypointRenderer.
     */
    exports.MapWaypointRenderRole = void 0;
    (function (MapWaypointRenderRole) {
        /** A highlighted waypoint. */
        MapWaypointRenderRole[MapWaypointRenderRole["Highlight"] = 1] = "Highlight";
        /** A waypoint which is the active waypoint in a flight plan. */
        MapWaypointRenderRole[MapWaypointRenderRole["FlightPlanActive"] = 2] = "FlightPlanActive";
        /** A waypoint in a flight plan which is not the active waypoint. */
        MapWaypointRenderRole[MapWaypointRenderRole["FlightPlanInactive"] = 4] = "FlightPlanInactive";
        /** A normally displayed waypoint. */
        MapWaypointRenderRole[MapWaypointRenderRole["Normal"] = 8] = "Normal";
        /** A waypoint in an airway. */
        MapWaypointRenderRole[MapWaypointRenderRole["Airway"] = 16] = "Airway";
        /** A VNAV waypoint. */
        MapWaypointRenderRole[MapWaypointRenderRole["VNav"] = 32] = "VNav";
        /** A waypoint in a procedure preview plan. */
        MapWaypointRenderRole[MapWaypointRenderRole["ProcedurePreview"] = 64] = "ProcedurePreview";
        /** A waypoint in a procedure transition preview plan. */
        MapWaypointRenderRole[MapWaypointRenderRole["ProcedureTransitionPreview"] = 128] = "ProcedureTransitionPreview";
    })(exports.MapWaypointRenderRole || (exports.MapWaypointRenderRole = {}));
    /**
     * A renderer which draws waypoints to a Garmin-style map.
     */
    class MapWaypointRenderer extends msfsSdk.MapWaypointRenderer {
        /**
         * Constructor.
         * @param textManager The text manager to use for waypoint labels.
         */
        constructor(textManager) {
            super(textManager);
            this.addRenderRole(exports.MapWaypointRenderRole.Highlight);
            this.addRenderRole(exports.MapWaypointRenderRole.FlightPlanActive);
            this.addRenderRole(exports.MapWaypointRenderRole.FlightPlanInactive);
            this.addRenderRole(exports.MapWaypointRenderRole.ProcedurePreview);
            this.addRenderRole(exports.MapWaypointRenderRole.ProcedureTransitionPreview);
            this.addRenderRole(exports.MapWaypointRenderRole.Normal);
            this.addRenderRole(exports.MapWaypointRenderRole.Airway);
            this.addRenderRole(exports.MapWaypointRenderRole.VNav);
        }
    }

    /**
     * A map layer which displays a flight plan.
     */
    class MapFlightPlanLayer extends msfsSdk.MapLayer {
        constructor() {
            super(...arguments);
            this.flightPathLayerRef = msfsSdk.FSComponent.createRef();
            this.resampler = new msfsSdk.GeoCircleResampler(Math.PI / 12, 0.25, 8);
            this.facLoader = new msfsSdk.FacilityLoader(msfsSdk.FacilityRepository.getRepository(this.props.bus));
            this.facWaypointCache = GarminFacilityWaypointCache.getCache(this.props.bus);
            this.waypointRecordManager = new MapFlightPlanWaypointRecordManager(this.facLoader, this.facWaypointCache, this.props.waypointRenderer, exports.MapWaypointRenderRole.FlightPlanInactive, exports.MapWaypointRenderRole.FlightPlanActive);
            this.clipBoundsSub = msfsSdk.VecNSubject.createFromVector(new Float64Array(4));
            this.clippedPathStream = new msfsSdk.ClippedPathStream(msfsSdk.NullPathStream.INSTANCE, this.clipBoundsSub);
            this.pathStreamStack = new msfsSdk.GeoProjectionPathStreamStack(msfsSdk.NullPathStream.INSTANCE, this.props.mapProjection.getGeoProjection(), this.resampler);
            this.vnavWaypointUid = MapFlightPlanLayer.vnavWaypointUidSource++;
            this.isObsActive = false;
            this.obsCourse = 0;
            this.needDrawRoute = false;
            this.needRefreshWaypoints = false;
            this.needRepickWaypoints = false;
            this.needUpdateVNavWaypoint = false;
            this.subs = [];
        }
        /** @inheritdoc */
        onAttached() {
            super.onAttached();
            this.flightPathLayerRef.instance.onAttached();
            this.pathStreamStack.pushPostProjected(this.clippedPathStream);
            this.pathStreamStack.setConsumer(this.flightPathLayerRef.instance.display.context);
            this.initFlightPlanHandlers();
            this.updateVNavWaypoint();
        }
        /**
         * Initializes handlers to respond to flight plan events.
         */
        initFlightPlanHandlers() {
            this.subs.push(this.props.drawEntirePlan.sub(() => { this.scheduleUpdates(true, true, true); }), this.props.dataProvider.plan.sub(() => { this.scheduleUpdates(true, true, true); }, true), this.props.dataProvider.planModified.on(() => { this.scheduleUpdates(false, true, true); }), this.props.dataProvider.planCalculated.on(() => {
                this.scheduleUpdates(true, true, false);
                this.needUpdateVNavWaypoint = true;
            }), this.props.dataProvider.activeLateralLegIndex.sub(() => { this.scheduleUpdates(true, true, true); }), this.props.dataProvider.lnavData.sub(() => { this.scheduleUpdates(true, false, false); }), this.props.dataProvider.vnavState.sub(() => { this.needUpdateVNavWaypoint = true; }, true), this.props.dataProvider.vnavTodLegIndex.sub(() => { this.needUpdateVNavWaypoint = true; }), this.props.dataProvider.vnavBodLegIndex.sub(() => { this.needUpdateVNavWaypoint = true; }), this.props.dataProvider.vnavTodLegDistance.sub(() => { var _a; this.needUpdateVNavWaypoint || (this.needUpdateVNavWaypoint = ((_a = this.renderedVNavWaypoint) === null || _a === void 0 ? void 0 : _a.ident) === 'TOD'); }), this.props.dataProvider.vnavDistanceToTod.sub(() => { this.needUpdateVNavWaypoint = true; }), this.props.dataProvider.vnavTocLegIndex.sub(() => { this.needUpdateVNavWaypoint = true; }), this.props.dataProvider.vnavBocLegIndex.sub(() => { this.needUpdateVNavWaypoint = true; }), this.props.dataProvider.vnavTocLegDistance.sub(() => { var _a; this.needUpdateVNavWaypoint || (this.needUpdateVNavWaypoint = ((_a = this.renderedVNavWaypoint) === null || _a === void 0 ? void 0 : _a.ident) === 'TOC'); }), this.props.dataProvider.vnavDistanceToToc.sub(() => { this.needUpdateVNavWaypoint = true; }), this.props.dataProvider.obsCourse.sub((course) => {
                const isActive = course !== undefined;
                const needFullUpdate = isActive !== this.isObsActive;
                this.isObsActive = isActive;
                this.obsCourse = course !== null && course !== void 0 ? course : this.obsCourse;
                this.scheduleUpdates(needFullUpdate || this.isObsActive, needFullUpdate, needFullUpdate);
            }));
        }
        /** @inheritdoc */
        onMapProjectionChanged(mapProjection, changeFlags) {
            this.flightPathLayerRef.instance.onMapProjectionChanged(mapProjection, changeFlags);
            this.updateClipBounds();
        }
        /**
         * Updates this layer's canvas clipping bounds.
         */
        updateClipBounds() {
            const size = this.flightPathLayerRef.instance.getSize();
            this.clipBoundsSub.set(-MapFlightPlanLayer.CLIP_BOUNDS_BUFFER, -MapFlightPlanLayer.CLIP_BOUNDS_BUFFER, size + MapFlightPlanLayer.CLIP_BOUNDS_BUFFER, size + MapFlightPlanLayer.CLIP_BOUNDS_BUFFER);
        }
        /** @inheritdoc */
        onUpdated(time, elapsed) {
            this.flightPathLayerRef.instance.onUpdated(time, elapsed);
            this.updateFromFlightPathLayerInvalidation();
            this.updateRedrawRoute();
            this.updateRefreshWaypoints();
            this.updateVNavWaypoint();
        }
        /**
         * Checks if the flight path layer's display canvas has been invalidated, and if so, clears it and schedules a redraw.
         */
        updateFromFlightPathLayerInvalidation() {
            const display = this.flightPathLayerRef.instance.display;
            this.needDrawRoute || (this.needDrawRoute = display.isInvalid);
            if (display.isInvalid) {
                display.clear();
                display.syncWithMapProjection(this.props.mapProjection);
            }
        }
        /**
         * Redraws the flight path if a redraw is scheduled.
         */
        updateRedrawRoute() {
            if (this.needDrawRoute) {
                this.drawRoute();
                this.needDrawRoute = false;
            }
        }
        /**
         * Draws the flight path route.
         */
        drawRoute() {
            const display = this.flightPathLayerRef.instance.display;
            const context = display.context;
            display.clear();
            const plan = this.props.dataProvider.plan.get();
            if (plan) {
                this.pathStreamStack.setProjection(display.geoProjection);
                this.props.pathRenderer.render(plan, context, this.pathStreamStack, this.props.drawEntirePlan.get(), this.props.dataProvider.activeLateralLegIndex.get(), this.props.dataProvider.lnavData.get(), this.isObsActive ? this.obsCourse : undefined);
            }
        }
        /**
         * Refreshes this layer's flight plan leg waypoint records if a refresh is scheduled.
         */
        updateRefreshWaypoints() {
            if (this.needRefreshWaypoints && !this.waypointRecordManager.isBusy()) {
                const plan = this.props.dataProvider.plan.get();
                const activeLegIndex = this.props.dataProvider.activeLateralLegIndex.get();
                const startIndex = plan ? this.getPickWaypointsStartIndex(plan, activeLegIndex, this.props.drawEntirePlan.get(), this.isObsActive) : undefined;
                this.waypointRecordManager.refreshWaypoints(plan, activeLegIndex, this.needRepickWaypoints, startIndex);
                this.needRefreshWaypoints = false;
                this.needRepickWaypoints = false;
            }
        }
        /**
         * Gets the global index of the first leg in a flight plan for which to display waypoints.
         * @param plan A flight plan.
         * @param activeLegIndex The global index of the active flight plan leg, or -1 if there is no active leg.
         * @param drawEntirePlan Whether to display the entire flight plan.
         * @param isObsActive Whether OBS is active.
         * @returns The global index of the first leg in a flight plan for which to display waypoints.
         */
        getPickWaypointsStartIndex(plan, activeLegIndex, drawEntirePlan, isObsActive) {
            if (drawEntirePlan) {
                return 0;
            }
            if (activeLegIndex < 0) {
                return plan.length;
            }
            if (isObsActive) {
                return activeLegIndex;
            }
            return Math.max(0, this.getActiveFromLegIndex(plan, activeLegIndex) - 1);
        }
        /**
         * Gets the global leg index of the leg from which the active leg of a flight plan originates.
         * @param plan A flight plan.
         * @param activeLegIndex The global leg index of the active leg.
         * @returns The global leg index of the leg from which the active leg originates, or -1 if one could not be found.
         */
        getActiveFromLegIndex(plan, activeLegIndex) {
            const activeLeg = plan.tryGetLeg(activeLegIndex);
            if (!activeLeg) {
                return -1;
            }
            const segmentIndex = plan.getSegmentIndex(activeLegIndex);
            const segmentLegIndex = activeLegIndex - plan.getSegment(segmentIndex).offset;
            return FmsUtils.getNominalFromLegIndex(plan, segmentIndex, segmentLegIndex);
        }
        /**
         * Schedules flight plan drawing updates.
         * @param scheduleRedrawRoute Whether to schedule a redraw of the flight path.
         * @param scheduleRefreshWaypoints Whether to schedule a refresh of waypoints records.
         * @param scheduleRepickWaypoints Whether to schedule a repick of waypoints records.
         */
        scheduleUpdates(scheduleRedrawRoute, scheduleRefreshWaypoints, scheduleRepickWaypoints) {
            this.needDrawRoute || (this.needDrawRoute = scheduleRedrawRoute);
            this.needRefreshWaypoints || (this.needRefreshWaypoints = scheduleRefreshWaypoints);
            this.needRepickWaypoints || (this.needRepickWaypoints = scheduleRepickWaypoints);
        }
        /**
         * Responds to when the VNAV TOD/BOD/TOC/BOC waypoints change.
         */
        updateVNavWaypoint() {
            var _a, _b, _c, _d;
            if (!this.needUpdateVNavWaypoint) {
                return;
            }
            this.needUpdateVNavWaypoint = false;
            const plan = this.props.dataProvider.plan.get();
            // TODO: Support Off-route DTOs
            if (!plan || plan.segmentCount < 1 || plan.getSegment(0).segmentType === msfsSdk.FlightPlanSegmentType.RandomDirectTo) {
                this.renderedVNavWaypoint && this.props.waypointRenderer.deregister(this.renderedVNavWaypoint, exports.MapWaypointRenderRole.VNav, 'flightplan-layer');
                this.renderedVNavWaypoint = undefined;
                return;
            }
            const vnavState = this.props.dataProvider.vnavState.get();
            if (vnavState === msfsSdk.VNavState.Disabled) {
                this.renderedVNavWaypoint && this.props.waypointRenderer.deregister(this.renderedVNavWaypoint, exports.MapWaypointRenderRole.VNav, 'flightplan-layer');
                this.renderedVNavWaypoint = undefined;
                return;
            }
            const todLegIndex = this.props.dataProvider.vnavTodLegIndex.get();
            const bodLegIndex = this.props.dataProvider.vnavBodLegIndex.get();
            const tocLegIndex = this.props.dataProvider.vnavTocLegIndex.get();
            const bocLegIndex = this.props.dataProvider.vnavBocLegIndex.get();
            const todDistance = this.props.dataProvider.vnavDistanceToTod.get();
            const tocDistance = this.props.dataProvider.vnavDistanceToToc.get();
            let waypointToRender;
            if (todLegIndex >= 0 && todDistance.number > 0) {
                const todLegEndDistance = this.props.dataProvider.vnavTodLegDistance.get();
                if (isFinite(todLegEndDistance.number) && plan.length > 0) {
                    const leg = plan.tryGetLeg(todLegIndex);
                    if (leg) {
                        if (this.todWaypoint === undefined) {
                            this.todWaypoint = new msfsSdk.VNavWaypoint(leg, todLegEndDistance.asUnit(msfsSdk.UnitType.METER), `flightplan-layer-${this.vnavWaypointUid}-vnav-tod`, 'TOD');
                        }
                        else {
                            this.todWaypoint.setLocation(leg, todLegEndDistance.asUnit(msfsSdk.UnitType.METER));
                        }
                        waypointToRender = this.todWaypoint;
                    }
                }
                else if (!isFinite(todLegEndDistance.number)) {
                    console.warn(`Invalid TOD leg end distance: ${todLegEndDistance}`);
                }
            }
            else if (bodLegIndex >= 0) {
                const leg = plan.tryGetLeg(bodLegIndex);
                if (leg) {
                    if (this.bodWaypoint === undefined) {
                        this.bodWaypoint = new msfsSdk.VNavWaypoint(leg, 0, `flightplan-layer-${this.vnavWaypointUid}-vnav-bod`, 'BOD');
                    }
                    else {
                        this.bodWaypoint.setLocation(leg, 0);
                    }
                    waypointToRender = this.bodWaypoint;
                }
            }
            else if (tocLegIndex >= 0 && tocDistance.number > 0) {
                const tocLegEndDistance = this.props.dataProvider.vnavTocLegDistance.get();
                if (isFinite(tocLegEndDistance.number) && plan.length > 0) {
                    const leg = plan.tryGetLeg(tocLegIndex);
                    if (leg) {
                        if (this.tocWaypoint === undefined) {
                            this.tocWaypoint = new msfsSdk.VNavWaypoint(leg, tocLegEndDistance.asUnit(msfsSdk.UnitType.METER), `flightplan-layer-${this.vnavWaypointUid}-vnav-toc`, 'TOC');
                        }
                        else {
                            this.tocWaypoint.setLocation(leg, tocLegEndDistance.asUnit(msfsSdk.UnitType.METER));
                        }
                        waypointToRender = this.tocWaypoint;
                    }
                }
                else if (!isFinite(tocLegEndDistance.number)) {
                    console.warn(`Invalid TOC leg end distance: ${tocLegEndDistance}`);
                }
            }
            else if (bocLegIndex >= 0) {
                const leg = plan.tryGetLeg(bocLegIndex);
                if (leg) {
                    if (this.bocWaypoint === undefined) {
                        this.bocWaypoint = new msfsSdk.VNavWaypoint(leg, (_b = (_a = leg.calculated) === null || _a === void 0 ? void 0 : _a.distanceWithTransitions) !== null && _b !== void 0 ? _b : 0, `flightplan-layer-${this.vnavWaypointUid}-vnav-boc`, 'BOC');
                    }
                    else {
                        this.bocWaypoint.setLocation(leg, (_d = (_c = leg.calculated) === null || _c === void 0 ? void 0 : _c.distanceWithTransitions) !== null && _d !== void 0 ? _d : 0);
                    }
                    waypointToRender = this.bocWaypoint;
                }
            }
            if (this.renderedVNavWaypoint !== undefined && waypointToRender !== this.renderedVNavWaypoint) {
                this.props.waypointRenderer.deregister(this.renderedVNavWaypoint, exports.MapWaypointRenderRole.VNav, 'flightplan-layer');
            }
            this.renderedVNavWaypoint = waypointToRender;
            if (waypointToRender !== undefined) {
                this.props.waypointRenderer.register(waypointToRender, exports.MapWaypointRenderRole.VNav, 'flightplan-layer');
            }
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { style: 'position: absolute; left: 0; top: 0; width: 100%; height: 100%;' },
                msfsSdk.FSComponent.buildComponent(msfsSdk.MapCachedCanvasLayer, { ref: this.flightPathLayerRef, model: this.props.model, mapProjection: this.props.mapProjection, overdrawFactor: Math.SQRT2 })));
        }
        /** @inheritdoc */
        destroy() {
            var _a;
            (_a = this.flightPathLayerRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
            this.subs.forEach(sub => { sub.destroy(); });
            super.destroy();
        }
    }
    MapFlightPlanLayer.CLIP_BOUNDS_BUFFER = 10; // number of pixels from edge of canvas to extend the clipping bounds, in pixels
    MapFlightPlanLayer.vnavWaypointUidSource = 0;

    /**
     * The map layer showing a rotating compass arrow pointing to true north.
     */
    class MapMiniCompassLayer extends msfsSdk.MapLayer {
        constructor() {
            super(...arguments);
            this.rootRef = msfsSdk.FSComponent.createRef();
            this.imgRef = msfsSdk.FSComponent.createRef();
            this.needUpdate = false;
        }
        /** @inheritdoc */
        onVisibilityChanged(isVisible) {
            const root = this.rootRef.getOrDefault();
            if (root !== null) {
                root.style.display = isVisible ? '' : 'none';
            }
        }
        /** @inheritdoc */
        onAttached() {
            this.needUpdate = true;
            this.onVisibilityChanged(this.isVisible());
        }
        /** @inheritdoc */
        onMapProjectionChanged(mapProjection, changeFlags) {
            this.needUpdate = msfsSdk.BitFlags.isAll(changeFlags, msfsSdk.MapProjectionChangeType.Rotation);
        }
        /** @inheritdoc */
        onUpdated() {
            if (!this.needUpdate || !this.isVisible()) {
                return;
            }
            this.updateRotation();
            this.needUpdate = false;
        }
        /**
         * Updates the rotation of the arrow.
         */
        updateRotation() {
            const rotation = this.props.mapProjection.getRotation();
            this.imgRef.instance.style.transform = `rotate3d(0,0,1,${rotation}rad)`;
        }
        /** @inheritdoc */
        render() {
            var _a;
            let cssClass;
            if (typeof this.props.class === 'object') {
                this.cssClassSub = msfsSdk.FSComponent.bindCssClassSet(cssClass = msfsSdk.SetSubject.create(['map-minicompass']), this.props.class, ['map-minicompass']);
            }
            else {
                cssClass = `map-minicompass ${(_a = this.props.class) !== null && _a !== void 0 ? _a : ''}`;
            }
            return (msfsSdk.FSComponent.buildComponent("div", { ref: this.rootRef, class: cssClass, style: 'position: relative; width: var(--minicompass-size, 4em); height: var(--minicompass-size, 4em);' },
                msfsSdk.FSComponent.buildComponent("img", { ref: this.imgRef, src: this.props.imgSrc, style: 'width: 100%; height: 100%;' }),
                msfsSdk.FSComponent.buildComponent("div", { style: 'position: absolute; top: 50%; width: 100%; transform: translateY(-50%); text-align: center; color: black;' }, "N")));
        }
        /** @inheritdoc */
        destroy() {
            var _a;
            super.destroy();
            (_a = this.cssClassSub) === null || _a === void 0 ? void 0 : _a.destroy();
        }
    }

    /**
     * Map pointer information box size.
     */
    exports.MapPointerInfoLayerSize = void 0;
    (function (MapPointerInfoLayerSize) {
        MapPointerInfoLayerSize[MapPointerInfoLayerSize["Full"] = 0] = "Full";
        MapPointerInfoLayerSize[MapPointerInfoLayerSize["Medium"] = 1] = "Medium";
        MapPointerInfoLayerSize[MapPointerInfoLayerSize["Small"] = 2] = "Small";
    })(exports.MapPointerInfoLayerSize || (exports.MapPointerInfoLayerSize = {}));
    /**
     * A map layer which displays a pointer information box.
     */
    class MapPointerInfoLayer extends msfsSdk.MapLayer {
        constructor() {
            var _a, _b, _c, _d, _e, _f;
            super(...arguments);
            this.rootRef = msfsSdk.FSComponent.createRef();
            this.ownAirplanePropsModule = this.props.model.getModule(msfsSdk.MapSystemKeys.OwnAirplaneProps);
            this.pointerModule = this.props.model.getModule(GarminMapKeys.Pointer);
            this.unitsModule = this.props.model.getModule(GarminMapKeys.Units);
            this.distanceLargeUnits = (_b = (_a = this.unitsModule) === null || _a === void 0 ? void 0 : _a.distanceLarge) !== null && _b !== void 0 ? _b : msfsSdk.Subject.create(msfsSdk.UnitType.NMILE);
            this.distanceSmallUnits = (_d = (_c = this.unitsModule) === null || _c === void 0 ? void 0 : _c.distanceSmall) !== null && _d !== void 0 ? _d : msfsSdk.Subject.create(msfsSdk.UnitType.FOOT);
            this.navAngleUnits = (_f = (_e = this.unitsModule) === null || _e === void 0 ? void 0 : _e.navAngle) !== null && _f !== void 0 ? _f : msfsSdk.Subject.create(msfsSdk.BasicNavAngleUnit.create(true));
            this.distance = msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.NMILE.createNumber(NaN));
            this.distanceUnit = msfsSdk.Subject.create(msfsSdk.UnitType.NMILE);
            this.bearing = msfsSdk.BasicNavAngleSubject.create(msfsSdk.BasicNavAngleUnit.create(false).createNumber(NaN));
            this.latLon = msfsSdk.GeoPointSubject.create(new msfsSdk.GeoPoint(0, 0));
            this.scheduleUpdateHandler = () => { this.needUpdate = true; };
            this.needUpdate = false;
        }
        /** @inheritdoc */
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        onVisibilityChanged(isVisible) {
            this.rootRef.getOrDefault() && this.updateFromVisibility();
        }
        /**
         * Updates this layer according to its current visibility.
         */
        updateFromVisibility() {
            if (this.isVisible()) {
                this.rootRef.instance.style.display = '';
                this.ownAirplanePropsModule.position.sub(this.scheduleUpdateHandler);
                this.pointerModule.position.sub(this.scheduleUpdateHandler, true);
            }
            else {
                this.rootRef.instance.style.display = 'none';
                this.ownAirplanePropsModule.position.unsub(this.scheduleUpdateHandler);
                this.pointerModule.position.unsub(this.scheduleUpdateHandler);
            }
        }
        /** @inheritdoc */
        onAfterRender() {
            this.updateFromVisibility();
            this.pointerModule.isActive.sub(isActive => this.setVisible(isActive), true);
            this.distanceLargeUnits.sub(this.updateDistanceUnit.bind(this));
        }
        /** @inheritdoc */
        onMapProjectionChanged(mapProjection, changeFlags) {
            this.needUpdate || (this.needUpdate = this.isVisible() && msfsSdk.BitFlags.isAny(changeFlags, msfsSdk.MapProjectionChangeType.Center | msfsSdk.MapProjectionChangeType.Rotation | msfsSdk.MapProjectionChangeType.ProjectedResolution));
        }
        /** @inheritdoc */
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        onUpdated(time, elapsed) {
            if (!this.needUpdate) {
                return;
            }
            this.updateInfo();
            this.needUpdate = false;
        }
        /**
         * Updates this layer's displayed information.
         */
        updateInfo() {
            const latLon = this.props.mapProjection.invert(this.pointerModule.position.get(), MapPointerInfoLayer.geoPointCache[0]);
            this.latLon.set(latLon);
            const airplanePos = this.ownAirplanePropsModule.position.get();
            this.distance.set(airplanePos.distance(latLon), msfsSdk.UnitType.GA_RADIAN);
            this.updateDistanceUnit();
            this.bearing.set(airplanePos.bearingTo(latLon), this.ownAirplanePropsModule.magVar.get());
        }
        /**
         * Updates the displayed distance unit type.
         */
        updateDistanceUnit() {
            const distance = this.distance.get();
            if (!distance.isNaN() && distance.asUnit(this.distanceLargeUnits.get()) < 0.1) {
                this.distanceUnit.set(this.distanceSmallUnits.get());
            }
            else {
                this.distanceUnit.set(this.distanceLargeUnits.get());
            }
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { ref: this.rootRef, class: 'map-pointerinfolayer-box' },
                msfsSdk.FSComponent.buildComponent("div", { class: 'map-pointerinfolayer-box-column map-pointerinfolayer-box-dis' },
                    msfsSdk.FSComponent.buildComponent("span", { class: 'map-pointerinfolayer-box-title', style: this.props.size === exports.MapPointerInfoLayerSize.Small ? 'display: none;' : '' }, "DIS"),
                    msfsSdk.FSComponent.buildComponent(NumberUnitDisplay, { value: this.distance, displayUnit: this.distanceUnit, formatter: msfsSdk.NumberFormatter.create({ precision: 0.1, maxDigits: 3, forceDecimalZeroes: false, nanString: '__._' }), class: 'map-pointerinfolayer-box-title-value' })),
                msfsSdk.FSComponent.buildComponent("div", { class: 'map-pointerinfolayer-box-column map-pointerinfolayer-box-brg', style: this.props.size === exports.MapPointerInfoLayerSize.Small ? 'display: none;' : '' },
                    msfsSdk.FSComponent.buildComponent("span", { class: 'map-pointerinfolayer-box-title' }, "BRG"),
                    msfsSdk.FSComponent.buildComponent(BearingDisplay, { value: this.bearing, displayUnit: this.navAngleUnits, formatter: msfsSdk.NumberFormatter.create({ precision: 1, pad: 3, nanString: '___' }), class: 'map-pointerinfolayer-box-title-value' })),
                this.props.size === exports.MapPointerInfoLayerSize.Full
                    ? msfsSdk.FSComponent.buildComponent(msfsSdk.LatLonDisplay, { location: this.latLon, class: 'map-pointerinfolayer-box-column map-pointerinfolayer-box-title-value' })
                    : null));
        }
    }
    MapPointerInfoLayer.geoPointCache = [new msfsSdk.GeoPoint(0, 0)];

    /**
     * A map layer which displays a pointer.
     */
    class MapPointerLayer extends msfsSdk.MapLayer {
        constructor() {
            super(...arguments);
            this.pointerRef = msfsSdk.FSComponent.createRef();
            this.pointerModule = this.props.model.getModule(GarminMapKeys.Pointer);
            this.needRepositionPointer = false;
        }
        /** @inheritdoc */
        onVisibilityChanged() {
            this.pointerRef.getOrDefault() && this.updateFromVisibility();
        }
        /**
         * Updates this layer according to its current visibility.
         */
        updateFromVisibility() {
            var _a, _b;
            const isVisible = this.isVisible();
            this.pointerRef.instance.style.display = isVisible ? '' : 'none';
            if (isVisible) {
                (_a = this.positionSub) === null || _a === void 0 ? void 0 : _a.resume(true);
            }
            else {
                (_b = this.positionSub) === null || _b === void 0 ? void 0 : _b.pause();
            }
        }
        /** @inheritdoc */
        onAttached() {
            this.positionSub = this.pointerModule.position.sub(() => { this.needRepositionPointer = true; }, false, true);
            this.updateFromVisibility();
            this.isActiveSub = this.pointerModule.isActive.sub(isActive => this.setVisible(isActive), true);
        }
        /** @inheritdoc */
        onUpdated() {
            if (!this.needRepositionPointer) {
                return;
            }
            this.repositionPointer();
            this.needRepositionPointer = false;
        }
        /**
         * Repositions this layer's pointer.
         */
        repositionPointer() {
            const position = this.pointerModule.position.get();
            this.pointerRef.instance.style.transform = `translate3d(${position[0]}px, ${position[1]}px, 0)`;
        }
        /** @inheritdoc */
        render() {
            var _a, _b;
            return ((_b = (_a = this.props.children) === null || _a === void 0 ? void 0 : _a.some(child => child !== undefined)) !== null && _b !== void 0 ? _b : false) ? this.renderCustom() : this.renderDefault();
        }
        /**
         * Renders the default pointer icon.
         * @returns The default pointer icon, as a VNode.
         */
        renderDefault() {
            return (msfsSdk.FSComponent.buildComponent("svg", { ref: this.pointerRef, class: 'map-pointer', viewBox: '0 0 100 100', style: 'position: absolute; left: 0; top: 0; transform: translate3d(0, 0, 0);' },
                msfsSdk.FSComponent.buildComponent("polygon", { points: '78.93 95.46 49.48 66.01 41.18 84.57 4.54 4.54 84.57 41.18 66.01 49.48 95.46 78.93 78.93 95.46' })));
        }
        /**
         * Renders a custom pointer icon.
         * @returns The custom pointer icon, as a VNode.
         */
        renderCustom() {
            return (msfsSdk.FSComponent.buildComponent("div", { ref: this.pointerRef, style: 'position: absolute; left: 0; top: 0; transform: translate3d(0, 0, 0);', class: 'map-pointer' }, this.props.children));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b;
            super.destroy();
            (_a = this.positionSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.isActiveSub) === null || _b === void 0 ? void 0 : _b.destroy();
        }
    }

    /**
     * A map layer which displays a procedure preview.
     */
    class MapProcedurePreviewLayer extends msfsSdk.MapLayer {
        constructor() {
            super(...arguments);
            this.flightPathLayerRef = msfsSdk.FSComponent.createRef();
            this.waypointLayerRef = msfsSdk.FSComponent.createRef();
            this.procPreviewModule = this.props.model.getModule(GarminMapKeys.ProcedurePreview);
            this.resampler = new msfsSdk.GeoCircleResampler(Math.PI / 12, 0.25, 8);
            this.facLoader = new msfsSdk.FacilityLoader(msfsSdk.FacilityRepository.getRepository(this.props.bus));
            this.waypointCache = GarminFacilityWaypointCache.getCache(this.props.bus);
            this.clipBoundsSub = msfsSdk.VecNSubject.createFromVector(new Float64Array(4));
            this.clippedPathStream = new msfsSdk.ClippedPathStream(msfsSdk.NullPathStream.INSTANCE, this.clipBoundsSub);
            this.pathStreamStack = new msfsSdk.GeoProjectionPathStreamStack(msfsSdk.NullPathStream.INSTANCE, this.props.mapProjection.getGeoProjection(), this.resampler);
            this.procedureWaypointRecordManager = new MapFlightPlanWaypointRecordManager(this.facLoader, this.waypointCache, this.props.waypointRenderer, exports.MapWaypointRenderRole.ProcedurePreview, exports.MapWaypointRenderRole.ProcedurePreview);
            this.transitionWaypointRecordManager = new ProcMapTransitionWaypointRecordManager(this.facLoader, this.waypointCache, this.props.waypointRenderer, exports.MapWaypointRenderRole.ProcedureTransitionPreview);
            this.needDrawRoute = false;
            this.needRefreshProcedureWaypoints = false;
            this.needRepickProcedureWaypoints = false;
            this.needRefreshTransitionWaypoints = false;
            this.needRepickTransitionWaypoints = false;
        }
        /** @inheritdoc */
        onAttached() {
            super.onAttached();
            this.flightPathLayerRef.instance.onAttached();
            this.waypointLayerRef.instance.onAttached();
            this.pathStreamStack.pushPostProjected(this.clippedPathStream);
            this.pathStreamStack.setConsumer(this.flightPathLayerRef.instance.display.context);
            this.initWaypointRenderer();
            this.initFlightPlanHandlers();
        }
        /**
         * Initializes the waypoint renderer.
         */
        initWaypointRenderer() {
            this.props.waypointRenderer.setCanvasContext(exports.MapWaypointRenderRole.ProcedureTransitionPreview, this.waypointLayerRef.instance.display.context);
            this.props.waypointRenderer.setCanvasContext(exports.MapWaypointRenderRole.ProcedurePreview, this.waypointLayerRef.instance.display.context);
        }
        /**
         * Initializes handlers to respond to flight plan events.
         */
        initFlightPlanHandlers() {
            this.procPreviewModule.procedurePlan.sub(() => { this.scheduleUpdates(true, true, true, false, false); }, true);
            this.procPreviewModule.transitionPlan.sub(() => { this.scheduleUpdates(true, false, false, true, true); }, true);
        }
        /** @inheritdoc */
        onMapProjectionChanged(mapProjection, changeFlags) {
            this.flightPathLayerRef.instance.onMapProjectionChanged(mapProjection, changeFlags);
            this.waypointLayerRef.instance.onMapProjectionChanged(mapProjection, changeFlags);
            this.updateClipBounds();
        }
        /**
         * Updates this layer's canvas clipping bounds.
         */
        updateClipBounds() {
            const size = this.flightPathLayerRef.instance.getSize();
            this.clipBoundsSub.set(-MapProcedurePreviewLayer.CLIP_BOUNDS_BUFFER, -MapProcedurePreviewLayer.CLIP_BOUNDS_BUFFER, size + MapProcedurePreviewLayer.CLIP_BOUNDS_BUFFER, size + MapProcedurePreviewLayer.CLIP_BOUNDS_BUFFER);
        }
        /** @inheritdoc */
        onUpdated(time, elapsed) {
            this.flightPathLayerRef.instance.onUpdated(time, elapsed);
            this.updateFromFlightPathLayerInvalidation();
            this.updateRedrawRoute();
            this.updateRefreshWaypoints();
        }
        /**
         * Checks if the flight path layer's display canvas has been invalidated, and if so, clears it and schedules a redraw.
         */
        updateFromFlightPathLayerInvalidation() {
            const display = this.flightPathLayerRef.instance.display;
            this.needDrawRoute || (this.needDrawRoute = display.isInvalid);
            if (display.isInvalid) {
                display.clear();
                display.syncWithMapProjection(this.props.mapProjection);
            }
        }
        /**
         * Redraws the flight path if a redraw is scheduled.
         */
        updateRedrawRoute() {
            if (this.needDrawRoute) {
                this.drawRoute();
                this.needDrawRoute = false;
            }
        }
        /**
         * Draws the flight path route.
         */
        drawRoute() {
            const display = this.flightPathLayerRef.instance.display;
            const context = display.context;
            display.clear();
            const procedurePlan = this.procPreviewModule.procedurePlan.get();
            const transitionPlan = this.procPreviewModule.transitionPlan.get();
            this.pathStreamStack.setProjection(display.geoProjection);
            if (transitionPlan) {
                this.props.pathRenderer.render(transitionPlan, context, this.pathStreamStack, true);
            }
            if (procedurePlan) {
                this.props.pathRenderer.render(procedurePlan, context, this.pathStreamStack, false);
            }
        }
        /**
         * Refreshes this layer's flight plan leg waypoint records if a refresh is scheduled.
         */
        updateRefreshWaypoints() {
            if (this.needRefreshProcedureWaypoints && !this.procedureWaypointRecordManager.isBusy()) {
                const plan = this.procPreviewModule.procedurePlan.get();
                this.procedureWaypointRecordManager.refreshWaypoints(plan, -1, this.needRepickProcedureWaypoints);
                this.needRefreshProcedureWaypoints = false;
                this.needRepickProcedureWaypoints = false;
            }
            if (this.needRefreshTransitionWaypoints && !this.transitionWaypointRecordManager.isBusy()) {
                const plan = this.procPreviewModule.transitionPlan.get();
                const pickPosition = this.procPreviewModule.procedureType.get() === exports.ProcedureType.DEPARTURE ? 'last' : 'first';
                this.transitionWaypointRecordManager.refreshWaypoints(plan, this.needRepickTransitionWaypoints, pickPosition);
                this.needRefreshTransitionWaypoints = false;
                this.needRepickTransitionWaypoints = false;
            }
        }
        /**
         * Schedules flight plan drawing updates.
         * @param scheduleRedrawRoute Whether to schedule a redraw of the flight path.
         * @param scheduleRefreshProcedureWaypoints Whether to schedule a refresh of procedure waypoint records.
         * @param scheduleRepickProcedureWaypoints Whether to schedule a repick of procedure waypoint records.
         * @param scheduleRefreshTransitionWaypoints Whether to schedule a refresh of transition waypoint records.
         * @param scheduleRepickTransitionWaypoints Whether to schedule a repick of transition waypoint records.
         */
        scheduleUpdates(scheduleRedrawRoute, scheduleRefreshProcedureWaypoints, scheduleRepickProcedureWaypoints, scheduleRefreshTransitionWaypoints, scheduleRepickTransitionWaypoints) {
            this.needDrawRoute || (this.needDrawRoute = scheduleRedrawRoute);
            this.needRefreshProcedureWaypoints || (this.needRefreshProcedureWaypoints = scheduleRefreshProcedureWaypoints);
            this.needRepickProcedureWaypoints || (this.needRepickProcedureWaypoints = scheduleRepickProcedureWaypoints);
            this.needRefreshTransitionWaypoints || (this.needRefreshTransitionWaypoints = scheduleRefreshTransitionWaypoints);
            this.needRepickTransitionWaypoints || (this.needRepickTransitionWaypoints = scheduleRepickTransitionWaypoints);
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent(msfsSdk.FSComponent.Fragment, null,
                msfsSdk.FSComponent.buildComponent(msfsSdk.MapCachedCanvasLayer, { ref: this.flightPathLayerRef, model: this.props.model, mapProjection: this.props.mapProjection, overdrawFactor: Math.SQRT2 }),
                msfsSdk.FSComponent.buildComponent(msfsSdk.MapSyncedCanvasLayer, { ref: this.waypointLayerRef, model: this.props.model, mapProjection: this.props.mapProjection })));
        }
    }
    MapProcedurePreviewLayer.CLIP_BOUNDS_BUFFER = 10; // number of pixels from edge of canvas to extend the clipping bounds, in pixels

    /**
     * A display which renders a map range value with units. Automatically switches between nautical miles/feet and
     * kilometers/meters at predefined thresholds.
     */
    class MapRangeDisplay extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.displayUnitSub = msfsSdk.Subject.create(null);
            this.autoSubject = msfsSdk.Subject.create('false');
            this.autoOverrideSubject = msfsSdk.Subject.create('false');
            this.displayUnitHandler = this.updateDisplayUnit.bind(this);
        }
        /** @inheritdoc */
        onAfterRender() {
            this.props.range.sub(this.displayUnitHandler);
            this.props.displayUnit.sub(this.displayUnitHandler, true);
        }
        /**
         * Updates this component's display unit.
         */
        updateDisplayUnit() {
            const nominalDisplayUnit = this.props.displayUnit.get();
            const range = this.props.range.get();
            let displayUnit;
            if (nominalDisplayUnit && nominalDisplayUnit.equals(msfsSdk.UnitType.NMILE)) {
                if (range.asUnit(msfsSdk.UnitType.FOOT) <= 2501) {
                    displayUnit = msfsSdk.UnitType.FOOT;
                }
                else {
                    displayUnit = msfsSdk.UnitType.NMILE;
                }
            }
            else if (nominalDisplayUnit && nominalDisplayUnit.equals(msfsSdk.UnitType.KILOMETER)) {
                if (range.asUnit(msfsSdk.UnitType.METER) < 999) {
                    displayUnit = msfsSdk.UnitType.METER;
                }
                else {
                    displayUnit = msfsSdk.UnitType.KILOMETER;
                }
            }
            else {
                displayUnit = nominalDisplayUnit;
            }
            this.displayUnitSub.set(displayUnit);
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: `map-range-display${this.props.class ? ` ${this.props.class}` : ''}`, auto: this.autoSubject, "auto-override": this.autoOverrideSubject, style: 'display: flex; flex-flow: column nowrap; align-items: center;' },
                msfsSdk.FSComponent.buildComponent("div", { class: 'map-range-display-auto', style: 'display: none;' }, "AUTO"),
                msfsSdk.FSComponent.buildComponent(NumberUnitDisplay, { value: this.props.range, displayUnit: this.displayUnitSub, formatter: msfsSdk.NumberFormatter.create({ precision: 0.01, forceDecimalZeroes: false, maxDigits: 3 }) })));
        }
    }

    /**
     * A map layer which draws a range compass in front of the map target.
     */
    class MapRangeCompassLayer extends msfsSdk.MapLayer {
        constructor() {
            var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
            super(...arguments);
            this.rootRef = msfsSdk.FSComponent.createRef();
            this.arcLayerRef = msfsSdk.FSComponent.createRef();
            this.roseLayerContainerRef = msfsSdk.FSComponent.createRef();
            this.roseLayerRef = msfsSdk.FSComponent.createRef();
            this.referenceMarkerContainerRef = msfsSdk.FSComponent.createRef();
            this.roseLabelsLayerRef = msfsSdk.FSComponent.createRef();
            this.headingIndicatorRef = msfsSdk.FSComponent.createRef();
            this.rangeDisplayContainerRef = msfsSdk.FSComponent.createRef();
            this.arcStrokeWidth = (_a = this.props.arcStrokeWidth) !== null && _a !== void 0 ? _a : MapRangeCompassLayer.DEFAULT_ARC_STROKE_WIDTH;
            this.arcStrokeColor = (_b = this.props.arcStrokeColor) !== null && _b !== void 0 ? _b : MapRangeCompassLayer.DEFAULT_ARC_STROKE_COLOR;
            this.arcEndTickLength = (_c = this.props.arcEndTickLength) !== null && _c !== void 0 ? _c : this.props.bearingTickMajorLength;
            this.bearingLabelFontColor = (_d = this.props.bearingLabelFontColor) !== null && _d !== void 0 ? _d : this.arcStrokeColor;
            this.bearingLabelOutlineWidth = (_e = this.props.bearingLabelOutlineWidth) !== null && _e !== void 0 ? _e : MapRangeCompassLayer.DEFAULT_HEADING_LABEL_OUTLINE_WIDTH;
            this.bearingLabelOutlineColor = (_f = this.props.bearingLabelOutlineColor) !== null && _f !== void 0 ? _f : MapRangeCompassLayer.DEFAULT_HEADING_LABEL_OUTLINE_COLOR;
            this.bearingLabelRadialOffset = (_g = this.props.bearingLabelRadialOffset) !== null && _g !== void 0 ? _g : MapRangeCompassLayer.DEFAULT_HEADING_LABEL_RADIAL_OFFSET;
            this.referenceTickWidth = (_h = this.props.referenceTickWidth) !== null && _h !== void 0 ? _h : this.arcStrokeWidth;
            this.referenceTickHeight = (_j = this.props.referenceTickHeight) !== null && _j !== void 0 ? _j : this.props.bearingTickMinorLength;
            this.headingBugWidth = (_k = this.props.headingBugWidth) !== null && _k !== void 0 ? _k : MapRangeCompassLayer.DEFAULT_HEADING_BUG_WIDTH;
            this.headingBugHeight = (_l = this.props.headingBugHeight) !== null && _l !== void 0 ? _l : MapRangeCompassLayer.DEFAULT_HEADING_BUG_HEIGHT;
            this.headingLineWidth = (_m = this.props.headingLineWidth) !== null && _m !== void 0 ? _m : this.arcStrokeWidth;
            this.headingLineStyle = (_o = this.props.headingLineStyle) !== null && _o !== void 0 ? _o : MapRangeCompassLayer.DEFAULT_HEADING_LINE_STYLE;
            this.headingLineDash = (_p = this.props.headingLineDash) !== null && _p !== void 0 ? _p : [3 * this.headingLineWidth, 3 * this.headingLineWidth];
            this.referenceArrowWidth = (_q = this.props.referenceArrowWidth) !== null && _q !== void 0 ? _q : MapRangeCompassLayer.DEFAULT_REF_ARROW_WIDTH;
            this.referenceArrowHeight = (_r = this.props.referenceArrowHeight) !== null && _r !== void 0 ? _r : MapRangeCompassLayer.DEFAULT_REF_ARROW_HEIGHT;
            this.unitsModule = this.props.model.getModule(GarminMapKeys.Units);
            this.rangeModule = this.props.model.getModule(GarminMapKeys.Range);
            this.orientationModule = this.props.model.getModule(GarminMapKeys.Orientation);
            this.rangeCompassModule = this.props.model.getModule(GarminMapKeys.RangeCompass);
            this.isFollowingAirplane = this.props.model.getModule(msfsSdk.MapSystemKeys.FollowAirplane).isFollowing;
            this.centerSubject = msfsSdk.Vec2Subject.create(new Float64Array(2));
            this.radiusSubject = msfsSdk.Subject.create(0);
            this.rotationSubject = msfsSdk.Subject.create(0);
            this.magVarCorrectionSubject = msfsSdk.MappedSubject.create(([navAngle, magVar]) => navAngle.isMagnetic() ? magVar : 0, (_t = (_s = this.unitsModule) === null || _s === void 0 ? void 0 : _s.navAngle) !== null && _t !== void 0 ? _t : msfsSdk.Subject.create(msfsSdk.BasicNavAngleUnit.create(true)), this.props.model.getModule(msfsSdk.MapSystemKeys.OwnAirplaneProps).magVar);
            this.referenceMarkerTypeSub = msfsSdk.Subject.create(MapRangeCompassReferenceMarkerType.TICK);
            this.needUpdateRootVisibility = false;
            this.needRedrawArc = true;
            this.needRedrawBearings = true;
            this.needRotateBearingTicks = true;
            this.needRechooseReferenceMarker = true;
            this.needRepositionReferenceMarker = true;
            this.needReclipTicks = true;
            this.needUpdateHeadingIndicatorVisibility = true;
            this.needRepositionLabel = true;
            this.subscriptions = [];
        }
        /** @inheritdoc */
        onAfterRender(thisNode) {
            this.thisNode = thisNode;
        }
        /** @inheritdoc */
        onVisibilityChanged(isVisible) {
            this.needUpdateRootVisibility = true;
            if (isVisible) {
                this.needRechooseReferenceMarker = true;
                this.updateParameters();
            }
            // We need to set the heading indicator's visibility to false when we hide the entire compass so that when we
            // show the compass again, the heading indicator runs the correct update code for changing from a hidden to a
            // visible state.
            const headingIndicator = this.headingIndicatorRef.getOrDefault();
            if (headingIndicator) {
                if (isVisible) {
                    this.needUpdateHeadingIndicatorVisibility = true;
                }
                else {
                    headingIndicator.setVisible(false);
                }
            }
        }
        /** @inheritdoc */
        onAttached() {
            var _a;
            super.onAttached();
            this.arcLayerRef.instance.onAttached();
            this.roseLayerRef.instance.onAttached();
            this.roseLabelsLayerRef.instance.onAttached();
            this.referenceMarkerContainerRef.instance.onAttached();
            (_a = this.headingIndicatorRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.onAttached();
            this.initListeners();
            this.updateVisibility();
            this.updateParameters();
        }
        /**
         * Initializes listeners.
         */
        initListeners() {
            this.centerSubject.sub(this.onCenterChanged.bind(this));
            this.radiusSubject.sub(this.onRadiusChanged.bind(this));
            this.rotationSubject.sub(this.onRotationChanged.bind(this));
            this.magVarCorrectionSubject.sub(this.onMagVarCorrectionChanged.bind(this));
            this.subscriptions.push(this.rangeModule.nominalRange.sub(this.onRangeChanged.bind(this)), this.orientationModule.orientation.sub(this.onOrientationChanged.bind(this)), this.rangeCompassModule.show.sub(this.onRangeCompassShowChanged.bind(this)), this.isFollowingAirplane.sub(() => {
                this.needRechooseReferenceMarker = true;
                this.needUpdateHeadingIndicatorVisibility = true;
            }));
        }
        /** @inheritdoc */
        onMapProjectionChanged(mapProjection, changeFlags) {
            var _a;
            this.arcLayerRef.instance.onMapProjectionChanged(mapProjection, changeFlags);
            this.roseLabelsLayerRef.instance.onMapProjectionChanged(mapProjection, changeFlags);
            (_a = this.headingIndicatorRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.onMapProjectionChanged(mapProjection, changeFlags);
            if (msfsSdk.BitFlags.isAll(changeFlags, msfsSdk.MapProjectionChangeType.ProjectedSize)) {
                // resizing the map will cause synced canvas layers to clear themselves, so we need to force a redraw on these
                // layers.
                this.needRedrawArc = true;
                this.needRedrawBearings = true;
            }
            if (!this.isVisible()) {
                return;
            }
            this.updateParameters();
        }
        /** @inheritdoc */
        onUpdated(time, elapsed) {
            if (this.needUpdateRootVisibility) {
                this.updateRootVisibility();
                this.needUpdateRootVisibility = false;
            }
            if (!this.isVisible()) {
                return;
            }
            this.redraw();
            this.updateSubLayers(time, elapsed);
        }
        /**
         * Updates the visibility of this layer's root.
         */
        updateRootVisibility() {
            this.rootRef.instance.style.display = this.isVisible() ? 'block' : 'none';
        }
        /**
         * Redraws the compass.
         */
        redraw() {
            this.redrawArc();
            this.redrawBearings();
            this.updateReferenceMarker();
            this.updateHeadingIndicator();
            if (this.props.showLabel) {
                this.updateLabel();
            }
        }
        /**
         * Redraws the arc of the compass.
         */
        redrawArc() {
            if (!this.needRedrawArc) {
                return;
            }
            const arcLayerDisplay = this.arcLayerRef.instance.display;
            arcLayerDisplay.clear();
            const center = this.centerSubject.get();
            const radius = this.radiusSubject.get();
            const angularWidthRad = MapRangeCompassLayer.ARC_ANGULAR_WIDTH * Avionics.Utils.DEG2RAD;
            const startAngle = -Math.PI / 2 - angularWidthRad / 2;
            const endAngle = -Math.PI / 2 + angularWidthRad / 2;
            const leftTickStart = msfsSdk.Vec2Math.add(msfsSdk.Vec2Math.setFromPolar(radius + this.arcEndTickLength, startAngle, MapRangeCompassLayer.vec2Cache[0]), center, MapRangeCompassLayer.vec2Cache[0]);
            const leftTickEnd = msfsSdk.Vec2Math.add(msfsSdk.Vec2Math.setFromPolar(radius, startAngle, MapRangeCompassLayer.vec2Cache[1]), center, MapRangeCompassLayer.vec2Cache[1]);
            const rightTickStart = msfsSdk.Vec2Math.add(msfsSdk.Vec2Math.setFromPolar(radius, endAngle, MapRangeCompassLayer.vec2Cache[2]), center, MapRangeCompassLayer.vec2Cache[2]);
            const rightTickEnd = msfsSdk.Vec2Math.add(msfsSdk.Vec2Math.setFromPolar(radius + this.arcEndTickLength, endAngle, MapRangeCompassLayer.vec2Cache[3]), center, MapRangeCompassLayer.vec2Cache[3]);
            this.composeArcPath(center, radius, angularWidthRad, leftTickStart, leftTickEnd, rightTickStart, rightTickEnd);
            arcLayerDisplay.context.lineWidth = this.arcStrokeWidth;
            arcLayerDisplay.context.strokeStyle = this.arcStrokeColor;
            arcLayerDisplay.context.stroke();
            this.needRedrawArc = false;
        }
        /**
         * Composes the path of the compass arc.
         * @param center The center of the compass, in pixels.
         * @param radius The radius of the compass, in pixels.
         * @param angularWidth The angular width of the arc, in radians.
         * @param leftTickStart The position of the start of the left end tick, in pixels.
         * @param leftTickEnd The position of the end of the left end tick, in pixels.
         * @param rightTickStart The position of the start of the right end tick, in pixels.
         * @param rightTickEnd The position of the end of the right end tick, in pixels.
         */
        composeArcPath(center, radius, angularWidth, leftTickStart, leftTickEnd, rightTickStart, rightTickEnd) {
            const arcLayerDisplay = this.arcLayerRef.instance.display;
            arcLayerDisplay.context.beginPath();
            arcLayerDisplay.context.moveTo(leftTickStart[0], leftTickStart[1]);
            arcLayerDisplay.context.lineTo(leftTickEnd[0], leftTickEnd[1]);
            arcLayerDisplay.context.arc(center[0], center[1], radius, (-angularWidth - Math.PI) / 2, (angularWidth - Math.PI) / 2);
            arcLayerDisplay.context.lineTo(rightTickEnd[0], rightTickEnd[1]);
        }
        /**
         * Redraws the bearing tick and labels.
         */
        redrawBearings() {
            if (!this.needRedrawBearings && !this.needRotateBearingTicks) {
                return;
            }
            this.roseLabelsLayerRef.instance.redraw();
            this.roseLayerRef.instance.updateRotation();
            this.needRotateBearingTicks = false;
            if (!this.needRedrawBearings && !this.needReclipTicks) {
                return;
            }
            if (this.needReclipTicks) {
                this.updateBearingTickClip();
            }
            this.roseLayerRef.instance.redraw();
            this.needRedrawBearings = false;
        }
        /**
         * Updates the bearing tick clip mask.
         */
        updateBearingTickClip() {
            const center = this.centerSubject.get();
            const radius = this.radiusSubject.get();
            const thick = this.arcStrokeWidth / 2;
            const innerToOuterLength = this.arcEndTickLength + thick + 5;
            const totalRadius = radius + this.arcEndTickLength + thick / 2 + 5;
            const leftAngle = -MapRangeCompassLayer.ARC_ANGULAR_WIDTH / 2 * Avionics.Utils.DEG2RAD - Math.PI / 2;
            const leftInner1 = msfsSdk.Vec2Math.setFromPolar(radius - thick / 2, leftAngle, MapRangeCompassLayer.vec2Cache[0]);
            const leftInner2 = msfsSdk.Vec2Math.setFromPolar(thick / 2, leftAngle - Math.PI / 2, MapRangeCompassLayer.vec2Cache[1]);
            const leftOuter = msfsSdk.Vec2Math.setFromPolar(innerToOuterLength, leftAngle, MapRangeCompassLayer.vec2Cache[2]);
            const outerWidth = Math.abs(leftInner1[0] + leftInner2[0] + leftOuter[0]) * 2;
            this.roseLayerContainerRef.instance.style.webkitClipPath // the cast is to avoid typescript complaining webkitCliPath doesn't exist
                = `path('M${center[0]},${center[1]} l${leftInner1[0]},${leftInner1[1]} l${leftInner2[0]},${leftInner2[1]} l${leftOuter[0]},${leftOuter[1]} a${totalRadius},${totalRadius},0,0,1,${outerWidth},0 l${leftInner2[0]},${-leftInner2[1]} l${leftInner1[0]},${-leftInner1[1]} Z')`;
            this.needReclipTicks = false;
        }
        /**
         * Redraws the reference marker.
         */
        updateReferenceMarker() {
            if (!this.needRechooseReferenceMarker && !this.needRepositionReferenceMarker) {
                return;
            }
            if (this.needRechooseReferenceMarker) {
                const orientation = this.orientationModule.orientation.get();
                const type = (this.props.showHeadingBug && this.isFollowingAirplane.get() && orientation === exports.MapOrientation.HeadingUp)
                    ? MapRangeCompassReferenceMarkerType.ARROW
                    : MapRangeCompassReferenceMarkerType.TICK;
                this.referenceMarkerTypeSub.set(type);
                this.needRechooseReferenceMarker = false;
            }
            if (!this.needRepositionReferenceMarker) {
                return;
            }
            this.referenceMarkerContainerRef.instance.reposition();
            this.needRepositionReferenceMarker = false;
        }
        /**
         * Updates the selected heading indicator.
         */
        updateHeadingIndicator() {
            const headingIndicator = this.headingIndicatorRef.getOrDefault();
            if (!headingIndicator || !this.needUpdateHeadingIndicatorVisibility) {
                return;
            }
            const orientation = this.orientationModule.orientation.get();
            headingIndicator.setVisible(this.isFollowingAirplane.get() && (orientation === exports.MapOrientation.HeadingUp
                || orientation === exports.MapOrientation.TrackUp));
            this.needUpdateHeadingIndicatorVisibility = false;
        }
        /**
         * Updates the range display label.
         */
        updateLabel() {
            if (!this.needRepositionLabel) {
                return;
            }
            const center = this.centerSubject.get();
            const radius = this.radiusSubject.get();
            const pos = msfsSdk.Vec2Math.add(msfsSdk.Vec2Math.setFromPolar(radius, MapRangeCompassLayer.RANGE_LABEL_RADIAL_ANGLE * Avionics.Utils.DEG2RAD, MapRangeCompassLayer.vec2Cache[0]), center, MapRangeCompassLayer.vec2Cache[0]);
            this.rangeDisplayContainerRef.instance.style.left = `${pos[0]}px`;
            this.rangeDisplayContainerRef.instance.style.top = `${pos[1]}px`;
            this.needRepositionLabel = false;
        }
        /**
         * Updates this layer's sublayers.
         * @param time The current time as a UNIX timestamp.
         * @param elapsed The elapsed time, in milliseconds, since the last update.
         */
        updateSubLayers(time, elapsed) {
            var _a;
            this.arcLayerRef.instance.onUpdated(time, elapsed);
            this.roseLayerRef.instance.onUpdated(time, elapsed);
            this.roseLabelsLayerRef.instance.onUpdated(time, elapsed);
            this.referenceMarkerContainerRef.instance.onUpdated(time, elapsed);
            (_a = this.headingIndicatorRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.onUpdated(time, elapsed);
        }
        /**
         * Updates this layer's visibility.
         */
        updateVisibility() {
            this.setVisible(this.rangeCompassModule.show.get());
        }
        /**
         * Updates the ring.
         */
        updateParameters() {
            const center = this.props.mapProjection.getTargetProjected();
            const radius = Math.round(this.rangeModule.nominalRange.get().asUnit(msfsSdk.UnitType.GA_RADIAN) / this.props.mapProjection.getProjectedResolution());
            const rotation = msfsSdk.MathUtils.round((this.props.mapProjection.getRotation() + this.magVarCorrectionSubject.get() * Avionics.Utils.DEG2RAD), 1e-3);
            this.centerSubject.set(Math.round(center[0]), Math.round(center[1]));
            this.radiusSubject.set(radius);
            this.rotationSubject.set(rotation);
        }
        /**
         * Responds to changes in the location of the center of the compass.
         */
        onCenterChanged() {
            this.needRedrawArc = true;
            this.needRedrawBearings = true;
            this.needRepositionReferenceMarker = true;
            this.needReclipTicks = true;
            this.needRepositionLabel = true;
        }
        /**
         * Responds to changes in the radius of the compass.
         */
        onRadiusChanged() {
            this.needRedrawArc = true;
            this.needRedrawBearings = true;
            this.needRepositionReferenceMarker = true;
            this.needReclipTicks = true;
            this.needRepositionLabel = true;
        }
        /**
         * Responds to changes in the rotation of the compass.
         */
        onRotationChanged() {
            this.needRotateBearingTicks = true;
        }
        /**
         * Responds to changes in the magnetic variation correction for the compass.
         */
        onMagVarCorrectionChanged() {
            if (this.isVisible()) {
                this.updateParameters();
            }
        }
        /**
         * Responds to changes in the nominal map range.
         */
        onRangeChanged() {
            if (this.isVisible()) {
                this.updateParameters();
            }
        }
        /**
         * Responds to changes in the map orientation.
         */
        onOrientationChanged() {
            this.needRechooseReferenceMarker = true;
            this.needUpdateHeadingIndicatorVisibility = true;
        }
        /**
         * Responds to changes in whether to show the range ring.
         */
        onRangeCompassShowChanged() {
            this.updateVisibility();
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { ref: this.rootRef, style: 'position: absolute; left: 0; top: 0; width: 100%; height: 100%;' },
                msfsSdk.FSComponent.buildComponent(msfsSdk.MapSyncedCanvasLayer, { ref: this.arcLayerRef, model: this.props.model, mapProjection: this.props.mapProjection }),
                msfsSdk.FSComponent.buildComponent("div", { ref: this.roseLayerContainerRef, style: 'position: absolute; width: 100%; height: 100%;' },
                    msfsSdk.FSComponent.buildComponent(MapRangeCompassRose, { ref: this.roseLayerRef, model: this.props.model, mapProjection: this.props.mapProjection, compassCenterSubject: this.centerSubject, compassRadiusSubject: this.radiusSubject, compassRotationSubject: this.rotationSubject, tickMajorInterval: MapRangeCompassLayer.BEARING_TICK_MAJOR_INTERVAL, tickMinorMultiplier: MapRangeCompassLayer.BEARING_TICK_MINOR_FACTOR, tickMajorLength: this.props.bearingTickMajorLength, tickMinorLength: this.props.bearingTickMinorLength, tickStrokeWidth: this.arcStrokeWidth, tickStrokeColor: this.arcStrokeColor })),
                msfsSdk.FSComponent.buildComponent(MapRangeCompassReferenceMarkerContainer, { ref: this.referenceMarkerContainerRef, model: this.props.model, mapProjection: this.props.mapProjection, compassCenterSubject: this.centerSubject, compassRadiusSubject: this.radiusSubject, compassRotationSubject: this.rotationSubject, activeType: this.referenceMarkerTypeSub, tickWidth: this.referenceTickWidth, tickHeight: this.referenceTickHeight, arrowWidth: this.referenceArrowWidth, arrowHeight: this.referenceArrowHeight, color: this.arcStrokeColor }),
                this.renderSelectedHeadingIndicator(),
                msfsSdk.FSComponent.buildComponent(MapRangeCompassRoseLabels, { ref: this.roseLabelsLayerRef, model: this.props.model, mapProjection: this.props.mapProjection, compassCenterSubject: this.centerSubject, compassRadiusSubject: this.radiusSubject, compassRotationSubject: this.rotationSubject, angularWidth: MapRangeCompassLayer.ARC_ANGULAR_WIDTH, interval: MapRangeCompassLayer.BEARING_TICK_MAJOR_INTERVAL, font: this.props.bearingLabelFont, fontSize: this.props.bearingLabelFontSize, fontColor: this.bearingLabelFontColor, outlineWidth: this.bearingLabelOutlineWidth, outlineColor: this.bearingLabelOutlineColor, radialOffset: this.props.bearingTickMajorLength + this.bearingLabelRadialOffset }),
                this.renderRangeDisplay()));
        }
        /**
         * Renders the selected heading indicator.
         * @returns a VNode representing the range display label.
         */
        renderSelectedHeadingIndicator() {
            const autopilotPropsModule = this.props.model.getModule(msfsSdk.MapSystemKeys.AutopilotProps);
            return this.props.showHeadingBug && autopilotPropsModule
                ? (msfsSdk.FSComponent.buildComponent(MapRangeCompassSelectedHeading, { ref: this.headingIndicatorRef, model: this.props.model, mapProjection: this.props.mapProjection, selectedHeading: autopilotPropsModule.selectedHeading, manualHeadingSelect: this.props.supportHeadingSync ? autopilotPropsModule.manualHeadingSelect : undefined, compassCenterSubject: this.centerSubject, compassRadiusSubject: this.radiusSubject, compassRotationSubject: this.rotationSubject, bugWidth: this.headingBugWidth, bugHeight: this.headingBugHeight, bugNotchHeight: this.referenceArrowHeight / 3, bugNotchWidth: this.referenceArrowWidth / 3, outlineWidth: 1, lineWidth: this.headingLineWidth, lineStyle: this.headingLineStyle, lineDash: this.headingLineDash }))
                : (msfsSdk.FSComponent.buildComponent("div", { style: 'display: none;' }));
        }
        /**
         * Renders the range display label.
         * @returns a VNode representing the range display label.
         */
        renderRangeDisplay() {
            var _a, _b;
            const rangeModule = this.rangeModule;
            const displayUnit = (_b = (_a = this.unitsModule) === null || _a === void 0 ? void 0 : _a.distanceLarge) !== null && _b !== void 0 ? _b : msfsSdk.Subject.create(msfsSdk.UnitType.NMILE);
            return this.props.showLabel
                ? (msfsSdk.FSComponent.buildComponent("div", { ref: this.rangeDisplayContainerRef, style: 'position: absolute; transform: translate(-50%, -50%);' }, this.props.renderLabel !== undefined
                    ? this.props.renderLabel(rangeModule.nominalRange, displayUnit)
                    : (msfsSdk.FSComponent.buildComponent(MapRangeDisplay, { range: rangeModule.nominalRange, displayUnit: displayUnit }))))
                : null;
        }
        /** @inheritdoc */
        destroy() {
            this.thisNode && msfsSdk.FSComponent.shallowDestroy(this.thisNode);
            for (const sub of this.subscriptions) {
                sub.destroy();
            }
            super.destroy();
        }
    }
    /** The angular width of the compass arc, in degrees. */
    MapRangeCompassLayer.ARC_ANGULAR_WIDTH = 120;
    /** The angular interval, in degrees, between major bearing ticks. */
    MapRangeCompassLayer.BEARING_TICK_MAJOR_INTERVAL = 30;
    /** The number of minor bearing ticks per major bearing tick. */
    MapRangeCompassLayer.BEARING_TICK_MINOR_FACTOR = 3;
    /** The radial on which the range label is positioned, in degrees. */
    MapRangeCompassLayer.RANGE_LABEL_RADIAL_ANGLE = -135;
    MapRangeCompassLayer.DEFAULT_ARC_STROKE_WIDTH = 2; // px
    MapRangeCompassLayer.DEFAULT_ARC_STROKE_COLOR = 'white';
    MapRangeCompassLayer.DEFAULT_HEADING_LABEL_OUTLINE_WIDTH = 6; // px
    MapRangeCompassLayer.DEFAULT_HEADING_LABEL_OUTLINE_COLOR = 'black';
    MapRangeCompassLayer.DEFAULT_HEADING_LABEL_RADIAL_OFFSET = 0; // px
    MapRangeCompassLayer.DEFAULT_HEADING_BUG_WIDTH = 20; // px
    MapRangeCompassLayer.DEFAULT_HEADING_BUG_HEIGHT = 10; // px
    MapRangeCompassLayer.DEFAULT_HEADING_LINE_STYLE = 'cyan';
    MapRangeCompassLayer.DEFAULT_REF_ARROW_WIDTH = 15; // px
    MapRangeCompassLayer.DEFAULT_REF_ARROW_HEIGHT = 20; // px
    MapRangeCompassLayer.vec2Cache = Array.from({ length: 4 }, () => new Float64Array(2));
    /**
     * A rotating compass rose with unlabeled graduated bearing ticks.
     */
    class MapRangeCompassRose extends msfsSdk.MapCanvasLayer {
        constructor() {
            super(...arguments);
            this.bearingStep = this.props.tickMajorInterval / this.props.tickMinorMultiplier * Avionics.Utils.DEG2RAD;
            this.numMinorBearingTicks = Math.floor(2 * Math.PI / this.bearingStep);
        }
        /**
         * Redraws the canvas.
         */
        redraw() {
            const center = this.props.compassCenterSubject.get();
            const radius = this.props.compassRadiusSubject.get();
            const canvasSize = Math.ceil(radius) * 2;
            this.setWidth(canvasSize);
            this.setHeight(canvasSize);
            this.display.canvas.style.left = `${center[0] - canvasSize / 2}px`;
            this.display.canvas.style.top = `${center[1] - canvasSize / 2}px`;
            this.display.clear();
            this.composeBearingTicksPath(radius);
            this.display.context.lineWidth = this.props.tickStrokeWidth;
            this.display.context.strokeStyle = this.props.tickStrokeColor;
            this.display.context.stroke();
        }
        /**
         * Composes the path of the bearing ticks.
         * @param radius The radius of the compass, in pixels.
         */
        composeBearingTicksPath(radius) {
            const canvasSize = this.getWidth();
            const center = msfsSdk.Vec2Math.set(canvasSize / 2, canvasSize / 2, MapRangeCompassRose.vec2Cache[0]);
            this.display.context.beginPath();
            for (let i = 0; i < this.numMinorBearingTicks; i++) {
                const bearing = i * this.bearingStep;
                const angle = bearing - Math.PI / 2;
                let start;
                if (i % MapRangeCompassLayer.BEARING_TICK_MINOR_FACTOR === 0) {
                    // major tick
                    start = msfsSdk.Vec2Math.add(msfsSdk.Vec2Math.setFromPolar(radius - this.props.tickMajorLength, angle, MapRangeCompassRose.vec2Cache[1]), center, MapRangeCompassRose.vec2Cache[1]);
                }
                else {
                    // minor tick
                    start = msfsSdk.Vec2Math.add(msfsSdk.Vec2Math.setFromPolar(radius - this.props.tickMinorLength, angle, MapRangeCompassRose.vec2Cache[1]), center, MapRangeCompassRose.vec2Cache[1]);
                }
                const end = msfsSdk.Vec2Math.add(msfsSdk.Vec2Math.setFromPolar(radius, angle, MapRangeCompassRose.vec2Cache[2]), center, MapRangeCompassRose.vec2Cache[2]);
                this.display.context.moveTo(start[0], start[1]);
                this.display.context.lineTo(end[0], end[1]);
            }
        }
        /**
         * Updates the rotation of this rose.
         */
        updateRotation() {
            this.display.canvas.style.transform = `rotate(${this.props.compassRotationSubject.get()}rad)`;
        }
    }
    MapRangeCompassRose.vec2Cache = [new Float64Array(2), new Float64Array(2), new Float64Array(2)];
    /**
     * Bearing labels for a rotating range compass rose.
     */
    class MapRangeCompassRoseLabels extends msfsSdk.MapSyncedCanvasLayer {
        constructor() {
            super(...arguments);
            this.labels = msfsSdk.ArrayUtils.create(Math.floor(360 / this.props.interval), index => {
                const bearing = index * this.props.interval;
                const bearingRounded = Math.round(bearing);
                return {
                    bearingRad: bearing * Avionics.Utils.DEG2RAD,
                    text: (bearingRounded === 0 ? 360 : bearingRounded).toString().padStart(3, '0')
                };
            });
        }
        /** @inheritdoc */
        onAttached() {
            super.onAttached();
            this.initStyles();
        }
        /**
         * Initializes styles on this layer's canvas context.
         */
        initStyles() {
            this.display.context.lineWidth = this.props.outlineWidth * 2;
            this.display.context.strokeStyle = this.props.outlineColor;
            this.display.context.font = `${this.props.fontSize}px ${this.props.font}`;
            this.display.context.fillStyle = this.props.fontColor;
        }
        /** @inheritdoc */
        onMapProjectionChanged(mapProjection, changeFlags) {
            super.onMapProjectionChanged(mapProjection, changeFlags);
            if (msfsSdk.BitFlags.isAll(changeFlags, msfsSdk.MapProjectionChangeType.ProjectedSize)) {
                this.initStyles();
            }
        }
        /**
         * Redraws the bearing labels.
         */
        redraw() {
            this.display.clear();
            const center = this.props.compassCenterSubject.get();
            const radius = this.props.compassRadiusSubject.get();
            const rotation = this.props.compassRotationSubject.get();
            const halfAngularWidth = this.props.angularWidth / 2 * Avionics.Utils.DEG2RAD;
            const centerBearing = (-rotation + msfsSdk.MathUtils.TWO_PI) % msfsSdk.MathUtils.TWO_PI;
            for (let i = 0; i < this.labels.length; i++) {
                const label = this.labels[i];
                if (msfsSdk.MathUtils.diffAngle(label.bearingRad, centerBearing, false) > halfAngularWidth) {
                    continue;
                }
                this.drawBearingLabel(center, radius, rotation, label.bearingRad, label.text);
            }
        }
        /**
         * Draws a bearing label.
         * @param center The center of the compass, in pixels.
         * @param radius The radius of the compass, in pixels.
         * @param rotation The rotation of the compass, in radians.
         * @param bearing The label's bearing, in radians.
         * @param text The label's text.
         */
        drawBearingLabel(center, radius, rotation, bearing, text) {
            const angle = bearing - Math.PI / 2 + rotation;
            const textWidth = this.display.context.measureText(text).width;
            const textHeight = this.props.fontSize;
            const textOffset = Math.hypot(textWidth, textHeight) / 2 + this.props.radialOffset;
            const textRadius = radius - textOffset;
            const labelPos = msfsSdk.Vec2Math.add(msfsSdk.Vec2Math.setFromPolar(textRadius, angle, MapRangeCompassRoseLabels.vec2Cache[0]), msfsSdk.Vec2Math.set(center[0] - textWidth / 2, center[1] + textHeight / 2, MapRangeCompassRoseLabels.vec2Cache[1]), MapRangeCompassRoseLabels.vec2Cache[0]);
            this.display.context.translate(labelPos[0], labelPos[1]);
            this.display.context.rotate(1e-3); // Applying a rotation will enable sub-pixel positioning of the text.
            if (this.props.outlineWidth > 0) {
                this.display.context.strokeText(text, 0, 0);
            }
            this.display.context.fillText(text, 0, 0);
            this.display.context.resetTransform();
        }
    }
    MapRangeCompassRoseLabels.vec2Cache = [new Float64Array(2), new Float64Array(2)];
    /**
     * A reference arrow for MapRangeCompassLayer.
     */
    class MapRangeCompassReferenceArrow extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.svgRef = msfsSdk.FSComponent.createRef();
        }
        /**
         * Sets whether this marker should be visible. This method has no effect if this marker has not been rendered.
         * @param val Whether this marker should be visible.
         */
        setVisible(val) {
            if (!this.svgRef.instance) {
                return;
            }
            this.svgRef.instance.style.display = val ? 'block' : 'none';
        }
        /**
         * Sets this marker's position. The provided position should be the position of the middle of the range compass arc.
         * This method has no effect if this marker has not been rendered.
         * @param pos The new position, in pixels.
         */
        setPosition(pos) {
            if (!this.svgRef.instance) {
                return;
            }
            const svg = this.svgRef.instance;
            svg.style.left = `${pos[0]}px`;
            svg.style.top = `${pos[1]}px`;
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("svg", { ref: this.svgRef, viewBox: '0 0 100 100', preserveAspectRatio: 'none', style: `display: none; position: absolute; width: ${this.props.width}px; height: ${this.props.height}px; transform: translate(-50%, -66.7%);` },
                msfsSdk.FSComponent.buildComponent("path", { d: 'M 0 0 L 100 0 L 50 100 Z', fill: this.props.color })));
        }
    }
    /**
     * A reference tick for MapRangeCompassLayer.
     */
    class MapRangeCompassReferenceTick extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.svgRef = msfsSdk.FSComponent.createRef();
        }
        /**
         * Sets whether this marker should be visible. This method has no effect if this marker has not been rendered.
         * @param val Whether this marker should be visible.
         */
        setVisible(val) {
            if (!this.svgRef.instance) {
                return;
            }
            this.svgRef.instance.style.display = val ? 'block' : 'none';
        }
        /**
         * Sets this marker's position. The provided position should be the position of the middle of the range compass arc.
         * This method has no effect if this marker has not been rendered.
         * @param pos The new position, in pixels.
         */
        setPosition(pos) {
            if (!this.svgRef.instance) {
                return;
            }
            const svg = this.svgRef.instance;
            svg.style.left = `${pos[0]}px`;
            svg.style.top = `${pos[1]}px`;
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("svg", { ref: this.svgRef, viewBox: '0 0 100 100', preserveAspectRatio: 'none', style: `display: none; position: absolute; width: ${this.props.width}px; height: ${this.props.height}px; transform: translate(-50%, -100%);` },
                msfsSdk.FSComponent.buildComponent("rect", { x: '0', y: '0', width: '100', height: '100', fill: this.props.color })));
        }
    }
    /**
     *
     */
    var MapRangeCompassReferenceMarkerType;
    (function (MapRangeCompassReferenceMarkerType) {
        MapRangeCompassReferenceMarkerType[MapRangeCompassReferenceMarkerType["TICK"] = 0] = "TICK";
        MapRangeCompassReferenceMarkerType[MapRangeCompassReferenceMarkerType["ARROW"] = 1] = "ARROW";
    })(MapRangeCompassReferenceMarkerType || (MapRangeCompassReferenceMarkerType = {}));
    /**
     * A container for range compass reference markers.
     */
    class MapRangeCompassReferenceMarkerContainer extends msfsSdk.MapLayer {
        constructor() {
            super(...arguments);
            this.containerRef = msfsSdk.FSComponent.createRef();
            this.referenceTickRef = msfsSdk.FSComponent.createRef();
            this.referenceArrowRef = msfsSdk.FSComponent.createRef();
            this.activeReferenceMarker = null;
        }
        /** @inheritdoc */
        onAttached() {
            this.props.activeType.sub(this.onActiveTypeChanged.bind(this), true);
        }
        /**
         * Responds to active marker type changes.
         * @param type The active marker type.
         */
        onActiveTypeChanged(type) {
            const selectedReferenceMarker = type === MapRangeCompassReferenceMarkerType.TICK
                ? this.referenceTickRef.instance
                : this.referenceArrowRef.instance;
            const oldActiveMarker = this.activeReferenceMarker;
            if (oldActiveMarker !== selectedReferenceMarker) {
                this.activeReferenceMarker = selectedReferenceMarker;
                oldActiveMarker === null || oldActiveMarker === void 0 ? void 0 : oldActiveMarker.setVisible(false);
                this.activeReferenceMarker.setVisible(true);
                this.reposition();
            }
        }
        /**
         * Repositions the reference marker.
         */
        reposition() {
            var _a;
            const center = this.props.compassCenterSubject.get();
            const radius = this.props.compassRadiusSubject.get();
            const pos = msfsSdk.Vec2Math.add(msfsSdk.Vec2Math.setFromPolar(radius, -Math.PI / 2, MapRangeCompassReferenceMarkerContainer.tempVec2), center, MapRangeCompassReferenceMarkerContainer.tempVec2);
            (_a = this.activeReferenceMarker) === null || _a === void 0 ? void 0 : _a.setPosition(pos);
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { ref: this.containerRef, style: 'position: absolute; width: 100%; height: 100%;' },
                msfsSdk.FSComponent.buildComponent(MapRangeCompassReferenceTick, { ref: this.referenceTickRef, width: this.props.tickWidth, height: this.props.tickHeight, color: this.props.color }),
                msfsSdk.FSComponent.buildComponent(MapRangeCompassReferenceArrow, { ref: this.referenceArrowRef, width: this.props.arrowWidth, height: this.props.arrowHeight, color: this.props.color })));
        }
    }
    MapRangeCompassReferenceMarkerContainer.tempVec2 = new Float64Array(2);
    /**
     * The selected heading bug and heading line for the map range compass layer.
     */
    class MapRangeCompassSelectedHeading extends msfsSdk.MapLayer {
        /** @inheritdoc */
        constructor(props) {
            super(props);
            this.canvasLayerRef = msfsSdk.FSComponent.createRef();
            this.selectedHeading = 0;
            this.isInit = false;
            this.isSuppressedSubject = msfsSdk.Subject.create(true);
            this.suppressTimer = null;
            this.centerSubject = msfsSdk.Vec2Subject.create(new Float64Array(2));
            this.radiusSubject = msfsSdk.Subject.create(0);
            this.rotationSubject = msfsSdk.Subject.create(0);
            this.isCanvasVisible = false;
            this.isHeadingOob = true;
            this.needUpdateCanvasVisibility = true;
            this.needRedraw = true;
            this.needReposition = true;
            this.needRotate = true;
            this.paramSubs = [];
            /**
             * A callback which is called when the suppress timer fires.
             */
            this.suppressCallback = () => {
                this.suppressTimer = null;
                this.isSuppressedSubject.set(true);
            };
            this.selectedHeadingSub = this.props.selectedHeading.sub(this.onSelectedHeadingChanged.bind(this), false, true);
            this.manualHeadingSelectSub = this.props.manualHeadingSelect
                ? this.props.manualHeadingSelect.on(this.onManualHeadingSelected.bind(this), true)
                : this.props.selectedHeading.sub(this.onManualHeadingSelected.bind(this), false, true);
        }
        /** @inheritdoc */
        onVisibilityChanged() {
            if (this.isInit) {
                this.updateFromVisibility();
            }
        }
        /**
         * Updates this layer according to its current visibility.
         */
        updateFromVisibility() {
            const isVisible = this.isVisible();
            if (isVisible) {
                this.selectedHeadingSub.resume(true);
                this.manualHeadingSelectSub.resume();
                this.needRedraw = true;
            }
            else {
                this.selectedHeadingSub.pause();
                this.manualHeadingSelectSub.pause();
                this.suppress();
            }
        }
        /** @inheritdoc */
        onAttached() {
            super.onAttached();
            this.canvasLayerRef.instance.onAttached();
            this.initCanvas();
            this.isInit = true;
            this.initSubjectListeners();
            this.updateFromVisibility();
        }
        /**
         * Initializes canvas width.
         */
        initCanvas() {
            const width = Math.max(this.props.lineWidth, this.props.bugWidth + this.props.outlineWidth * 2);
            this.canvasLayerRef.instance.setWidth(width);
            const canvasLayerDisplay = this.canvasLayerRef.instance.display;
            canvasLayerDisplay.canvas.style.width = `${width}px`;
            canvasLayerDisplay.canvas.style.transformOrigin = '50% 100%';
        }
        /**
         * Initializes subject listeners.
         */
        initSubjectListeners() {
            this.paramSubs.push(this.props.compassCenterSubject.sub(this.updateParameters.bind(this)), this.props.compassRadiusSubject.sub(this.updateParameters.bind(this)), this.props.compassRotationSubject.sub(this.updateParameters.bind(this)));
            this.centerSubject.sub(this.onCenterChanged.bind(this));
            this.radiusSubject.sub(this.onRadiusChanged.bind(this));
            this.rotationSubject.sub(this.onRotationChanged.bind(this));
            this.isSuppressedSubject.sub(this.onIsSuppressedChanged.bind(this));
        }
        // eslint-disable-next-line jsdoc/require-jsdoc
        onMapProjectionChanged(mapProjection, changeFlags) {
            if (msfsSdk.BitFlags.isAll(changeFlags, msfsSdk.MapProjectionChangeType.ProjectedSize)) {
                this.needReposition = true;
                this.needRedraw = true;
            }
        }
        // eslint-disable-next-line jsdoc/require-jsdoc
        onUpdated(time, elapsed) {
            if (this.needUpdateCanvasVisibility) {
                this.canvasLayerRef.instance.setVisible(this.isCanvasVisible);
                this.needUpdateCanvasVisibility = false;
            }
            this.canvasLayerRef.instance.onUpdated(time, elapsed);
            if (!this.isCanvasVisible) {
                return;
            }
            if (this.needReposition) {
                this.reposition();
            }
            else if (this.needRedraw) {
                this.redraw();
            }
            if (this.needRotate) {
                this.rotate();
            }
        }
        /**
         * Repositions the canvas.
         */
        reposition() {
            const center = this.props.compassCenterSubject.get();
            const projectedWidth = this.props.mapProjection.getProjectedSize()[0];
            const projectedHeight = this.props.mapProjection.getProjectedSize()[1];
            // find the distance to the farthest corner.
            const isLeft = center[0] > projectedWidth / 2;
            const isTop = center[1] > projectedHeight / 2;
            const height = Math.hypot(center[0] - (isLeft ? 0 : projectedWidth), center[1] - (isTop ? 0 : projectedHeight));
            this.canvasLayerRef.instance.setHeight(height);
            const canvasLayerDisplay = this.canvasLayerRef.instance.display;
            canvasLayerDisplay.canvas.style.height = `${height}px`;
            canvasLayerDisplay.canvas.style.left = `${center[0] - this.canvasLayerRef.instance.getWidth() / 2}px`;
            canvasLayerDisplay.canvas.style.bottom = `${projectedHeight - center[1]}px`;
            this.needReposition = false;
            this.redraw();
        }
        /**
         * Redraws the canvas.
         */
        redraw() {
            const canvasWidth = this.canvasLayerRef.instance.getWidth();
            const canvasHeight = this.canvasLayerRef.instance.getHeight();
            const radius = this.props.compassRadiusSubject.get();
            const canvasLayerDisplay = this.canvasLayerRef.instance.display;
            canvasLayerDisplay.clear();
            this.redrawLine(canvasWidth, canvasHeight);
            if (!this.isHeadingOob) {
                this.redrawBug(canvasWidth, canvasHeight, radius);
            }
            this.needRedraw = false;
        }
        /**
         * Redraws the heading line.
         * @param canvasWidth The width of the canvas, in pixels.
         * @param canvasHeight The height of the canvas, in pixels.
         */
        redrawLine(canvasWidth, canvasHeight) {
            const canvasLayerDisplay = this.canvasLayerRef.instance.display;
            canvasLayerDisplay.context.beginPath();
            canvasLayerDisplay.context.moveTo(canvasWidth / 2, canvasHeight);
            canvasLayerDisplay.context.lineTo(canvasWidth / 2, 0);
            canvasLayerDisplay.context.lineWidth = this.props.lineWidth;
            canvasLayerDisplay.context.strokeStyle = MapRangeCompassSelectedHeading.COLOR;
            canvasLayerDisplay.context.setLineDash(this.props.lineDash);
            canvasLayerDisplay.context.stroke();
        }
        /**
         * Redraws the heading bug.
         * @param canvasWidth The width of the canvas, in pixels.
         * @param canvasHeight The height of the canvas, in pixels.
         * @param radius The radius of the compass, in pixels.
         */
        redrawBug(canvasWidth, canvasHeight, radius) {
            const canvasLayerDisplay = this.canvasLayerRef.instance.display;
            const left = (canvasWidth - this.props.bugWidth) / 2;
            const top = canvasHeight - radius;
            const middle = canvasWidth / 2;
            const right = left + this.props.bugWidth;
            const bottom = top + this.props.bugHeight;
            canvasLayerDisplay.context.beginPath();
            canvasLayerDisplay.context.moveTo(left, top);
            canvasLayerDisplay.context.lineTo(middle - this.props.bugNotchWidth / 2, top);
            canvasLayerDisplay.context.lineTo(middle, top + this.props.bugNotchHeight);
            canvasLayerDisplay.context.lineTo(middle + this.props.bugNotchWidth / 2, top);
            canvasLayerDisplay.context.lineTo(right, top);
            canvasLayerDisplay.context.lineTo(right, bottom);
            canvasLayerDisplay.context.lineTo(left, bottom);
            canvasLayerDisplay.context.closePath();
            canvasLayerDisplay.context.fillStyle = MapRangeCompassSelectedHeading.COLOR;
            canvasLayerDisplay.context.lineWidth = this.props.outlineWidth * 2;
            canvasLayerDisplay.context.strokeStyle = MapRangeCompassSelectedHeading.OUTLINE_COLOR;
            canvasLayerDisplay.context.setLineDash(MapRangeCompassSelectedHeading.NO_LINE_DASH);
            canvasLayerDisplay.context.stroke();
            canvasLayerDisplay.context.fill();
        }
        /**
         * Rotates the canvas.
         */
        rotate() {
            const compassRotation = this.props.compassRotationSubject.get();
            const rotation = this.selectedHeading * Avionics.Utils.DEG2RAD + compassRotation;
            this.canvasLayerRef.instance.display.canvas.style.transform = `rotate(${rotation}rad)`;
            this.needRotate = false;
        }
        /**
         * Suppresses this indicator, making it invisible. Also kills the suppress timer if it is running.
         */
        suppress() {
            this.killSuppressTimer();
            this.isSuppressedSubject.set(true);
        }
        /**
         * Unsuppresses this indicator, making it visible, for a certain duration. If the suppress timer is currently
         * running, it is killed and replaced with a new one which will fire after the specified duration.
         * @param duration The duration for which to unsuppress, in milliseconds.
         */
        unsuppress(duration) {
            this.killSuppressTimer();
            this.isSuppressedSubject.set(false);
            this.suppressTimer = setTimeout(this.suppressCallback, duration);
        }
        /**
         * Kills the timer to suppress this indicator, if one is currently running.
         */
        killSuppressTimer() {
            if (this.suppressTimer !== null) {
                clearTimeout(this.suppressTimer);
            }
        }
        /**
         * Updates this indicator based on whether it should be suppressed.
         * @param isSuppressed Whether this indicator should be suppressed.
         */
        updateFromIsSuppressed(isSuppressed) {
            this.updateCanvasVisibility(!isSuppressed);
        }
        /**
         * Updates the visibility of the canvas.
         * @param isVisible Whether the canvas should be visible.
         */
        updateCanvasVisibility(isVisible) {
            if (isVisible !== this.isCanvasVisible) {
                this.isCanvasVisible = isVisible;
                this.needUpdateCanvasVisibility = true;
            }
        }
        /**
         * Updates this indicator's center, radius, and rotation.
         */
        updateParameters() {
            const compassRotation = this.props.compassRotationSubject.get();
            const compassCenter = -compassRotation * Avionics.Utils.RAD2DEG;
            const isHeadingOob = Math.abs(msfsSdk.NavMath.diffAngle(this.selectedHeading, compassCenter)) > MapRangeCompassLayer.ARC_ANGULAR_WIDTH / 2;
            if (isHeadingOob !== this.isHeadingOob) {
                this.isHeadingOob = isHeadingOob;
                this.needRedraw = true;
            }
            const center = this.props.compassCenterSubject.get();
            const radius = this.props.compassRadiusSubject.get();
            const rotation = compassRotation + this.selectedHeading * Avionics.Utils.DEG2RAD;
            this.centerSubject.set(center);
            this.radiusSubject.set(radius);
            this.rotationSubject.set(rotation);
        }
        /**
         * A callback which is called when the center of the compass changes.
         */
        onCenterChanged() {
            this.needReposition = true;
        }
        /**
         * A callback which is called when the center of the compass changes.
         */
        onRadiusChanged() {
            this.needRedraw = true;
        }
        /**
         * A callback which is called when the rotation of the compass changes.
         */
        onRotationChanged() {
            this.needRotate = true;
        }
        /**
         * A callback which is called when the selected heading is changed manually.
         */
        onManualHeadingSelected() {
            this.unsuppress(MapRangeCompassSelectedHeading.UNSUPPRESS_DURATION);
        }
        /**
         * A callback which is called when the selected heading changes.
         * @param heading The new selected heading, in degrees.
         */
        onSelectedHeadingChanged(heading) {
            this.selectedHeading = heading;
            this.updateParameters();
        }
        /**
         * A callback which is called when whether this indicator is suppressed has changed.
         * @param isSuppressed Whether this indicator is suppressed.
         */
        onIsSuppressedChanged(isSuppressed) {
            this.updateFromIsSuppressed(isSuppressed);
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent(msfsSdk.MapCanvasLayer, { ref: this.canvasLayerRef, model: this.props.model, mapProjection: this.props.mapProjection }));
        }
        /** @inheritdoc */
        destroy() {
            var _a;
            (_a = this.canvasLayerRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
            for (const sub of this.paramSubs) {
                sub.destroy();
            }
            this.selectedHeadingSub.destroy();
            this.manualHeadingSelectSub.destroy();
            super.destroy();
        }
    }
    /** The amount of time, in milliseconds, the indicator is unsuppressed when the selected heading is changed. */
    MapRangeCompassSelectedHeading.UNSUPPRESS_DURATION = 3000;
    /** The color of the bug and line. */
    MapRangeCompassSelectedHeading.COLOR = 'cyan';
    /** The outline color of the bug. */
    MapRangeCompassSelectedHeading.OUTLINE_COLOR = 'black';
    MapRangeCompassSelectedHeading.NO_LINE_DASH = [];

    /**
     * A map layer which draws a range ring around the map target.
     */
    class MapRangeRingLayer extends msfsSdk.MapLabeledRingLayer {
        constructor() {
            super(...arguments);
            this.rangeModule = this.props.model.getModule(GarminMapKeys.Range);
            this.rangeRingModule = this.props.model.getModule(GarminMapKeys.RangeRing);
            this.label = null;
            this.needUpdateRing = false;
        }
        /** @inheritdoc */
        updateFromVisibility() {
            super.updateFromVisibility();
            if (this.isVisible()) {
                this.needUpdateRing = true;
            }
        }
        /** @inheritdoc */
        onAttached() {
            super.onAttached();
            this.initLabel();
            this.initStyles();
            this.initModuleListeners();
            this.updateVisibility();
            this.needUpdateRing = true;
        }
        /**
         * Initializes the range display label.
         */
        initLabel() {
            var _a, _b, _c;
            if (!this.props.showLabel) {
                return;
            }
            const displayUnit = (_b = (_a = this.props.model.getModule(GarminMapKeys.Units)) === null || _a === void 0 ? void 0 : _a.distanceLarge) !== null && _b !== void 0 ? _b : msfsSdk.Subject.create(msfsSdk.UnitType.NMILE);
            this.label = this.createLabel(this.props.renderLabel !== undefined
                ? this.props.renderLabel(this.rangeModule.nominalRange, displayUnit)
                : (msfsSdk.FSComponent.buildComponent(MapRangeDisplay, { range: this.rangeModule.nominalRange, displayUnit: displayUnit })));
            this.label.setAnchor(new Float64Array([0.5, 0.5]));
            this.label.setRadialAngle(((_c = this.props.labelRadial) !== null && _c !== void 0 ? _c : 225) * Avionics.Utils.DEG2RAD);
        }
        /**
         * Initializes ring styles.
         */
        initStyles() {
            var _a, _b, _c, _d;
            this.setRingStrokeStyles((_a = this.props.strokeWidth) !== null && _a !== void 0 ? _a : 2, (_b = this.props.strokeStyle) !== null && _b !== void 0 ? _b : 'white', this.props.strokeDash);
            this.setRingOutlineStyles((_c = this.props.outlineWidth) !== null && _c !== void 0 ? _c : 0, (_d = this.props.outlineStyle) !== null && _d !== void 0 ? _d : 'black', this.props.outlineDash);
        }
        /**
         * Initializes modules listeners.
         */
        initModuleListeners() {
            this.rangeModule.nominalRange.sub(this.onRangeChanged.bind(this));
            this.rangeRingModule.show.sub(this.updateVisibility.bind(this));
        }
        /** @inheritdoc */
        onMapProjectionChanged(mapProjection, changeFlags) {
            super.onMapProjectionChanged(mapProjection, changeFlags);
            if (!this.isVisible()) {
                return;
            }
            this.needUpdateRing = true;
        }
        /**
         * Updates this layer's visibility.
         */
        updateVisibility() {
            this.setVisible(this.rangeRingModule.show.get());
        }
        /** @inheritdoc */
        onUpdated(time, elapsed) {
            if (this.needUpdateRing) {
                this.updateRing();
                this.needUpdateRing = false;
            }
            super.onUpdated(time, elapsed);
        }
        /**
         * Updates the ring.
         */
        updateRing() {
            const center = this.props.mapProjection.getTargetProjected();
            const radius = this.rangeModule.nominalRange.get().asUnit(msfsSdk.UnitType.GA_RADIAN) / this.props.mapProjection.getProjectedResolution();
            this.setRingPosition(center, radius);
        }
        /**
         * A callback which is called when the nominal map range changes.
         */
        onRangeChanged() {
            if (!this.isVisible()) {
                return;
            }
            this.needUpdateRing = true;
        }
    }

    /**
     * A map layer which displays a track vector.
     */
    class MapTrackVectorLayer extends msfsSdk.MapLayer {
        constructor() {
            var _a, _b, _c, _d;
            super(...arguments);
            this.canvasLayerRef = msfsSdk.FSComponent.createRef();
            this.strokeWidth = (_a = this.props.strokeWidth) !== null && _a !== void 0 ? _a : MapTrackVectorLayer.DEFAULT_STROKE_WIDTH;
            this.strokeStyle = (_b = this.props.strokeStyle) !== null && _b !== void 0 ? _b : MapTrackVectorLayer.DEFAULT_STROKE_STYLE;
            this.outlineWidth = (_c = this.props.outlineWidth) !== null && _c !== void 0 ? _c : MapTrackVectorLayer.DEFAULT_OUTLINE_WIDTH;
            this.outlineStyle = (_d = this.props.outlineStyle) !== null && _d !== void 0 ? _d : MapTrackVectorLayer.DEFAULT_OUTLINE_STYLE;
            this.ownAirplanePropsModule = this.props.model.getModule(msfsSdk.MapSystemKeys.OwnAirplaneProps);
            this.trackVectorModule = this.props.model.getModule(GarminMapKeys.TrackVector);
            this.arcTurnRateThreshold = (typeof this.props.arcTurnRateThreshold === 'object')
                ? this.props.arcTurnRateThreshold
                : msfsSdk.Subject.create(this.props.arcTurnRateThreshold);
            this.arcMaxLookaheadTime = ('isSubscribable' in this.props.arcMaxLookaheadTime)
                ? this.props.arcMaxLookaheadTime
                : msfsSdk.Subject.create(this.props.arcMaxLookaheadTime);
            this.projectedPlanePosition = msfsSdk.Vec2Subject.createFromVector(new Float64Array(2));
            this.projectPlanePositionHandler = () => {
                const projected = this.props.mapProjection.project(this.ownAirplanePropsModule.position.get(), MapTrackVectorLayer.vec2Cache[0]);
                this.projectedPlanePosition.set(projected);
            };
            this.turnRateSmoother = new msfsSdk.ExpSmoother(500 / Math.LN2, undefined, 1000);
            this.needUpdate = false;
            this.subscriptions = [];
        }
        /** @inheritdoc */
        onVisibilityChanged(isVisible) {
            var _a, _b;
            if (isVisible) {
                this.needUpdate = true;
            }
            else {
                (_b = (_a = this.canvasLayerRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.tryGetDisplay()) === null || _b === void 0 ? void 0 : _b.clear();
            }
        }
        /** @inheritdoc */
        onAttached() {
            var _a, _b, _c, _d;
            this.canvasLayerRef.instance.onAttached();
            this.subscriptions.push(this.ownAirplanePropsModule.position.sub(this.projectPlanePositionHandler));
            const scheduleUpdate = () => { this.needUpdate = true; };
            const dataIntegrityModule = this.props.model.getModule(msfsSdk.MapSystemKeys.DataIntegrity);
            this.isVectorVisible = msfsSdk.MappedSubject.create(([show, isOnGround, isGpsValid, isHeadingValid, isAttitudeValid, isAdcValid]) => show && !isOnGround && isGpsValid && isAdcValid && (isHeadingValid || isAttitudeValid), this.trackVectorModule.show, this.ownAirplanePropsModule.isOnGround, (_a = dataIntegrityModule === null || dataIntegrityModule === void 0 ? void 0 : dataIntegrityModule.gpsSignalValid) !== null && _a !== void 0 ? _a : msfsSdk.Subject.create(true), (_b = dataIntegrityModule === null || dataIntegrityModule === void 0 ? void 0 : dataIntegrityModule.headingSignalValid) !== null && _b !== void 0 ? _b : msfsSdk.Subject.create(true), (_c = dataIntegrityModule === null || dataIntegrityModule === void 0 ? void 0 : dataIntegrityModule.attitudeSignalValid) !== null && _c !== void 0 ? _c : msfsSdk.Subject.create(true), (_d = dataIntegrityModule === null || dataIntegrityModule === void 0 ? void 0 : dataIntegrityModule.adcSignalValid) !== null && _d !== void 0 ? _d : msfsSdk.Subject.create(true));
            this.isVectorVisible.sub(isVisible => { this.setVisible(isVisible); }, true);
            this.subscriptions.push(this.projectedPlanePosition.sub(scheduleUpdate));
            this.subscriptions.push(this.ownAirplanePropsModule.turnRate.sub(scheduleUpdate));
            this.subscriptions.push(this.ownAirplanePropsModule.trackTrue.sub(scheduleUpdate));
            this.subscriptions.push(this.ownAirplanePropsModule.groundSpeed.sub(scheduleUpdate));
            this.subscriptions.push(this.trackVectorModule.lookaheadTime.sub(scheduleUpdate, true));
        }
        /** @inheritdoc */
        onMapProjectionChanged(mapProjection, changeFlags) {
            this.canvasLayerRef.instance.onMapProjectionChanged(mapProjection, changeFlags);
            this.projectPlanePositionHandler();
            this.needUpdate = true;
        }
        /** @inheritdoc */
        onUpdated(time, elapsed) {
            if (!this.needUpdate || !this.isVisible()) {
                return;
            }
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            const display = this.canvasLayerRef.instance.display;
            display.clear();
            const lookaheadTime = this.trackVectorModule.lookaheadTime.get();
            const track = this.ownAirplanePropsModule.trackTrue.get();
            const groundSpeed = this.ownAirplanePropsModule.groundSpeed.get();
            const turnRate = this.turnRateSmoother.next(this.ownAirplanePropsModule.turnRate.get(), elapsed);
            const distanceNM = groundSpeed.asUnit(msfsSdk.UnitType.KNOT) * lookaheadTime.asUnit(msfsSdk.UnitType.HOUR);
            const distancePx = msfsSdk.UnitType.NMILE.convertTo(distanceNM, msfsSdk.UnitType.GA_RADIAN) / this.props.mapProjection.getProjectedResolution();
            const projectedTrackAngle = track * Avionics.Utils.DEG2RAD + this.props.mapProjection.getRotation() - Math.PI / 2;
            const projectedPlanePos = this.projectedPlanePosition.get();
            display.context.beginPath();
            display.context.moveTo(projectedPlanePos[0], projectedPlanePos[1]);
            if (Math.abs(turnRate) < this.arcTurnRateThreshold.get() || lookaheadTime.compare(this.arcMaxLookaheadTime.get()) > 0) {
                // draw a line
                const delta = msfsSdk.Vec2Math.setFromPolar(distancePx, projectedTrackAngle, MapTrackVectorLayer.vec2Cache[0]);
                display.context.lineTo(projectedPlanePos[0] + delta[0], projectedPlanePos[1] + delta[1]);
            }
            else {
                // draw an arc
                const groundSpeedPxPerSec = msfsSdk.UnitType.NMILE.convertTo(groundSpeed.asUnit(msfsSdk.UnitType.KNOT) / 3600, msfsSdk.UnitType.GA_RADIAN) / this.props.mapProjection.getProjectedResolution();
                const turnRadius = groundSpeedPxPerSec / (turnRate * Avionics.Utils.DEG2RAD);
                const angularWidthDrawn = Utils.Clamp(distancePx / turnRadius, -Math.PI / 2, Math.PI / 2);
                const circleOffsetAngle = projectedTrackAngle + Math.PI / 2;
                const circleCenter = msfsSdk.Vec2Math.add(msfsSdk.Vec2Math.setFromPolar(turnRadius, circleOffsetAngle, MapTrackVectorLayer.vec2Cache[0]), projectedPlanePos, MapTrackVectorLayer.vec2Cache[0]);
                const startAngle = circleOffsetAngle + (turnRadius < 0 ? 0 : Math.PI);
                const endAngle = startAngle + angularWidthDrawn;
                display.context.arc(circleCenter[0], circleCenter[1], Math.abs(turnRadius), startAngle, endAngle, turnRadius < 0);
            }
            display.context.lineWidth = this.strokeWidth + this.outlineWidth * 2;
            display.context.strokeStyle = this.outlineStyle;
            display.context.stroke();
            display.context.lineWidth = this.strokeWidth;
            display.context.strokeStyle = this.strokeStyle;
            display.context.stroke();
            this.needUpdate = false;
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent(msfsSdk.MapSyncedCanvasLayer, { ref: this.canvasLayerRef, model: this.props.model, mapProjection: this.props.mapProjection }));
        }
        /** @inheritdoc */
        destroy() {
            var _a;
            super.destroy();
            (_a = this.isVectorVisible) === null || _a === void 0 ? void 0 : _a.destroy();
            this.subscriptions.forEach(sub => sub.destroy());
        }
    }
    MapTrackVectorLayer.DEFAULT_STROKE_WIDTH = 2; // px
    MapTrackVectorLayer.DEFAULT_STROKE_STYLE = 'cyan';
    MapTrackVectorLayer.DEFAULT_OUTLINE_WIDTH = 1; // px
    MapTrackVectorLayer.DEFAULT_OUTLINE_STYLE = '#505050';
    MapTrackVectorLayer.vec2Cache = [new Float64Array(2)];

    /**
     * The map layer showing highlighted waypoints.
     */
    class MapWaypointHighlightLayer extends msfsSdk.MapSyncedCanvasLayer {
        constructor() {
            super(...arguments);
            this.registeredWaypoint = null;
        }
        /** @inheritdoc */
        onAttached() {
            super.onAttached();
            this.isInit = false;
            this.initWaypointRenderer();
            this.initModuleListener();
            this.isInit = true;
        }
        /**
         * Initializes the waypoint renderer.
         */
        initWaypointRenderer() {
            this.props.waypointRenderer.setCanvasContext(exports.MapWaypointRenderRole.Highlight, this.display.context);
        }
        /**
         * Initializes the waypoint highlight listener.
         */
        initModuleListener() {
            this.props.model.getModule(GarminMapKeys.WaypointHighlight).waypoint.sub(this.onWaypointChanged.bind(this), true);
        }
        /**
         * A callback which is called when the highlighted waypoint changes.
         * @param waypoint The new highlighted waypoint.
         */
        onWaypointChanged(waypoint) {
            this.registeredWaypoint && this.props.waypointRenderer.deregister(this.registeredWaypoint, exports.MapWaypointRenderRole.Highlight, 'waypoint-highlight-layer');
            waypoint && this.props.waypointRenderer.register(waypoint, exports.MapWaypointRenderRole.Highlight, 'waypoint-highlight-layer');
            this.registeredWaypoint = waypoint;
        }
    }

    /**
     * A waypoint for a map runway label.
     */
    class MapRunwayLabelWaypoint extends msfsSdk.AbstractWaypoint {
        /**
         * Constructor.
         * @param airport The parent airport of the runway associated with this waypoint.
         * @param runway The runway associated with this waypoint.
         */
        constructor(airport, runway) {
            super();
            this.runway = runway;
            this._uid = MapRunwayLabelWaypoint.getUid(airport, runway);
            this._location = msfsSdk.GeoPointSubject.create(new msfsSdk.GeoPoint(runway.latitude, runway.longitude).offset(runway.course, msfsSdk.UnitType.METER.convertTo(-runway.startThresholdLength, msfsSdk.UnitType.GA_RADIAN)));
        }
        /** @inheritdoc */
        get type() {
            return MapRunwayLabelWaypoint.TYPE;
        }
        /** @inheritdoc */
        get uid() {
            return this._uid;
        }
        /** @inheritdoc */
        get location() {
            return this._location;
        }
        /**
         * Gets the unique ID for a MapRunwayLabelWaypoint associated with a given airport and runway.
         * @param airport The parent airport of the runway associated with the waypoint.
         * @param runway The runway associated with the waypoint.
         * @returns The unique ID for the waypoint associated with the specified airport and runway.
         */
        static getUid(airport, runway) {
            return `${msfsSdk.RunwayUtils.getRunwayFacilityIcao(airport, runway)} LABEL`;
        }
    }
    MapRunwayLabelWaypoint.TYPE = 'RunwayLabel';

    /**
     * A cache for map runway label waypoints.
     */
    class MapRunwayLabelWaypointCache {
        /**
         * Constructor.
         * @param size The maximum size of this cache.
         */
        constructor(size) {
            this.size = size;
            this.cache = new Map();
        }
        /**
         * Gets a waypoint from this cache for a specific runway. If one does not exist, a new waypoint will be created.
         * @param airport The parent airport of the runway for which to get a waypoint.
         * @param runway The runway for which to get a waypoint.
         * @returns A waypoint.
         */
        get(airport, runway) {
            const uid = MapRunwayLabelWaypoint.getUid(airport, runway);
            let existing = this.cache.get(uid);
            if (!existing) {
                this.addToCache(existing = new MapRunwayLabelWaypoint(airport, runway));
            }
            return existing;
        }
        /**
         * Adds a waypoint to this cache. If the size of the cache is greater than the maximum after the new waypoint is
         * added, a waypoint will be removed from the cache in FIFO order.
         * @param waypoint The waypoint to add.
         */
        addToCache(waypoint) {
            this.cache.set(waypoint.uid, waypoint);
            if (this.cache.size > this.size) {
                this.cache.delete(this.cache.keys().next().value);
            }
        }
        /**
         * Gets a MapRunwayOutlineWaypointCache instance.
         * @returns A MapRunwayOutlineWaypointCache instance.
         */
        static getCache() {
            var _a;
            return (_a = MapRunwayLabelWaypointCache.INSTANCE) !== null && _a !== void 0 ? _a : (MapRunwayLabelWaypointCache.INSTANCE = new MapRunwayLabelWaypointCache(1000));
        }
    }

    /**
     * A waypoint for a map runway outline.
     */
    class MapRunwayOutlineWaypoint extends msfsSdk.AbstractWaypoint {
        /**
         * Constructor.
         * @param airport The parent airport of the runway associated with this waypoint.
         * @param runway The runway associated with this waypoint.
         */
        constructor(airport, runway) {
            super();
            this.runway = runway;
            this._location = msfsSdk.GeoPointSubject.create(new msfsSdk.GeoPoint(this.runway.latitude, this.runway.longitude));
            /** The surface category of this waypoint's associated runway. */
            this.surfaceCategory = msfsSdk.RunwayUtils.getSurfaceCategory(this.runway);
            /** The primary runway number of this waypoint's associated runway. */
            this.primaryNumber = msfsSdk.RunwayUtils.getRunwayNumberPrimary(this.runway);
            /** The secondary runway number of this waypoint's associated runway, or `undefined` if there is no secondary runway. */
            this.secondaryNumber = msfsSdk.RunwayUtils.getRunwayNumberSecondary(this.runway);
            this._uid = MapRunwayOutlineWaypoint.getUid(airport, runway);
        }
        /** @inheritdoc */
        get type() {
            return MapRunwayOutlineWaypoint.TYPE;
        }
        /** @inheritdoc */
        get uid() {
            return this._uid;
        }
        /** @inheritdoc */
        get location() {
            return this._location;
        }
        /**
         * Gets the unique ID for a MapRunwayLabelWaypoint associated with a given airport and runway.
         * @param airport The parent airport of the runway associated with the waypoint.
         * @param runway The runway associated with the waypoint.
         * @returns The unique ID for the waypoint associated with the specified airport and runway.
         */
        static getUid(airport, runway) {
            const runwayName = msfsSdk.RunwayUtils.getRunwayPairNameString(runway);
            return `${airport.icao} RW${runwayName} OUTLINE`;
        }
    }
    MapRunwayOutlineWaypoint.TYPE = 'RunwayOutline';

    /**
     * A cache for map runway outline waypoints.
     */
    class MapRunwayOutlineWaypointCache {
        /**
         * Constructor.
         * @param size The maximum size of this cache.
         */
        constructor(size) {
            this.size = size;
            this.cache = new Map();
        }
        /**
         * Gets a waypoint from this cache for a specific runway. If one does not exist, a new waypoint will be created.
         * @param airport The parent airport of the runway for which to get a waypoint.
         * @param runway The runway for which to get a waypoint.
         * @returns A waypoint.
         */
        get(airport, runway) {
            const uid = MapRunwayOutlineWaypoint.getUid(airport, runway);
            let existing = this.cache.get(uid);
            if (!existing) {
                this.addToCache(existing = new MapRunwayOutlineWaypoint(airport, runway));
            }
            return existing;
        }
        /**
         * Adds a waypoint to this cache. If the size of the cache is greater than the maximum after the new waypoint is
         * added, a waypoint will be removed from the cache in FIFO order.
         * @param waypoint The waypoint to add.
         */
        addToCache(waypoint) {
            this.cache.set(waypoint.uid, waypoint);
            if (this.cache.size > this.size) {
                this.cache.delete(this.cache.keys().next().value);
            }
        }
        /**
         * Gets a MapRunwayOutlineWaypointCache instance.
         * @returns A MapRunwayOutlineWaypointCache instance.
         */
        static getCache() {
            var _a;
            return (_a = MapRunwayOutlineWaypointCache.INSTANCE) !== null && _a !== void 0 ? _a : (MapRunwayOutlineWaypointCache.INSTANCE = new MapRunwayOutlineWaypointCache(1000));
        }
    }

    /**
     * A map layer which displays waypoints.
     */
    class MapWaypointsLayer extends msfsSdk.MapLayer {
        constructor() {
            super(...arguments);
            this.waypointsLayerRef = msfsSdk.FSComponent.createRef();
            this.waypointsModule = this.props.model.getModule(msfsSdk.MapSystemKeys.NearestWaypoints);
            this.waypointCache = GarminFacilityWaypointCache.getCache(this.props.bus);
            this.runwayOutlineWaypointCache = MapRunwayOutlineWaypointCache.getCache();
            this.runwayLabelWaypointCache = MapRunwayLabelWaypointCache.getCache();
            this.isAirportVisible = {
                [exports.AirportSize.Large]: false,
                [exports.AirportSize.Medium]: false,
                [exports.AirportSize.Small]: false
            };
            this.isVorVisible = false;
            this.isNdbVisible = false;
            this.isIntersectionVisible = false;
            this.isUserVisible = false;
        }
        /** @inheritdoc */
        onAttached() {
            super.onAttached();
            this.waypointsLayerRef.instance.onAttached();
            this.initVisibilityFlags();
        }
        /** @inheritdoc */
        onMapProjectionChanged(mapProjection, changeFlags) {
            this.waypointsLayerRef.instance.onMapProjectionChanged(mapProjection, changeFlags);
        }
        /** @inheritdoc */
        onUpdated(time, elapsed) {
            this.waypointsLayerRef.instance.onUpdated(time, elapsed);
        }
        /**
         * Initializes waypoint visibility flags and listeners.
         */
        initVisibilityFlags() {
            const updateLargeAirportVisibility = this.updateAirportVisibility.bind(this, exports.AirportSize.Large);
            const updateMediumAirportVisibility = this.updateAirportVisibility.bind(this, exports.AirportSize.Medium);
            const updateSmallAirportVisibility = this.updateAirportVisibility.bind(this, exports.AirportSize.Small);
            this.waypointsModule.airportShow[exports.AirportSize.Large].sub(updateLargeAirportVisibility, true);
            this.waypointsModule.airportShow[exports.AirportSize.Medium].sub(updateMediumAirportVisibility, true);
            this.waypointsModule.airportShow[exports.AirportSize.Small].sub(updateSmallAirportVisibility, true);
            this.waypointsModule.runwayShow.sub(() => {
                updateLargeAirportVisibility();
                updateMediumAirportVisibility();
                updateSmallAirportVisibility();
            });
            this.waypointsModule.vorShow.sub(this.updateVorVisibility.bind(this), true);
            this.waypointsModule.ndbShow.sub(this.updateNdbVisibility.bind(this), true);
            this.waypointsModule.intShow.sub(this.updateIntersectionVisibility.bind(this), true);
            this.waypointsModule.userShow.sub(this.updateUserVisibility.bind(this), true);
        }
        /**
         * Updates airport waypoint visibility.
         * @param size The airport size class to update.
         */
        updateAirportVisibility(size) {
            const wasAnyAirportVisible = this.isAirportVisible[exports.AirportSize.Large]
                || this.isAirportVisible[exports.AirportSize.Medium]
                || this.isAirportVisible[exports.AirportSize.Small];
            this.isAirportVisible[size] = this.waypointsModule.airportShow[size].get() || (this.props.supportRunwayOutlines && this.waypointsModule.runwayShow.get());
            if (!wasAnyAirportVisible && this.isAirportVisible[size]) {
                this.waypointsLayerRef.instance.tryRefreshSearch(msfsSdk.FacilitySearchType.Airport, this.props.mapProjection.getCenter());
            }
        }
        /**
         * Updates VOR waypoint visibility.
         */
        updateVorVisibility() {
            this.isVorVisible = this.waypointsModule.vorShow.get();
            if (this.isVorVisible) {
                this.waypointsLayerRef.instance.tryRefreshSearch(msfsSdk.FacilitySearchType.Vor, this.props.mapProjection.getCenter());
            }
        }
        /**
         * Updates NDB waypoint visibility.
         */
        updateNdbVisibility() {
            this.isNdbVisible = this.waypointsModule.ndbShow.get();
            if (this.isNdbVisible) {
                this.waypointsLayerRef.instance.tryRefreshSearch(msfsSdk.FacilitySearchType.Ndb, this.props.mapProjection.getCenter());
            }
        }
        /**
         * Updates intersection waypoint visibility.
         */
        updateIntersectionVisibility() {
            this.isIntersectionVisible = this.waypointsModule.intShow.get();
            if (this.isIntersectionVisible) {
                this.waypointsLayerRef.instance.tryRefreshSearch(msfsSdk.FacilitySearchType.Intersection, this.props.mapProjection.getCenter());
            }
        }
        /**
         * Updates user waypoint visibility.
         */
        updateUserVisibility() {
            this.isUserVisible = this.waypointsModule.userShow.get();
            if (this.isUserVisible) {
                this.waypointsLayerRef.instance.tryRefreshSearch(msfsSdk.FacilitySearchType.User, this.props.mapProjection.getCenter());
            }
        }
        /** @inheritdoc */
        initWaypointRenderer(renderer, canvasLayer) {
            renderer.setCanvasContext(exports.MapWaypointRenderRole.Normal, canvasLayer.display.context);
            renderer.setVisibilityHandler(exports.MapWaypointRenderRole.Normal, this.isWaypointVisible.bind(this));
        }
        /**
         * Responds to when this layer's facility search sessions have been started.
         * @param airportSession The airport search session.
         * @param vorSession The VOR search session.
         * @param ndbSession The NDB search session.
         * @param intSession The intersection search session.
         */
        onSessionsStarted(airportSession, vorSession, ndbSession, intSession) {
            intSession.setIntersectionFilter(msfsSdk.BitFlags.union(msfsSdk.BitFlags.createFlag(msfsSdk.IntersectionType.None), msfsSdk.BitFlags.createFlag(msfsSdk.IntersectionType.Named), msfsSdk.BitFlags.createFlag(msfsSdk.IntersectionType.Unnamed), msfsSdk.BitFlags.createFlag(msfsSdk.IntersectionType.Offroute), msfsSdk.BitFlags.createFlag(msfsSdk.IntersectionType.IAF), msfsSdk.BitFlags.createFlag(msfsSdk.IntersectionType.FAF), msfsSdk.BitFlags.createFlag(msfsSdk.IntersectionType.RNAV)), true);
        }
        /**
         * Checks whether a waypoint is visible.
         * @param waypoint A waypoint.
         * @returns whether the waypoint is visible.
         */
        isWaypointVisible(waypoint) {
            if (msfsSdk.FacilityWaypointUtils.isFacilityWaypoint(waypoint)) {
                switch (msfsSdk.ICAO.getFacilityType(waypoint.facility.get().icao)) {
                    case msfsSdk.FacilityType.Airport:
                        return this.waypointsModule.airportShow[waypoint.size].get();
                    case msfsSdk.FacilityType.VOR:
                        return this.isVorVisible;
                    case msfsSdk.FacilityType.NDB:
                        return this.isNdbVisible;
                    case msfsSdk.FacilityType.Intersection:
                        return this.isIntersectionVisible;
                    case msfsSdk.FacilityType.USR:
                        return this.isUserVisible;
                }
            }
            else if (waypoint instanceof MapRunwayLabelWaypoint) {
                return this.waypointsModule.runwayLabelShow.get()
                    && msfsSdk.UnitType.METER.convertTo(waypoint.runway.length, msfsSdk.UnitType.GA_RADIAN) / this.props.mapProjection.getProjectedResolution()
                        >= this.waypointsModule.runwayLabelMinLength.get();
            }
            else if (waypoint instanceof MapRunwayOutlineWaypoint) {
                return this.waypointsModule.runwayShow.get();
            }
            return false;
        }
        /** @inheritdoc */
        shouldRefreshSearch(type) {
            switch (type) {
                case msfsSdk.FacilitySearchType.Airport:
                    return this.isAirportVisible[exports.AirportSize.Large] || this.isAirportVisible[exports.AirportSize.Medium] || this.isAirportVisible[exports.AirportSize.Small];
                case msfsSdk.FacilitySearchType.Vor:
                    return this.isVorVisible;
                case msfsSdk.FacilitySearchType.Ndb:
                    return this.isNdbVisible;
                case msfsSdk.FacilitySearchType.Intersection:
                    return this.isIntersectionVisible;
                case msfsSdk.FacilitySearchType.User:
                    return this.isUserVisible;
            }
        }
        /**
         * Registers a waypoint with a renderer.
         * @param waypoint The waypoint to register.
         * @param renderer A waypoint renderer.
         */
        registerWaypoint(waypoint, renderer) {
            renderer.register(waypoint, exports.MapWaypointRenderRole.Normal, 'waypoints-layer');
            if (this.props.supportRunwayOutlines && waypoint instanceof AirportWaypoint) {
                const runwayOutlineWaypoints = this.getRunwayWaypoints(waypoint.facility.get());
                for (let i = 0; i < runwayOutlineWaypoints.length; i++) {
                    renderer.register(runwayOutlineWaypoints[i], exports.MapWaypointRenderRole.Normal, 'waypoints-layer');
                }
            }
        }
        /**
         * Deregisters a waypoint with a renderer.
         * @param waypoint The waypoint to deregister.
         * @param renderer A waypoint renderer.
         */
        deregisterWaypoint(waypoint, renderer) {
            renderer.deregister(waypoint, exports.MapWaypointRenderRole.Normal, 'waypoints-layer');
            if (this.props.supportRunwayOutlines && waypoint instanceof AirportWaypoint) {
                const runwayOutlineWaypoints = this.getRunwayWaypoints(waypoint.facility.get());
                for (let i = 0; i < runwayOutlineWaypoints.length; i++) {
                    renderer.deregister(runwayOutlineWaypoints[i], exports.MapWaypointRenderRole.Normal, 'waypoints-layer');
                }
            }
        }
        /**
         * Gets an array of runway outline and label waypoints from an airport.
         * @param airport An airport.
         * @returns An array of runway outline and label waypoints for the specified airport.
         */
        getRunwayWaypoints(airport) {
            const waypoints = [];
            const runways = airport.runways;
            for (let i = 0; i < runways.length; i++) {
                const runway = runways[i];
                waypoints.push(this.runwayOutlineWaypointCache.get(airport, runway));
                const oneWayRunways = msfsSdk.RunwayUtils.getOneWayRunways(runway, i);
                const primary = oneWayRunways[0];
                const secondary = oneWayRunways[1];
                if (primary) {
                    waypoints.push(this.runwayLabelWaypointCache.get(airport, primary));
                }
                if (secondary) {
                    waypoints.push(this.runwayLabelWaypointCache.get(airport, secondary));
                }
            }
            return waypoints;
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent(msfsSdk.MapNearestWaypointsLayer, { ref: this.waypointsLayerRef, model: this.props.model, mapProjection: this.props.mapProjection, bus: this.props.bus, waypointRenderer: this.props.waypointRenderer, waypointForFacility: (facility) => this.waypointCache.get(facility), initRenderer: this.initWaypointRenderer.bind(this), onSessionsStarted: this.onSessionsStarted.bind(this), registerWaypoint: this.registerWaypoint.bind(this), deregisterWaypoint: this.deregisterWaypoint.bind(this), searchItemLimit: (type) => MapWaypointsLayer.SEARCH_ITEM_LIMITS[type], shouldRefreshSearch: this.shouldRefreshSearch.bind(this) }));
        }
    }
    MapWaypointsLayer.SEARCH_ITEM_LIMITS = {
        [msfsSdk.FacilitySearchType.Airport]: 500,
        [msfsSdk.FacilitySearchType.Vor]: 250,
        [msfsSdk.FacilitySearchType.Ndb]: 250,
        [msfsSdk.FacilitySearchType.Intersection]: 500,
        [msfsSdk.FacilitySearchType.User]: 100
    };

    /**
     * A map layer which displays a readout of wind speed and an arrow depicting wind direction.
     */
    class MapWindVectorLayer extends msfsSdk.MapLayer {
        constructor() {
            super(...arguments);
            this.windVectorModule = this.props.model.getModule(GarminMapKeys.WindVector);
            this.rootDisplay = msfsSdk.Subject.create('');
            this.arrowDisplay = msfsSdk.Subject.create('');
            this.arrowTransform = msfsSdk.CssTransformSubject.create(msfsSdk.CssTransformBuilder.rotate3d('deg'));
            this.speedValue = msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.KNOT.createNumber(0));
            this.show = msfsSdk.MappedSubject.create(([show, isDataFailed]) => show && !isDataFailed, this.windVectorModule.show, this.windVectorModule.isDataFailed).pause();
            this.isAttached = false;
            this.isAwake = true;
            this.isPaused = true;
            this.needUpdateArrow = false;
            this.pauseableSubs = [];
        }
        /** @inheritdoc */
        onVisibilityChanged(isVisible) {
            this.rootDisplay.set(isVisible ? '' : 'none');
            this.updateIsPaused();
        }
        /** @inheritdoc */
        onAttached() {
            this.isAttached = true;
            this.show.resume();
            this.show.sub(this.setVisible.bind(this), true);
            this.pauseableSubs.push(this.windVectorModule.windSpeed.pipe(this.arrowDisplay, (speed, display) => {
                return speed < MapWindVectorLayer.SHOW_ARROW_WIND_SPEED_THRESHOLD - (display === '' ? MapWindVectorLayer.SHOW_ARROW_WIND_SPEED_HYSTERESIS : 0) ? 'none' : '';
            }, true), this.windVectorModule.windDirection.sub(() => { this.needUpdateArrow = true; }, false, true), this.windVectorModule.windSpeed.pipe(this.speedValue, true));
            this.updateIsPaused();
            this.needUpdateArrow = true;
        }
        /** @inheritdoc */
        onMapProjectionChanged(mapProjection, changeFlags) {
            this.needUpdateArrow = msfsSdk.BitFlags.isAny(changeFlags, msfsSdk.MapProjectionChangeType.Rotation);
        }
        /** @inheritdoc */
        onWake() {
            this.isAwake = true;
            this.show.resume();
            this.updateIsPaused();
        }
        /** @inheritdoc */
        onSleep() {
            this.isAwake = false;
            this.show.pause();
            this.updateIsPaused();
        }
        /**
         * Updates whether this layer is paused.
         */
        updateIsPaused() {
            const isPaused = !(this.isAttached && this.isVisible() && this.isAwake);
            if (isPaused !== this.isPaused) {
                this.isPaused = isPaused;
                if (isPaused) {
                    for (const sub of this.pauseableSubs) {
                        sub.pause();
                    }
                }
                else {
                    for (const sub of this.pauseableSubs) {
                        sub.resume(true);
                    }
                }
            }
        }
        /** @inheritdoc */
        onUpdated() {
            if (!this.needUpdateArrow || !this.isVisible()) {
                return;
            }
            this.updateArrow();
            this.needUpdateArrow = false;
        }
        /**
         * Updates the rotation of this layer's arrow.
         */
        updateArrow() {
            const rotation = this.windVectorModule.windDirection.get() + this.props.mapProjection.getRotation() * Avionics.Utils.RAD2DEG;
            this.arrowTransform.transform.set(0, 0, 1, rotation, 0.1);
            this.arrowTransform.resolve();
        }
        /** @inheritdoc */
        render() {
            var _a;
            let cssClass;
            if (typeof this.props.class === 'object') {
                this.cssClassSub = msfsSdk.FSComponent.bindCssClassSet(cssClass = msfsSdk.SetSubject.create(['map-windvector']), this.props.class, ['map-windvector']);
            }
            else {
                cssClass = `map-windvector ${(_a = this.props.class) !== null && _a !== void 0 ? _a : ''}`;
            }
            return (msfsSdk.FSComponent.buildComponent("div", { class: cssClass, style: { 'display': this.rootDisplay } },
                msfsSdk.FSComponent.buildComponent("div", { class: 'map-windvector-arrow-container' },
                    msfsSdk.FSComponent.buildComponent("svg", { viewBox: '-7 -10 14 20', class: 'map-windvector-arrow', style: { 'display': this.arrowDisplay, 'transform': this.arrowTransform, 'overflow': 'visible' } },
                        msfsSdk.FSComponent.buildComponent("path", { d: 'M -2 -10 l 0 11 c -1 0 -2 -1 -5 -1 l 7 10 l 7 -10 c -3 0 -4 1 -5 1 l 0 -11 z', class: 'map-windvector-arrow-outline' }),
                        msfsSdk.FSComponent.buildComponent("path", { d: 'M -2 -10 l 0 11 c -1 0 -2 -1 -5 -1 l 7 10 l 7 -10 c -3 0 -4 1 -5 1 l 0 -11 z', class: 'map-windvector-arrow-stroke' }))),
                msfsSdk.FSComponent.buildComponent(NumberUnitDisplay, { value: this.speedValue, displayUnit: null, formatter: MapWindVectorLayer.FORMATTER, class: 'map-windvector-speed' })));
        }
        /** @inheritdoc */
        destroy() {
            var _a;
            this.show.destroy();
            for (const sub of this.pauseableSubs) {
                sub.destroy();
            }
            (_a = this.cssClassSub) === null || _a === void 0 ? void 0 : _a.destroy();
            super.destroy();
        }
    }
    MapWindVectorLayer.SHOW_ARROW_WIND_SPEED_THRESHOLD = 1; // knots
    MapWindVectorLayer.SHOW_ARROW_WIND_SPEED_HYSTERESIS = 0.5; // knots
    MapWindVectorLayer.FORMATTER = msfsSdk.NumberFormatter.create({ precision: 1 });

    /**
     * A map layer which displays inner and outer range rings for traffic maps.
     */
    class TrafficMapRangeLayer extends msfsSdk.MapLayer {
        constructor() {
            var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z;
            super(...arguments);
            this.canvasLayerRef = msfsSdk.FSComponent.createRef();
            this.tickLayerRef = msfsSdk.FSComponent.createRef();
            this.innerRingLayerRef = msfsSdk.FSComponent.createRef();
            this.outerRingLayerRef = msfsSdk.FSComponent.createRef();
            this.outerStrokeWidth = (_a = this.props.outerStrokeWidth) !== null && _a !== void 0 ? _a : TrafficMapRangeLayer.DEFAULT_STROKE_WIDTH;
            this.outerStrokeStyle = (_b = this.props.outerStrokeStyle) !== null && _b !== void 0 ? _b : TrafficMapRangeLayer.DEFAULT_STROKE_STYLE;
            this.outerStrokeDash = (_c = this.props.outerStrokeDash) !== null && _c !== void 0 ? _c : TrafficMapRangeLayer.DEFAULT_STROKE_DASH;
            this.outerOutlineWidth = (_d = this.props.outerOutlineWidth) !== null && _d !== void 0 ? _d : TrafficMapRangeLayer.DEFAULT_OUTLINE_WIDTH;
            this.outerOutlineStyle = (_e = this.props.outerOutlineStyle) !== null && _e !== void 0 ? _e : TrafficMapRangeLayer.DEFAULT_OUTLINE_STYLE;
            this.outerOutlineDash = (_f = this.props.outerOutlineDash) !== null && _f !== void 0 ? _f : TrafficMapRangeLayer.DEFAULT_OUTLINE_DASH;
            this.innerStrokeWidth = (_g = this.props.innerStrokeWidth) !== null && _g !== void 0 ? _g : TrafficMapRangeLayer.DEFAULT_STROKE_WIDTH;
            this.innerStrokeStyle = (_h = this.props.innerStrokeStyle) !== null && _h !== void 0 ? _h : TrafficMapRangeLayer.DEFAULT_STROKE_STYLE;
            this.innerStrokeDash = (_j = this.props.innerStrokeDash) !== null && _j !== void 0 ? _j : TrafficMapRangeLayer.DEFAULT_STROKE_DASH;
            this.innerOutlineWidth = (_k = this.props.innerOutlineWidth) !== null && _k !== void 0 ? _k : TrafficMapRangeLayer.DEFAULT_OUTLINE_WIDTH;
            this.innerOutlineStyle = (_l = this.props.innerOutlineStyle) !== null && _l !== void 0 ? _l : TrafficMapRangeLayer.DEFAULT_OUTLINE_STYLE;
            this.innerOutlineDash = (_m = this.props.innerOutlineDash) !== null && _m !== void 0 ? _m : TrafficMapRangeLayer.DEFAULT_OUTLINE_DASH;
            this.outerMajorTickColor = (_p = (_o = this.props.outerMajorTickColor) !== null && _o !== void 0 ? _o : this.props.tickColor) !== null && _p !== void 0 ? _p : TrafficMapRangeLayer.DEFAULT_TICK_COLOR;
            this.outerMajorTickSize = (_q = this.props.outerMajorTickSize) !== null && _q !== void 0 ? _q : TrafficMapRangeLayer.DEFAULT_MAJOR_TICK_SIZE;
            this.outerMinorTickColor = (_s = (_r = this.props.outerMinorTickColor) !== null && _r !== void 0 ? _r : this.props.tickColor) !== null && _s !== void 0 ? _s : TrafficMapRangeLayer.DEFAULT_TICK_COLOR;
            this.outerMinorTickSize = (_t = this.props.outerMinorTickSize) !== null && _t !== void 0 ? _t : TrafficMapRangeLayer.DEFAULT_MINOR_TICK_SIZE;
            this.innerMajorTickColor = (_v = (_u = this.props.innerMajorTickColor) !== null && _u !== void 0 ? _u : this.props.tickColor) !== null && _v !== void 0 ? _v : TrafficMapRangeLayer.DEFAULT_TICK_COLOR;
            this.innerMajorTickSize = (_w = this.props.innerMajorTickSize) !== null && _w !== void 0 ? _w : TrafficMapRangeLayer.DEFAULT_MAJOR_TICK_SIZE;
            this.innerMinorTickColor = (_y = (_x = this.props.innerMinorTickColor) !== null && _x !== void 0 ? _x : this.props.tickColor) !== null && _y !== void 0 ? _y : TrafficMapRangeLayer.DEFAULT_TICK_COLOR;
            this.innerMinorTickSize = (_z = this.props.innerMinorTickSize) !== null && _z !== void 0 ? _z : TrafficMapRangeLayer.DEFAULT_MINOR_TICK_SIZE;
            this.rangeModule = this.props.model.getModule(GarminMapKeys.Range);
            this.trafficModule = this.props.model.getModule(GarminMapKeys.Traffic);
            this.innerRange = msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.NMILE.createNumber(0));
            this.outerRange = msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.NMILE.createNumber(0));
            this.innerRadius = 0;
            this.outerRadius = 0;
            this.innerLabel = null;
            this.outerLabel = null;
            this.needUpdateRings = false;
            this.needUpdateTicks = false;
        }
        /** @inheritdoc */
        onAttached() {
            this.canvasLayerRef.instance.onAttached();
            this.initLabels();
            this.initStyles();
            this.initModuleListeners();
            this.innerRange.sub(() => { this.needUpdateRings = true; });
            this.outerRange.sub(() => { this.needUpdateRings = true; });
            this.needUpdateRings = true;
        }
        /**
         * Initializes the range display labels.
         */
        initLabels() {
            const displayUnit = msfsSdk.Subject.create(msfsSdk.UnitType.NMILE);
            if (this.props.innerLabelRadial !== null && this.props.innerLabelRadial !== undefined) {
                this.innerLabel = this.innerRingLayerRef.instance.createLabel(this.props.renderLabel !== undefined
                    ? this.props.renderLabel(this.innerRange, displayUnit)
                    : (msfsSdk.FSComponent.buildComponent(MapRangeDisplay, { range: this.innerRange, displayUnit: displayUnit })));
                this.innerLabel.setAnchor(new Float64Array([0.5, 0.5]));
                this.innerLabel.setRadialAngle(this.props.innerLabelRadial * Avionics.Utils.DEG2RAD);
            }
            if (this.props.outerLabelRadial !== null && this.props.outerLabelRadial !== undefined) {
                this.outerLabel = this.outerRingLayerRef.instance.createLabel(this.props.renderLabel !== undefined
                    ? this.props.renderLabel(this.outerRange, displayUnit)
                    : (msfsSdk.FSComponent.buildComponent(MapRangeDisplay, { range: this.outerRange, displayUnit: displayUnit })));
                this.outerLabel.setAnchor(new Float64Array([0.5, 0.5]));
                this.outerLabel.setRadialAngle(this.props.outerLabelRadial * Avionics.Utils.DEG2RAD);
            }
        }
        /**
         * Initializes ring styles.
         */
        initStyles() {
            this.innerRingLayerRef.instance.setRingStrokeStyles(this.innerStrokeWidth, this.innerStrokeStyle, this.innerStrokeDash);
            this.innerRingLayerRef.instance.setRingOutlineStyles(this.innerOutlineWidth, this.innerOutlineStyle, this.innerOutlineDash);
            this.outerRingLayerRef.instance.setRingStrokeStyles(this.outerStrokeWidth, this.outerStrokeStyle, this.outerStrokeDash);
            this.outerRingLayerRef.instance.setRingOutlineStyles(this.outerOutlineWidth, this.outerOutlineStyle, this.outerOutlineDash);
        }
        /**
         * Initializes modules listeners.
         */
        initModuleListeners() {
            const innerRangeCallback = this.updateInnerRange.bind(this);
            const outerRangeCallback = this.updateOuterRange.bind(this);
            this.rangeModule.nominalRanges.sub(innerRangeCallback);
            this.rangeModule.nominalRanges.sub(outerRangeCallback);
            this.trafficModule.innerRangeIndex.sub(innerRangeCallback, true);
            this.trafficModule.outerRangeIndex.sub(outerRangeCallback, true);
        }
        /** @inheritdoc */
        onMapProjectionChanged(mapProjection, changeFlags) {
            this.canvasLayerRef.instance.onMapProjectionChanged(mapProjection, changeFlags);
            this.needUpdateRings || (this.needUpdateRings = msfsSdk.BitFlags.isAny(changeFlags, msfsSdk.MapProjectionChangeType.TargetProjected | msfsSdk.MapProjectionChangeType.ProjectedResolution));
        }
        /** @inheritdoc */
        onUpdated(time, elapsed) {
            if (this.needUpdateRings) {
                this.updateRings();
                this.needUpdateRings = false;
            }
            this.canvasLayerRef.instance.onUpdated(time, elapsed);
        }
        /**
         * Updates the rings.
         */
        updateRings() {
            const center = this.props.mapProjection.getTargetProjected();
            const innerRadius = this.innerRange.get().asUnit(msfsSdk.UnitType.GA_RADIAN) / this.props.mapProjection.getProjectedResolution();
            const outerRadius = this.outerRange.get().asUnit(msfsSdk.UnitType.GA_RADIAN) / this.props.mapProjection.getProjectedResolution();
            if (innerRadius > 0) {
                this.innerRingLayerRef.instance.setVisible(true);
                this.innerRingLayerRef.instance.setRingPosition(center, innerRadius);
            }
            else {
                this.innerRingLayerRef.instance.setVisible(false);
            }
            if (outerRadius > 0) {
                this.outerRingLayerRef.instance.setVisible(true);
                this.outerRingLayerRef.instance.setRingPosition(center, outerRadius);
            }
            else {
                this.outerRingLayerRef.instance.setVisible(false);
            }
            this.innerRadius = innerRadius;
            this.outerRadius = outerRadius;
            this.needUpdateTicks = true;
        }
        /**
         * Updates this layer's ring tick marks.
         * @param context A canvas 2D rendering context to which to render the ticks.
         */
        updateTicks(context) {
            const center = this.props.mapProjection.getTargetProjected();
            if (this.innerRadius > 0) {
                this.drawTicks(context, center, this.innerRadius, this.innerMajorTickColor, this.innerMajorTickSize, this.innerMinorTickColor, this.innerMinorTickSize);
            }
            if (this.outerRadius > 0) {
                this.drawTicks(context, center, this.outerRadius, this.outerMajorTickColor, this.outerMajorTickSize, this.outerMinorTickColor, this.outerMinorTickSize);
            }
        }
        /**
         * Draws this layer's ring tick marks to a canvas. One major tick is drawn at each of the four cardinal positions,
         * and one minor tick is drawn at each of the eight remaining hour positions.
         * @param context A canvas 2D rendering context.
         * @param center The projected center of the outer ring.
         * @param radius The radius of the ring, in pixels.
         * @param majorTickColor The color of each major tick.
         * @param majorTickSize The size of each major tick, in pixels.
         * @param minorTickColor The color of each minor tick.
         * @param minorTickSize The size of each minor tick, in pixels.
         */
        drawTicks(context, center, radius, majorTickColor, majorTickSize, minorTickColor, minorTickSize) {
            // Minor ticks
            context.fillStyle = minorTickColor;
            for (let i = 0; i < 12; i++) {
                if (i % 3 === 0) {
                    continue;
                }
                const pos = msfsSdk.Vec2Math.setFromPolar(radius, i * Math.PI / 6, TrafficMapRangeLayer.vec2Cache[0]);
                this.drawTick(context, center[0] + pos[0], center[1] + pos[1], minorTickSize);
            }
            // Major ticks
            context.fillStyle = majorTickColor;
            for (let i = 0; i < 4; i++) {
                const pos = msfsSdk.Vec2Math.setFromPolar(radius, i * msfsSdk.MathUtils.HALF_PI, TrafficMapRangeLayer.vec2Cache[0]);
                this.drawTick(context, center[0] + pos[0], center[1] + pos[1], majorTickSize);
            }
        }
        /**
         * Draws a ring tick to a canvas.
         * @param context A canvas 2D rendering context.
         * @param x The x-coordinate of the center of the tick.
         * @param y The y-coordinate of the center of the tick.
         * @param size The size of the tick, in pixels.
         */
        drawTick(context, x, y, size) {
            context.fillRect(x - size / 2, y - size / 2, size, size);
        }
        /**
         * Updates the inner ring range.
         */
        updateInnerRange() {
            const range = this.rangeModule.nominalRanges.get()[this.trafficModule.innerRangeIndex.get()];
            this.innerRange.set(range !== null && range !== void 0 ? range : 0);
        }
        /**
         * Updates the outer ring range.
         */
        updateOuterRange() {
            const range = this.rangeModule.nominalRanges.get()[this.trafficModule.outerRangeIndex.get()];
            this.outerRange.set(range !== null && range !== void 0 ? range : 0);
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent(msfsSdk.MapSharedCanvasLayer, { ref: this.canvasLayerRef, model: this.props.model, mapProjection: this.props.mapProjection },
                msfsSdk.FSComponent.buildComponent(msfsSdk.MapLabeledRingCanvasSubLayer, { ref: this.innerRingLayerRef, model: this.props.model }),
                msfsSdk.FSComponent.buildComponent(msfsSdk.MapLabeledRingCanvasSubLayer, { ref: this.outerRingLayerRef, model: this.props.model }),
                msfsSdk.FSComponent.buildComponent(msfsSdk.GenericMapSharedCanvasSubLayer, { ref: this.tickLayerRef, model: this.props.model, shouldInvalidate: () => this.needUpdateTicks, onUpdated: (projection, display) => {
                        if (display.isInvalidated) {
                            this.needUpdateTicks = false;
                            this.updateTicks(display.context);
                        }
                    } })));
        }
        /** @inheritdoc */
        destroy() {
            var _a;
            (_a = this.canvasLayerRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
            super.destroy();
        }
    }
    TrafficMapRangeLayer.DEFAULT_STROKE_WIDTH = 2;
    TrafficMapRangeLayer.DEFAULT_STROKE_STYLE = 'white';
    TrafficMapRangeLayer.DEFAULT_STROKE_DASH = [4, 4];
    TrafficMapRangeLayer.DEFAULT_OUTLINE_WIDTH = 0;
    TrafficMapRangeLayer.DEFAULT_OUTLINE_STYLE = 'black';
    TrafficMapRangeLayer.DEFAULT_OUTLINE_DASH = [];
    TrafficMapRangeLayer.DEFAULT_TICK_COLOR = 'white';
    TrafficMapRangeLayer.DEFAULT_MAJOR_TICK_SIZE = 10;
    TrafficMapRangeLayer.DEFAULT_MINOR_TICK_SIZE = 5;
    TrafficMapRangeLayer.vec2Cache = [new Float64Array(2)];

    var AirspaceRenderType;
    (function (AirspaceRenderType) {
        AirspaceRenderType[AirspaceRenderType["BlueSingle"] = 0] = "BlueSingle";
        AirspaceRenderType[AirspaceRenderType["MaroonSingle"] = 1] = "MaroonSingle";
        AirspaceRenderType[AirspaceRenderType["BlueDashed"] = 2] = "BlueDashed";
        AirspaceRenderType[AirspaceRenderType["BlueCombed"] = 3] = "BlueCombed";
        AirspaceRenderType[AirspaceRenderType["MaroonCombed"] = 4] = "MaroonCombed";
        AirspaceRenderType[AirspaceRenderType["Null"] = 5] = "Null";
    })(AirspaceRenderType || (AirspaceRenderType = {}));
    /**
     * Renders airspace boundaries with a comb-like pattern.
     */
    class CombedAirspaceRenderer extends msfsSdk.MapMultiLineAirspaceRenderer {
        /**
         * Constructor.
         * @param color The color of the rendered airspace.
         * @param baseLineWidth The stroke width of the base line that is drawn on the airspace boundary.
         * @param isTeethOutside Whether the teeth should appear on the outside of the boundary.
         * @param teethWidth The width of the teeth.
         * @param teethDash The dash of the teeth.
         */
        constructor(color, baseLineWidth, isTeethOutside, teethWidth, teethDash) {
            super();
            this.color = color;
            this.baseLineWidth = baseLineWidth;
            this.isTeethOutside = isTeethOutside;
            this.teethWidth = teethWidth;
            this.teethDash = teethDash;
        }
        /** @inheritdoc */
        renderLines(shape, context, stream) {
            // render base line
            shape.renderLine(context, 0, this.baseLineWidth, this.color, CombedAirspaceRenderer.emptyDash, stream);
            // render teeth
            shape.renderLine(context, this.teethWidth / 2 * (this.isTeethOutside ? 1 : -1), this.teethWidth, this.color, this.teethDash, stream);
        }
    }
    CombedAirspaceRenderer.emptyDash = [];
    /**
     * Utility class containing functions defining the rendering behavior of airspaces for Garmin maps.
     */
    class MapAirspaceRendering {
        /**
         * Determines the rendering order of airspaces for Garmin maps.
         * @returns The relative rendering order of two airspaces for Garmin maps.
         */
        static renderOrder() {
            return 0;
        }
        /**
         * Selects airspace renderers for Garmin maps.
         * @param airspace The airspace to render.
         * @returns The renderer to use to render the specified airspace.
         */
        static selectRenderer(airspace) {
            switch (airspace.facility.type) {
                case msfsSdk.BoundaryType.ClassB:
                    return MapAirspaceRendering.RENDERERS[AirspaceRenderType.BlueSingle];
                case msfsSdk.BoundaryType.ClassC:
                case msfsSdk.BoundaryType.ClassE:
                    return MapAirspaceRendering.RENDERERS[AirspaceRenderType.MaroonSingle];
                case msfsSdk.BoundaryType.ClassD:
                    return MapAirspaceRendering.RENDERERS[AirspaceRenderType.BlueDashed];
                case msfsSdk.BoundaryType.Restricted:
                case msfsSdk.BoundaryType.Prohibited:
                case msfsSdk.BoundaryType.Warning:
                case msfsSdk.BoundaryType.Danger:
                case msfsSdk.BoundaryType.Training:
                    return MapAirspaceRendering.RENDERERS[AirspaceRenderType.BlueCombed];
                case msfsSdk.BoundaryType.MOA:
                case msfsSdk.BoundaryType.Alert:
                    return MapAirspaceRendering.RENDERERS[AirspaceRenderType.MaroonCombed];
                default:
                    return MapAirspaceRendering.RENDERERS[AirspaceRenderType.Null];
            }
        }
    }
    MapAirspaceRendering.RENDERERS = {
        [AirspaceRenderType.BlueSingle]: new msfsSdk.MapSingleLineAirspaceRenderer(1.5, '#3080ff', []),
        [AirspaceRenderType.MaroonSingle]: new msfsSdk.MapSingleLineAirspaceRenderer(1.5, '#4a0045', []),
        [AirspaceRenderType.BlueDashed]: new msfsSdk.MapSingleLineAirspaceRenderer(1.5, '#3080ff', [5, 5]),
        [AirspaceRenderType.BlueCombed]: new CombedAirspaceRenderer('#3080ff', 1.5, false, 6, [1.5, 2.5]),
        [AirspaceRenderType.MaroonCombed]: new CombedAirspaceRenderer('#4a0045', 1.5, false, 6, [1.5, 2.5]),
        [AirspaceRenderType.Null]: new msfsSdk.NullAirspaceRenderer(),
    };

    /**
     * Types of Garmin traffic systems.
     */
    exports.TrafficSystemType = void 0;
    (function (TrafficSystemType) {
        TrafficSystemType["Tis"] = "TIS";
        TrafficSystemType["Tas"] = "TAS";
        TrafficSystemType["TcasII"] = "TCAS-II";
    })(exports.TrafficSystemType || (exports.TrafficSystemType = {}));

    /// <reference types="@microsoft/msfs-types/js/avionics" />
    /**
     * A view representation of a TCAS intruder for MapTrafficIntruderLayer.
     */
    class MapTrafficIntruderIcon extends msfsSdk.AbstractMapTrafficIntruderIcon {
        /**
         * Constructor.
         * @param intruder This view's associated intruder.
         * @param trafficModule The traffic module for this icon's parent map.
         * @param ownshipModule The own airplane properties module for this icon's parent map.
         * @param garminTrafficModule The Garmin traffic module for this icon's parent map.
         * @param followAirplaneModule The follow airplane module for this icon's parent map.
         * @param options Configuration options for this icon.
         */
        constructor(intruder, trafficModule, ownshipModule, garminTrafficModule, followAirplaneModule, options) {
            super(intruder, trafficModule, ownshipModule);
            this.garminTrafficModule = garminTrafficModule;
            this.followAirplaneModule = followAirplaneModule;
            this.options = options;
            this.supportTisVector = this.garminTrafficModule.trafficSystem.type === exports.TrafficSystemType.Tis;
            this.altitudeText = '';
        }
        /** @inheritdoc */
        drawIcon(projection, context, projectedPos, isOffScale) {
            const alertLevel = this.intruder.alertLevel.get();
            const isTARA = alertLevel === msfsSdk.TcasAlertLevel.TrafficAdvisory || alertLevel === msfsSdk.TcasAlertLevel.ResolutionAdvisory;
            if (isOffScale && (!this.options.drawOffScale || !isTARA)) {
                return;
            }
            if (!isTARA
                && (this.intruder.relativePositionVec[2] > this.trafficModule.altitudeRestrictionAbove.get().asUnit(msfsSdk.UnitType.METER)
                    || this.intruder.relativePositionVec[2] < -this.trafficModule.altitudeRestrictionBelow.get().asUnit(msfsSdk.UnitType.METER))) {
                return;
            }
            context.translate(projectedPos[0], projectedPos[1]);
            if (this.garminTrafficModule.showIntruderLabel.get()) {
                this.drawIconVSArrow(context, alertLevel);
                this.drawIconAltitudeLabel(context, alertLevel);
            }
            const isAdsbActive = this.garminTrafficModule.adsbOperatingMode.get() !== msfsSdk.AdsbOperatingMode.Standby;
            if (this.options.supportAdsbVector && isAdsbActive) {
                this.drawAdsbMotionVector(context, projection);
            }
            else if (this.supportTisVector) {
                this.drawTisMotionVector(context, projection);
            }
            if (isAdsbActive && !this.options.forceDrawNoArrow) {
                this.drawArrowIcon(context, projection, projectedPos, isOffScale, alertLevel);
            }
            else {
                this.drawNoArrowIcon(context, projection, projectedPos, isOffScale, alertLevel);
            }
            context.resetTransform();
        }
        /**
         * Draws the icon without a directional arrow display.
         * @param context The canvas rendering context to which to draw the icon.
         * @param projection The map projection.
         * @param projectedPos The projected position of the intruder.
         * @param isOffScale Whether the intruder is off-scale.
         * @param alertLevel The alert level assigned to this view's intruder.
         */
        drawNoArrowIcon(context, projection, projectedPos, isOffScale, alertLevel) {
            context.setLineDash(MapTrafficIntruderIcon.EMPTY_LINE_DASH);
            switch (alertLevel) {
                case msfsSdk.TcasAlertLevel.None:
                case msfsSdk.TcasAlertLevel.ProximityAdvisory:
                    this.drawDiamondIcon(context, alertLevel === msfsSdk.TcasAlertLevel.ProximityAdvisory);
                    break;
                case msfsSdk.TcasAlertLevel.TrafficAdvisory:
                    this.drawCircleIcon(context, projection, projectedPos, isOffScale);
                    break;
                case msfsSdk.TcasAlertLevel.ResolutionAdvisory:
                    this.drawSquareIcon(context, projection, projectedPos, isOffScale);
                    break;
            }
        }
        /**
         * Draws a diamond icon.
         * @param context The canvas rendering context to which to draw the icon.
         * @param isFilled Whether the diamond should be filled.
         */
        drawDiamondIcon(context, isFilled) {
            const size = 0.35 * this.options.iconSize;
            context.beginPath();
            context.moveTo(0, -size);
            context.lineTo(size, 0);
            context.lineTo(0, size);
            context.lineTo(-size, 0);
            context.closePath();
            context.strokeStyle = 'black';
            context.lineWidth = Math.max(1, 0.05 * this.options.iconSize);
            context.fillStyle = 'white';
            context.fill();
            context.stroke();
            if (!isFilled) {
                context.beginPath();
                context.moveTo(0, -size * 0.6);
                context.lineTo(size * 0.6, 0);
                context.lineTo(0, size * 0.6);
                context.lineTo(-size * 0.6, 0);
                context.closePath();
                context.fillStyle = 'black';
                context.fill();
            }
        }
        /**
         * Draws a circle icon for TAs.
         * @param context The canvas rendering context to which to draw the icon.
         * @param projection The map projection.
         * @param projectedPos The projected position of the intruder.
         * @param isOffScale Whether the intruder is off-scale.
         */
        drawCircleIcon(context, projection, projectedPos, isOffScale) {
            context.beginPath();
            context.arc(0, 0, 0.35 * this.options.iconSize, 0, msfsSdk.MathUtils.TWO_PI);
            context.strokeStyle = 'black';
            context.lineWidth = Math.max(1, 0.05 * this.options.iconSize);
            context.fillStyle = MapTrafficIntruderIcon.TA_COLOR;
            context.fill();
            context.stroke();
            if (isOffScale) {
                const projectedAngle = msfsSdk.Vec2Math.theta(msfsSdk.Vec2Math.sub(projectedPos, projection.getTargetProjected(), MapTrafficIntruderIcon.vec2Cache[0]));
                context.beginPath();
                context.arc(0, 0, 0.35 * this.options.iconSize, projectedAngle - msfsSdk.MathUtils.HALF_PI, projectedAngle + msfsSdk.MathUtils.HALF_PI);
                context.closePath();
                context.fillStyle = MapTrafficIntruderIcon.TA_OFFSCALE_COLOR;
                context.fill();
                context.stroke();
            }
        }
        /**
         * Draws a square icon for RAs.
         * @param context The canvas rendering context to which to draw the icon.
         * @param projection The map projection.
         * @param projectedPos The projected position of the intruder.
         * @param isOffScale Whether the intruder is off-scale.
         */
        drawSquareIcon(context, projection, projectedPos, isOffScale) {
            const size = 0.35 * this.options.iconSize;
            context.beginPath();
            context.moveTo(-size, -size);
            context.lineTo(size, -size);
            context.lineTo(size, size);
            context.lineTo(-size, size);
            context.closePath();
            context.strokeStyle = 'black';
            context.lineWidth = Math.max(1, 0.05 * this.options.iconSize);
            context.fillStyle = MapTrafficIntruderIcon.RA_COLOR;
            context.fill();
            context.stroke();
            if (isOffScale) {
                context.save();
                context.clip();
                const projectedAngle = msfsSdk.Vec2Math.theta(msfsSdk.Vec2Math.sub(projectedPos, projection.getTargetProjected(), MapTrafficIntruderIcon.vec2Cache[0]));
                context.beginPath();
                context.arc(0, 0, this.options.iconSize, projectedAngle - msfsSdk.MathUtils.HALF_PI, projectedAngle + msfsSdk.MathUtils.HALF_PI);
                context.closePath();
                context.fillStyle = MapTrafficIntruderIcon.RA_OFFSCALE_COLOR;
                context.fill();
                context.stroke();
                context.restore();
            }
        }
        /**
         * Draws the icon with a directional arrow display.
         * @param context The canvas rendering context to which to draw the icon.
         * @param projection The map projection.
         * @param projectedPos The projected position of the intruder.
         * @param isOffScale Whether the intruder is off-scale.
         * @param alertLevel The alert level assigned to this view's intruder.
         */
        drawArrowIcon(context, projection, projectedPos, isOffScale, alertLevel) {
            context.setLineDash(MapTrafficIntruderIcon.EMPTY_LINE_DASH);
            this.drawArrowIconBackground(context, projection, projectedPos, isOffScale, alertLevel);
            this.drawIconArrow(context, projection, alertLevel);
        }
        /**
         * Draws the icon's background when it has a directional arrow display.
         * @param context The canvas rendering context to which to draw the icon.
         * @param projection The map projection.
         * @param projectedPos The projected position of the intruder.
         * @param isOffScale Whether the intruder is off-scale.
         * @param alertLevel The alert level assigned to this view's intruder.
         */
        drawArrowIconBackground(context, projection, projectedPos, isOffScale, alertLevel) {
            if (alertLevel === msfsSdk.TcasAlertLevel.None || alertLevel === msfsSdk.TcasAlertLevel.ProximityAdvisory) {
                return;
            }
            context.strokeStyle = '#1a1d21';
            context.lineWidth = Math.max(1, 0.05 * this.options.iconSize);
            context.fillStyle = alertLevel === msfsSdk.TcasAlertLevel.ResolutionAdvisory ? MapTrafficIntruderIcon.RA_COLOR : MapTrafficIntruderIcon.TA_COLOR;
            context.beginPath();
            context.arc(0, 0, 0.45 * this.options.iconSize, 0, msfsSdk.MathUtils.TWO_PI);
            context.fill();
            context.stroke();
            if (isOffScale) {
                const projectedAngle = msfsSdk.Vec2Math.theta(msfsSdk.Vec2Math.sub(projectedPos, projection.getTargetProjected(), MapTrafficIntruderIcon.vec2Cache[0]));
                context.beginPath();
                context.arc(0, 0, 0.45 * this.options.iconSize, projectedAngle - msfsSdk.MathUtils.HALF_PI, projectedAngle + msfsSdk.MathUtils.HALF_PI);
                context.fillStyle = 'black';
                context.fill();
            }
        }
        /**
         * Draws the icon's directional arrow.
         * @param context The canvas rendering context to which to draw the icon.
         * @param projection The map projection.
         * @param alertLevel The alert level assigned to this view's intruder.
         */
        drawIconArrow(context, projection, alertLevel) {
            context.save();
            context.rotate(this.intruder.groundTrack * Avionics.Utils.DEG2RAD + projection.getRotation());
            this.drawIconArrowBackground(context, alertLevel);
            this.drawIconArrowOutline(context, alertLevel);
            context.restore();
        }
        /**
         * Draws the icon's directional arrow background.
         * @param context The canvas rendering context to which to draw the icon.
         * @param alertLevel The alert level assigned to this view's intruder.
         */
        drawIconArrowBackground(context, alertLevel) {
            switch (alertLevel) {
                case msfsSdk.TcasAlertLevel.None:
                case msfsSdk.TcasAlertLevel.ProximityAdvisory:
                    context.fillStyle = 'black';
                    break;
                case msfsSdk.TcasAlertLevel.TrafficAdvisory:
                    context.fillStyle = MapTrafficIntruderIcon.TA_COLOR;
                    break;
                case msfsSdk.TcasAlertLevel.ResolutionAdvisory:
                    context.fillStyle = MapTrafficIntruderIcon.RA_COLOR;
                    break;
            }
            context.beginPath();
            context.moveTo(0, -0.3 * this.options.iconSize * 1.4);
            context.lineTo(0.212 * this.options.iconSize * 1.4, 0.212 * this.options.iconSize * 1.4);
            context.lineTo(0, 0.1 * this.options.iconSize * 1.4);
            context.lineTo(-0.212 * this.options.iconSize * 1.4, 0.212 * this.options.iconSize * 1.4);
            context.closePath();
            context.fill();
        }
        /**
         * Draws the icon's directional arrow outline.
         * @param context The canvas rendering context to which to draw the icon.
         * @param alertLevel The alert level assigned to this view's intruder.
         */
        drawIconArrowOutline(context, alertLevel) {
            context.lineWidth = Math.max(1, this.options.iconSize * 0.05);
            switch (alertLevel) {
                case msfsSdk.TcasAlertLevel.None:
                    context.strokeStyle = 'white';
                    context.fillStyle = 'black';
                    break;
                case msfsSdk.TcasAlertLevel.ProximityAdvisory:
                    context.strokeStyle = 'transparent';
                    context.fillStyle = 'white';
                    break;
                case msfsSdk.TcasAlertLevel.TrafficAdvisory:
                    context.strokeStyle = 'black';
                    context.fillStyle = MapTrafficIntruderIcon.TA_COLOR;
                    break;
                case msfsSdk.TcasAlertLevel.ResolutionAdvisory:
                    context.strokeStyle = 'black';
                    context.fillStyle = MapTrafficIntruderIcon.RA_COLOR;
                    break;
            }
            context.beginPath();
            context.moveTo(0, -0.3 * this.options.iconSize);
            context.lineTo(0.212 * this.options.iconSize, 0.212 * this.options.iconSize);
            context.lineTo(0, 0.1 * this.options.iconSize);
            context.lineTo(-0.212 * this.options.iconSize, 0.212 * this.options.iconSize);
            context.closePath();
            context.fill();
            context.stroke();
        }
        /**
         * Draws the icon's vertical speed indicator arrow.
         * @param context The canvas rendering context to which to draw the icon.
         * @param alertLevel The alert level assigned to this view's intruder.
         */
        drawIconVSArrow(context, alertLevel) {
            const showArrow = MapTrafficIntruderIcon.VERTICAL_SPEED_THRESHOLD.compare(Math.abs(this.intruder.velocityVec[2]), msfsSdk.UnitType.MPS) <= 0;
            if (!showArrow) {
                return;
            }
            const vsSign = Math.sign(this.intruder.velocityVec[2]);
            context.beginPath();
            context.moveTo(0.67 * this.options.iconSize, -0.16 * this.options.iconSize * vsSign);
            context.lineTo(0.67 * this.options.iconSize, 0.16 * this.options.iconSize * vsSign);
            context.moveTo(0.55 * this.options.iconSize, -0.04 * this.options.iconSize * vsSign);
            context.lineTo(0.67 * this.options.iconSize, -0.18 * this.options.iconSize * vsSign);
            context.lineTo(0.79 * this.options.iconSize, -0.04 * this.options.iconSize * vsSign);
            context.lineWidth = Math.max(1, this.options.iconSize * 0.125);
            context.strokeStyle = 'black';
            context.stroke();
            context.lineWidth = Math.max(1, this.options.iconSize * 0.075);
            switch (alertLevel) {
                case msfsSdk.TcasAlertLevel.None:
                case msfsSdk.TcasAlertLevel.ProximityAdvisory:
                    context.strokeStyle = 'white';
                    break;
                case msfsSdk.TcasAlertLevel.TrafficAdvisory:
                    context.strokeStyle = MapTrafficIntruderIcon.TA_COLOR;
                    break;
                case msfsSdk.TcasAlertLevel.ResolutionAdvisory:
                    context.strokeStyle = MapTrafficIntruderIcon.RA_COLOR;
                    break;
            }
            context.stroke();
        }
        /**
         * Draws the icon's altitude label.
         * @param context The canvas rendering context to which to draw the icon.
         * @param alertLevel The alert level assigned to this view's intruder.
         */
        drawIconAltitudeLabel(context, alertLevel) {
            const isRelative = this.trafficModule.isAltitudeRelative.get();
            const isAltitudeAbove = this.intruder.relativePositionVec[2] >= 0;
            const altitudeFeet = this.trafficModule.isAltitudeRelative.get()
                ? msfsSdk.UnitType.METER.convertTo(this.intruder.relativePositionVec[2], msfsSdk.UnitType.FOOT)
                : this.intruder.altitude.asUnit(msfsSdk.UnitType.FOOT);
            const altitudeRounded = Math.round(altitudeFeet / 100);
            const altitudeAbs = Math.abs(altitudeRounded);
            const prefix = altitudeRounded < 0 ? '−'
                : isRelative ? '+' : '';
            if (altitudeAbs !== this.lastDrawnAltitudeValue || prefix !== this.lastDrawnAltitudePrefix) {
                this.lastDrawnAltitudeValue = altitudeAbs;
                this.lastDrawnAltitudePrefix = prefix;
                this.altitudeText = `${prefix}${altitudeAbs}`;
            }
            const textWidth = context.measureText(this.altitudeText).width;
            const textHeight = this.options.fontSize;
            // draw background
            context.fillStyle = 'black';
            if (isAltitudeAbove) {
                context.fillRect(-textWidth / 2 - 2, -0.5 * this.options.iconSize - textHeight - 2, textWidth + 4, textHeight + 2);
            }
            else {
                context.fillRect(-textWidth / 2 - 2, 0.5 * this.options.iconSize, textWidth + 4, textHeight + 2);
            }
            // draw text
            switch (alertLevel) {
                case msfsSdk.TcasAlertLevel.None:
                case msfsSdk.TcasAlertLevel.ProximityAdvisory:
                    context.fillStyle = 'white';
                    break;
                case msfsSdk.TcasAlertLevel.TrafficAdvisory:
                    context.fillStyle = MapTrafficIntruderIcon.TA_COLOR;
                    break;
                case msfsSdk.TcasAlertLevel.ResolutionAdvisory:
                    context.fillStyle = MapTrafficIntruderIcon.RA_COLOR;
                    break;
            }
            if (isAltitudeAbove) {
                context.textBaseline = 'bottom';
                context.fillText(this.altitudeText, 0, -0.5 * this.options.iconSize);
            }
            else {
                context.textBaseline = 'top';
                context.fillText(this.altitudeText, 0, 0.5 * this.options.iconSize);
            }
        }
        /**
         * Draws a TIS motion vector for this icon.
         * @param context The canvas rendering context to which to draw the vector.
         * @param projection The map projection.
         */
        drawTisMotionVector(context, projection) {
            let color;
            const alertLevel = this.intruder.alertLevel.get();
            switch (alertLevel) {
                case msfsSdk.TcasAlertLevel.None:
                case msfsSdk.TcasAlertLevel.ProximityAdvisory:
                    color = MapTrafficIntruderIcon.VECTOR_ABS_COLOR;
                    break;
                case msfsSdk.TcasAlertLevel.TrafficAdvisory:
                    color = MapTrafficIntruderIcon.TA_COLOR;
                    break;
                case msfsSdk.TcasAlertLevel.ResolutionAdvisory:
                    color = MapTrafficIntruderIcon.RA_COLOR;
                    break;
            }
            const vector = this.intruder.velocityVec;
            const vectorDir = msfsSdk.Vec2Math.theta(vector);
            const vectorMag = this.options.vectorLength !== undefined ? this.options.vectorLength : msfsSdk.Vec2Math.abs(vector);
            const roundedVector = msfsSdk.Vec2Math.setFromPolar(vectorMag, Math.round(vectorDir / MapTrafficIntruderIcon.TIS_VECTOR_DIR_PRECISION) * MapTrafficIntruderIcon.TIS_VECTOR_DIR_PRECISION, MapTrafficIntruderIcon.vec2Cache[0]);
            this.drawLookaheadVector(projection, context, color, roundedVector, MapTrafficIntruderIcon.TIS_VECTOR_LOOKAHEAD);
        }
        /**
         * Draws an ADS-B motion vector for this icon.
         * @param context The canvas rendering context to which to draw the vector.
         * @param projection The map projection.
         */
        drawAdsbMotionVector(context, projection) {
            const vectorMode = this.garminTrafficModule.motionVectorMode.get();
            if (vectorMode === exports.MapTrafficMotionVectorMode.Off) {
                return;
            }
            const vector = vectorMode === exports.MapTrafficMotionVectorMode.Absolute || !this.followAirplaneModule.isFollowing.get()
                ? this.intruder.velocityVec
                : this.intruder.relativeVelocityVec;
            const alertLevel = this.intruder.alertLevel.get();
            if ((alertLevel === msfsSdk.TcasAlertLevel.None || alertLevel === msfsSdk.TcasAlertLevel.ProximityAdvisory) || this.options.drawTARAVectorAsNormalVector) {
                let color;
                if (alertLevel === msfsSdk.TcasAlertLevel.ResolutionAdvisory) {
                    color = MapTrafficIntruderIcon.RA_COLOR;
                }
                else if (alertLevel === msfsSdk.TcasAlertLevel.TrafficAdvisory) {
                    color = MapTrafficIntruderIcon.TA_COLOR;
                }
                else {
                    color = vectorMode === exports.MapTrafficMotionVectorMode.Absolute
                        ? MapTrafficIntruderIcon.VECTOR_ABS_COLOR
                        : MapTrafficIntruderIcon.VECTOR_REL_COLOR;
                }
                this.drawLookaheadVector(projection, context, color, vector, this.garminTrafficModule.motionVectorLookahead.get().asUnit(msfsSdk.UnitType.SECOND));
            }
            else {
                let prediction, color;
                if (alertLevel === msfsSdk.TcasAlertLevel.ResolutionAdvisory) {
                    prediction = this.intruder.tcaRA;
                    color = MapTrafficIntruderIcon.RA_COLOR;
                }
                else {
                    prediction = this.intruder.tcaTA;
                    color = MapTrafficIntruderIcon.TA_COLOR;
                }
                this.drawCPAVector(projection, context, prediction, color, vector);
            }
        }
        /**
         * Draws a motion vector projected to a certain lookahead time.
         * @param projection The map projection.
         * @param context The canvas rendering context to which to draw the vector.
         * @param color The color of the vector.
         * @param vector The vector to draw.
         * @param lookaheadTime The lookahead time, in seconds.
         */
        drawLookaheadVector(projection, context, color, vector, lookaheadTime) {
            context.lineWidth = MapTrafficIntruderIcon.VECTOR_STROKE_WIDTH;
            context.strokeStyle = color;
            context.setLineDash(MapTrafficIntruderIcon.EMPTY_LINE_DASH);
            context.beginPath();
            const distance = msfsSdk.Vec2Math.abs(vector) * lookaheadTime;
            const distanceView = this.options.vectorLength !== undefined
                ? this.options.vectorLength
                : distance / msfsSdk.UnitType.GA_RADIAN.convertTo(projection.getProjectedResolution(), msfsSdk.UnitType.METER);
            const track = -msfsSdk.Vec2Math.theta(vector);
            const angle = track + projection.getRotation();
            const end = msfsSdk.Vec2Math.setFromPolar(distanceView, angle, MapTrafficIntruderIcon.vec2Cache[1]);
            context.moveTo(0, 0);
            context.lineTo(end[0], end[1]);
            context.stroke();
        }
        /**
         * Draws a motion vector projected to the point of closest horizontal approach (CPA).
         * @param projection The map projection.
         * @param context The canvas rendering context to which to draw the vector.
         * @param prediction The time of closest approach prediction to use.
         * @param color The color of the vector.
         * @param vector The vector to draw.
         */
        drawCPAVector(projection, context, prediction, color, vector) {
            const distanceToEnd = msfsSdk.Vec2Math.abs(projection.getProjectedSize());
            if (distanceToEnd > 0) {
                context.lineWidth = MapTrafficIntruderIcon.VECTOR_STROKE_WIDTH;
                context.strokeStyle = color;
                context.setLineDash(MapTrafficIntruderIcon.VECTOR_LINE_DASH);
                context.beginPath();
                const track = -msfsSdk.Vec2Math.theta(vector);
                const angle = track + projection.getRotation();
                const end = msfsSdk.Vec2Math.setFromPolar(distanceToEnd, angle, MapTrafficIntruderIcon.vec2Cache[1]);
                context.moveTo(0, 0);
                context.lineTo(end[0], end[1]);
                context.stroke();
                context.setLineDash(MapTrafficIntruderIcon.EMPTY_LINE_DASH);
                const distanceToCPA = msfsSdk.Vec2Math.abs(vector) * prediction.tcpa.asUnit(msfsSdk.UnitType.SECOND);
                const distanceToCPAProjected = distanceToCPA / msfsSdk.UnitType.GA_RADIAN.convertTo(projection.getProjectedResolution(), msfsSdk.UnitType.METER);
                if (distanceToCPAProjected > 0) {
                    context.beginPath();
                    const cpa = msfsSdk.Vec2Math.setFromPolar(distanceToCPAProjected, angle, MapTrafficIntruderIcon.vec2Cache[1]);
                    context.moveTo(0, 0);
                    context.lineTo(cpa[0], cpa[1]);
                    context.stroke();
                }
            }
        }
    }
    MapTrafficIntruderIcon.VERTICAL_SPEED_THRESHOLD = msfsSdk.UnitType.FPM.createNumber(500);
    MapTrafficIntruderIcon.TA_COLOR = '#ffff00';
    MapTrafficIntruderIcon.TA_OFFSCALE_COLOR = '#454500';
    MapTrafficIntruderIcon.RA_COLOR = 'red';
    MapTrafficIntruderIcon.RA_OFFSCALE_COLOR = '#450000';
    MapTrafficIntruderIcon.TIS_VECTOR_DIR_PRECISION = 45 * Avionics.Utils.DEG2RAD;
    MapTrafficIntruderIcon.TIS_VECTOR_LOOKAHEAD = 60; // seconds
    MapTrafficIntruderIcon.VECTOR_STROKE_WIDTH = 2;
    MapTrafficIntruderIcon.VECTOR_ABS_COLOR = 'white';
    MapTrafficIntruderIcon.VECTOR_REL_COLOR = '#4ecc3d';
    MapTrafficIntruderIcon.VECTOR_LINE_DASH = [5, 5];
    MapTrafficIntruderIcon.EMPTY_LINE_DASH = [];
    MapTrafficIntruderIcon.vec2Cache = [msfsSdk.Vec2Math.create(), msfsSdk.Vec2Math.create()];

    exports.MapTrafficOffScaleStatus = void 0;
    (function (MapTrafficOffScaleStatus) {
        MapTrafficOffScaleStatus[MapTrafficOffScaleStatus["None"] = 0] = "None";
        MapTrafficOffScaleStatus[MapTrafficOffScaleStatus["TA"] = 1] = "TA";
        MapTrafficOffScaleStatus[MapTrafficOffScaleStatus["RA"] = 2] = "RA";
    })(exports.MapTrafficOffScaleStatus || (exports.MapTrafficOffScaleStatus = {}));

    /**
     * An icon which depicts a schematic outline of a runway.
     */
    class MapRunwayOutlineIcon {
        /**
         * Constructor.
         * @param waypoint The waypoint associated with this icon.
         * @param priority The render priority of this icon, or a subscribable which provides it. Icons with higher
         * priorities should be rendered above those with lower priorities.
         * @param options Styling options.
         * @param designationImgCache The cache from which this icon retrieves runway designation images. If not defined,
         * this icon will not render runway designations.
         */
        constructor(waypoint, priority, options, designationImgCache) {
            var _a, _b, _c, _d, _e, _f, _g, _h;
            this.waypoint = waypoint;
            this.hasSecondary = this.waypoint.secondaryNumber !== undefined;
            this.hasDesignatorChar = this.waypoint.runway.designatorCharPrimary !== RunwayDesignator.RUNWAY_DESIGNATOR_NONE;
            this.usableLength = this.waypoint.runway.length - this.waypoint.runway.primaryThresholdLength - this.waypoint.runway.secondaryThresholdLength;
            this.centerLineWidth = this.waypoint.runway.width * MapRunwayOutlineIcon.CENTER_LINE_WIDTH_FACTOR;
            this.thresholdBarLength = Math.min(MapRunwayOutlineIcon.THRESHOLD_BAR_LENGTH, this.usableLength);
            this.thresholdStripeWidth = this.waypoint.runway.width * MapRunwayOutlineIcon.THRESHOLD_STRIPE_WIDTH_FACTOR;
            this.thresholdStripeCenterGap = this.waypoint.runway.width * MapRunwayOutlineIcon.THRESHOLD_STRIPE_CENTER_GAP_FACTOR;
            this.thresholdStripeSideMargin = this.waypoint.runway.width * MapRunwayOutlineIcon.THRESHOLD_STRIPE_SIDE_MARGIN_FACTOR;
            this.thresholdStripeGap = (this.waypoint.runway.width - this.thresholdStripeWidth * MapRunwayOutlineIcon.THRESHOLD_STRIPE_COUNT - this.thresholdStripeSideMargin * 2 - this.thresholdStripeCenterGap)
                / ((MapRunwayOutlineIcon.THRESHOLD_STRIPE_COUNT / 2 - 1) * 2);
            this.thresholdStripeEndMargin = this.waypoint.runway.width * MapRunwayOutlineIcon.THRESHOLD_STRIPE_END_MARGIN_FACTOR;
            this.thresholdStripeStart = MapRunwayOutlineIcon.THRESHOLD_BAR_LENGTH + this.thresholdStripeEndMargin;
            this.thresholdStripeLength = Math.min(this.waypoint.runway.width * MapRunwayOutlineIcon.THRESHOLD_STRIPE_LENGTH_FACTOR, Math.max((this.usableLength / 2) - this.thresholdStripeStart, 0));
            this.designationWidth = this.waypoint.runway.width * MapRunwayOutlineIcon.DESIGNATION_WIDTH_FACTOR;
            this.designationGap = this.waypoint.runway.width * MapRunwayOutlineIcon.DESIGNATION_GAP_FACTOR;
            this.designationStart = this.thresholdStripeStart + this.thresholdStripeLength + this.designationGap;
            this.desiredDesignationLength = this.waypoint.runway.width * MapRunwayOutlineIcon.DESIGNATION_LENGTH_FACTOR;
            this.desiredTotalDesignationLength = this.desiredDesignationLength + (this.hasDesignatorChar ? this.desiredDesignationLength + this.designationGap : 0);
            this.trueTotalDesignationLength = this.designationStart + this.desiredDesignationLength <= this.usableLength / 2 ? this.desiredTotalDesignationLength : 0;
            this.centerLineStart = Math.min(this.designationStart + this.trueTotalDesignationLength + MapRunwayOutlineIcon.CENTER_LINE_GAP / 2, this.usableLength / 2);
            this.dispThresholdArrowStrokeWidth = this.waypoint.runway.width * MapRunwayOutlineIcon.DISP_THRESHOLD_ARROW_STROKE_WIDTH_FACTOR;
            this.dispThresholdArrowWidth = this.waypoint.runway.width * MapRunwayOutlineIcon.DISP_THRESHOLD_ARROW_WIDTH_FACTOR;
            this.dispThresholdArrowLength = this.waypoint.runway.width * MapRunwayOutlineIcon.DISP_THRESHOLD_ARROW_LENGTH_FACTOR;
            this.dispThresholdArrowStemLength = this.waypoint.runway.width * MapRunwayOutlineIcon.DISP_THRESHOLD_ARROW_STEM_LENGTH_FACTOR;
            this.dispThresholdArrowGapLength = this.dispThresholdArrowLength + this.dispThresholdArrowStemLength;
            this.dispThresholdArrowEndMargin = this.waypoint.runway.width * MapRunwayOutlineIcon.DISP_THRESHOLD_ARROW_END_MARGIN_FACTOR;
            this.transform = new msfsSdk.Transform2D();
            this.priority = msfsSdk.SubscribableUtils.toSubscribable(priority, true);
            this.fillStyle = msfsSdk.SubscribableUtils.toSubscribable((_a = options === null || options === void 0 ? void 0 : options.fillStyle) !== null && _a !== void 0 ? _a : MapRunwayOutlineIcon.DEFAULT_FILL_STYLE, true);
            this.outlineWidth = msfsSdk.SubscribableUtils.toSubscribable((_b = options === null || options === void 0 ? void 0 : options.outlineWidth) !== null && _b !== void 0 ? _b : MapRunwayOutlineIcon.DEFAULT_OUTLINE_WIDTH, true);
            this.outlineStyle = msfsSdk.SubscribableUtils.toSubscribable((_c = options === null || options === void 0 ? void 0 : options.outlineStyle) !== null && _c !== void 0 ? _c : MapRunwayOutlineIcon.DEFAULT_OUTLINE_STYLE, true);
            this.markingColor = msfsSdk.SubscribableUtils.toSubscribable((_d = options === null || options === void 0 ? void 0 : options.markingColor) !== null && _d !== void 0 ? _d : MapRunwayOutlineIcon.DEFAULT_MARKING_COLOR, true);
            this.drawCenterLine = msfsSdk.SubscribableUtils.toSubscribable((_e = options === null || options === void 0 ? void 0 : options.drawCenterLine) !== null && _e !== void 0 ? _e : true, true);
            this.drawThreshold = msfsSdk.SubscribableUtils.toSubscribable((_f = options === null || options === void 0 ? void 0 : options.drawThreshold) !== null && _f !== void 0 ? _f : true, true);
            this.drawDesignation = msfsSdk.SubscribableUtils.toSubscribable((_g = options === null || options === void 0 ? void 0 : options.drawDesignation) !== null && _g !== void 0 ? _g : true, true);
            this.drawDisplacedThreshold = msfsSdk.SubscribableUtils.toSubscribable((_h = options === null || options === void 0 ? void 0 : options.drawDisplacedThreshold) !== null && _h !== void 0 ? _h : true, true);
            this.primaryDesignationNumberImg = designationImgCache === null || designationImgCache === void 0 ? void 0 : designationImgCache.getNumber(waypoint.primaryNumber);
            this.primaryDesignationDesignatorImg = this.hasDesignatorChar ? designationImgCache === null || designationImgCache === void 0 ? void 0 : designationImgCache.getDesignator(waypoint.runway.designatorCharPrimary) : undefined;
            if (waypoint.secondaryNumber !== undefined) {
                this.secondaryDesignationNumberImg = designationImgCache === null || designationImgCache === void 0 ? void 0 : designationImgCache.getNumber(waypoint.secondaryNumber);
                this.secondaryDesignationDesignatorImg = this.hasDesignatorChar ? designationImgCache === null || designationImgCache === void 0 ? void 0 : designationImgCache.getDesignator(waypoint.runway.designatorCharSecondary) : undefined;
            }
        }
        /** @inheritdoc */
        draw(context, mapProjection) {
            const projectedCenter = mapProjection.project(this.waypoint.location.get(), MapRunwayOutlineIcon.vec2Cache[0]);
            const resolution = msfsSdk.UnitType.GA_RADIAN.convertTo(mapProjection.getProjectedResolution(), msfsSdk.UnitType.METER);
            const projectedWidth = this.waypoint.runway.width / resolution;
            const projectedLength = this.waypoint.runway.length / resolution;
            // LODs based on the projected width of the runway:
            // >= 15 px -> LOD 0: outlined runway with markings
            // >= 10 px -> LOD 1: outlined runway without markings
            // >= 0.5 px -> LOD 2: solid line
            // < 0.5 px -> not rendered
            if (projectedWidth < 0.5) {
                return;
            }
            // Bounding box check
            this.transform
                .toTranslation(-projectedLength / 2, -projectedWidth / 2)
                .addRotation((this.waypoint.runway.direction - 90) * Avionics.Utils.DEG2RAD + mapProjection.getRotation())
                .addTranslation(projectedCenter[0], projectedCenter[1]);
            const topLeft = msfsSdk.Vec2Math.set(0, 0, MapRunwayOutlineIcon.vec2Cache[1]);
            const topRight = msfsSdk.Vec2Math.set(projectedLength, 0, MapRunwayOutlineIcon.vec2Cache[2]);
            const bottomRight = msfsSdk.Vec2Math.set(projectedLength, projectedWidth, MapRunwayOutlineIcon.vec2Cache[3]);
            const bottomLeft = msfsSdk.Vec2Math.set(0, projectedWidth, MapRunwayOutlineIcon.vec2Cache[4]);
            this.transform.apply(topLeft, topLeft);
            this.transform.apply(topRight, topRight);
            this.transform.apply(bottomRight, bottomRight);
            this.transform.apply(bottomLeft, bottomLeft);
            const minX = Math.min(topLeft[0], topRight[0], bottomRight[0], bottomLeft[0]);
            const maxX = Math.max(topLeft[0], topRight[0], bottomRight[0], bottomLeft[0]);
            const minY = Math.min(topLeft[1], topRight[1], bottomRight[1], bottomLeft[1]);
            const maxY = Math.max(topLeft[1], topRight[1], bottomRight[1], bottomLeft[1]);
            const [windowWidth, windowHeight] = mapProjection.getProjectedSize();
            if (maxX <= 0 || maxY <= 0 || minX >= windowWidth || minY >= windowHeight) {
                return;
            }
            const width = this.waypoint.runway.width;
            const length = this.waypoint.runway.length;
            const halfWidth = width / 2;
            const halfLength = length / 2;
            const scalingFactor = 1 / resolution;
            // Transform the context such that the center of the runway is at the origin and the direction of the runway
            // runs along the y-axis, and apply a scaling factor based on the map projection.
            const transformParams = this.transform.getParameters();
            this.transform
                .toScale(scalingFactor, scalingFactor)
                .addRotation(this.waypoint.runway.direction * Avionics.Utils.DEG2RAD + mapProjection.getRotation())
                .addTranslation(projectedCenter[0], projectedCenter[1]);
            context.setTransform(transformParams[0], transformParams[3], transformParams[1], transformParams[4], transformParams[2], transformParams[5]);
            // Draw the runway fill.
            context.fillStyle = this.fillStyle.get();
            context.fillRect(-halfWidth, -halfLength, width, length);
            if (projectedWidth < 10) {
                context.resetTransform();
                return;
            }
            // Draw the runway outline.
            context.lineWidth = this.outlineWidth.get() * resolution;
            context.strokeStyle = this.outlineStyle.get();
            context.strokeRect(-halfWidth, -halfLength, width, length);
            if (projectedWidth < 15) {
                context.resetTransform();
                return;
            }
            context.fillStyle = this.markingColor.get();
            context.strokeStyle = this.markingColor.get();
            const primaryDisplacedThresholdLength = this.waypoint.runway.primaryThresholdLength;
            const secondaryDisplacedThresholdLength = this.waypoint.runway.secondaryThresholdLength;
            const primaryThresholdY = halfLength - primaryDisplacedThresholdLength;
            const secondaryThresholdY = -halfLength + secondaryDisplacedThresholdLength;
            if (this.drawCenterLine.get()) {
                context.beginPath();
                context.moveTo(0, primaryThresholdY - this.centerLineStart);
                context.lineTo(0, secondaryThresholdY + this.centerLineStart);
                context.lineWidth = this.centerLineWidth;
                context.setLineDash(MapRunwayOutlineIcon.CENTER_LINE_DASH_ARRAY);
                context.stroke();
            }
            // Transform the context such that the start of the primary runway lies at the origin and the direction of the
            // primary runway points in the negative y direction, then draw end markings.
            this.transform
                .toTranslation(0, halfLength)
                .addScale(scalingFactor, scalingFactor)
                .addRotation(this.waypoint.runway.direction * Avionics.Utils.DEG2RAD + mapProjection.getRotation())
                .addTranslation(projectedCenter[0], projectedCenter[1]);
            context.setTransform(transformParams[0], transformParams[3], transformParams[1], transformParams[4], transformParams[2], transformParams[5]);
            this.drawEndMarkings(context, width, halfWidth, primaryDisplacedThresholdLength, this.primaryDesignationNumberImg, this.primaryDesignationDesignatorImg);
            if (this.hasSecondary) {
                // Transform the context such that the start of the secondary runway lies at the origin and the direction of the
                // secondary runway points in the negative y direction, then draw end markings.
                this.transform
                    .toTranslation(0, halfLength)
                    .addScale(scalingFactor, scalingFactor)
                    .addRotation((this.waypoint.runway.direction + 180) * Avionics.Utils.DEG2RAD + mapProjection.getRotation())
                    .addTranslation(projectedCenter[0], projectedCenter[1]);
                context.setTransform(transformParams[0], transformParams[3], transformParams[1], transformParams[4], transformParams[2], transformParams[5]);
                this.drawEndMarkings(context, width, halfWidth, secondaryDisplacedThresholdLength, this.secondaryDesignationNumberImg, this.secondaryDesignationDesignatorImg);
            }
            context.resetTransform();
        }
        /**
         * Draws threshold and displaced threshold markings for a directional runway.
         * @param context The canvas 2D rendering context to which to render. The context should be transformed such that
         * the end of the runway lies at the origin and the direction of the runway points in the positive x direction, and
         * the scaling factor equals the local scaling factor of the map projection.
         * @param runwayWidth The width of the runway, in meters.
         * @param runwayHalfWidth The width of the runway divided by two, in meters.
         * @param displacedThresholdLength The length of the runway's displaced threshold, in meters.
         * @param numberImg The runway number image.
         * @param designatorImg The runway designator image.
         */
        drawEndMarkings(context, runwayWidth, runwayHalfWidth, displacedThresholdLength, numberImg, designatorImg) {
            const thresholdY = -displacedThresholdLength;
            if (this.drawThreshold.get()) {
                // Draw threshold bars
                context.fillRect(-runwayHalfWidth, thresholdY - this.thresholdBarLength, runwayWidth, this.thresholdBarLength);
                // Draw threshold stripes
                if (this.thresholdStripeLength > 0) {
                    const y = thresholdY - this.thresholdStripeStart - this.thresholdStripeLength;
                    let x = this.thresholdStripeSideMargin - runwayHalfWidth;
                    for (let i = 0; i < MapRunwayOutlineIcon.THRESHOLD_STRIPE_COUNT / 2; i++) {
                        context.fillRect(x, y, this.thresholdStripeWidth, this.thresholdStripeLength);
                        x += this.thresholdStripeWidth + this.thresholdStripeGap;
                    }
                    x = runwayHalfWidth - this.thresholdStripeSideMargin - this.thresholdStripeWidth;
                    for (let i = 0; i < MapRunwayOutlineIcon.THRESHOLD_STRIPE_COUNT / 2; i++) {
                        context.fillRect(x, y, this.thresholdStripeWidth, this.thresholdStripeLength);
                        x -= this.thresholdStripeWidth + this.thresholdStripeGap;
                    }
                }
            }
            if (this.drawDesignation.get() && this.trueTotalDesignationLength > 0) {
                // Draw designation
                const x = -this.designationWidth / 2;
                let y = thresholdY - this.designationStart - this.desiredDesignationLength;
                if (designatorImg !== undefined) {
                    context.drawImage(designatorImg, x, y, this.designationWidth, this.desiredDesignationLength);
                    y -= this.designationGap + this.desiredDesignationLength;
                }
                if (numberImg !== undefined) {
                    context.drawImage(numberImg, x, y, this.designationWidth, this.desiredDesignationLength);
                }
            }
            if (this.drawDisplacedThreshold.get()) {
                if (displacedThresholdLength < this.dispThresholdArrowEndMargin + this.dispThresholdArrowLength) {
                    return;
                }
                context.lineWidth = this.dispThresholdArrowStrokeWidth;
                context.beginPath();
                // Draw end arrows
                const endArrowY = thresholdY + this.dispThresholdArrowEndMargin;
                let endArrowX = runwayWidth / 6 - runwayHalfWidth;
                for (let i = 0; i < 3; i++) {
                    this.drawDisplacedThresholdArrowEndPattern(context, endArrowX, endArrowY);
                    endArrowX += runwayWidth / 3;
                }
                const centerLineStop = endArrowY + this.dispThresholdArrowLength + this.dispThresholdArrowEndMargin;
                const patternLength = this.dispThresholdArrowGapLength * 2;
                if (patternLength <= 0 || -centerLineStop < patternLength) {
                    context.stroke();
                    return;
                }
                // Draw center line arrows
                const patternCount = Math.floor(-centerLineStop / patternLength);
                const start = (centerLineStop + patternLength * patternCount) / 2;
                let centerArrowY = start;
                for (let i = 0; i < patternCount; i++) {
                    this.drawDisplacedThresholdArrowCenterPattern(context, centerArrowY);
                    centerArrowY -= patternLength;
                }
                context.stroke();
            }
        }
        /**
         * Draws a single unit of a displaced threshold end arrow pattern.
         * @param context The canvas 2D rendering context to which to render.
         * @param x The x coordinate of the top of the arrow, in meters.
         * @param y The y coordinate of the center of the arrow, in meters.
         */
        drawDisplacedThresholdArrowEndPattern(context, x, y) {
            context.moveTo(x - this.dispThresholdArrowWidth / 2, y + this.dispThresholdArrowLength);
            context.lineTo(x, y);
            context.lineTo(x + this.dispThresholdArrowWidth / 2, y + this.dispThresholdArrowLength);
        }
        /**
         * Draws a single unit of a displaced threshold centerline arrow pattern.
         * @param context The canvas 2D rendering context to which to render.
         * @param y The y coordinate of the start of the pattern unit, in meters.
         */
        drawDisplacedThresholdArrowCenterPattern(context, y) {
            const stemStartY = y - this.dispThresholdArrowGapLength * 0.5;
            const stemEndY = stemStartY - this.dispThresholdArrowStemLength;
            const arrowEndY = stemEndY - this.dispThresholdArrowLength;
            context.moveTo(0, stemStartY);
            context.lineTo(0, stemEndY);
            context.moveTo(-this.dispThresholdArrowWidth / 2, stemEndY);
            context.lineTo(0, arrowEndY);
            context.lineTo(this.dispThresholdArrowWidth / 2, stemEndY);
        }
    }
    MapRunwayOutlineIcon.CENTER_LINE_LENGTH = msfsSdk.UnitType.FOOT.convertTo(120, msfsSdk.UnitType.METER);
    MapRunwayOutlineIcon.CENTER_LINE_GAP = msfsSdk.UnitType.FOOT.convertTo(80, msfsSdk.UnitType.METER);
    MapRunwayOutlineIcon.CENTER_LINE_WIDTH_FACTOR = 0.1; // relative to runway width
    MapRunwayOutlineIcon.CENTER_LINE_DASH_ARRAY = [MapRunwayOutlineIcon.CENTER_LINE_LENGTH, MapRunwayOutlineIcon.CENTER_LINE_GAP];
    MapRunwayOutlineIcon.THRESHOLD_BAR_LENGTH = 3; // meters
    MapRunwayOutlineIcon.THRESHOLD_STRIPE_COUNT = 8;
    MapRunwayOutlineIcon.THRESHOLD_STRIPE_WIDTH_FACTOR = 0.06; // relative to runway width
    MapRunwayOutlineIcon.THRESHOLD_STRIPE_LENGTH_FACTOR = 0.7; // relative to runway width
    MapRunwayOutlineIcon.THRESHOLD_STRIPE_CENTER_GAP_FACTOR = 0.15; // relative to runway width
    MapRunwayOutlineIcon.THRESHOLD_STRIPE_SIDE_MARGIN_FACTOR = 0.1; // relative to runway width
    MapRunwayOutlineIcon.THRESHOLD_STRIPE_END_MARGIN_FACTOR = 0.1; // relative to runway width
    MapRunwayOutlineIcon.DESIGNATION_WIDTH_FACTOR = 0.9; // relative to runway width
    MapRunwayOutlineIcon.DESIGNATION_LENGTH_FACTOR = 0.6; // relative to runway width
    MapRunwayOutlineIcon.DESIGNATION_GAP_FACTOR = 0.15; // relative to runway width
    MapRunwayOutlineIcon.DISP_THRESHOLD_ARROW_STROKE_WIDTH_FACTOR = 0.05; // relative to runway width
    MapRunwayOutlineIcon.DISP_THRESHOLD_ARROW_WIDTH_FACTOR = 0.25; // relative to runway width
    MapRunwayOutlineIcon.DISP_THRESHOLD_ARROW_LENGTH_FACTOR = 0.1; // relative to runway width
    MapRunwayOutlineIcon.DISP_THRESHOLD_ARROW_STEM_LENGTH_FACTOR = 0.33; // relative to runway width
    MapRunwayOutlineIcon.DISP_THRESHOLD_ARROW_END_MARGIN_FACTOR = 0.05; // relative to runway width
    MapRunwayOutlineIcon.DEFAULT_FILL_STYLE = '#afafaf';
    MapRunwayOutlineIcon.DEFAULT_OUTLINE_WIDTH = 1;
    MapRunwayOutlineIcon.DEFAULT_OUTLINE_STYLE = 'white';
    MapRunwayOutlineIcon.DEFAULT_MARKING_COLOR = 'white';
    MapRunwayOutlineIcon.vec2Cache = [msfsSdk.Vec2Math.create(), msfsSdk.Vec2Math.create(), msfsSdk.Vec2Math.create(), msfsSdk.Vec2Math.create(), msfsSdk.Vec2Math.create()];

    /// <reference types="@microsoft/msfs-types/js/avionics" />
    /**
     * An airport icon.
     */
    class MapAirportIcon extends msfsSdk.MapWaypointSpriteIcon {
        /**
         * Constructor.
         * @param waypoint The waypoint associated with this icon.
         * @param priority The render priority of this icon. Icons with higher priorities should be rendered above those
         * with lower priorities.
         * @param img The image to use for the icon.
         * @param size The size of this icon, as `[width, height]` in pixels, or a subscribable which provides it.
         * @param options Options with which to initialize this icon.
         */
        constructor(waypoint, priority, img, size, options) {
            super(waypoint, priority, img, 32, 32, size, options);
        }
        // eslint-disable-next-line jsdoc/require-jsdoc
        getSpriteFrame(mapProjection) {
            if (!this.waypoint.longestRunway) {
                return 0;
            }
            const mapRotationDeg = mapProjection.getRotation() * Avionics.Utils.RAD2DEG;
            return Math.round(msfsSdk.NavMath.normalizeHeading((this.waypoint.longestRunway.direction + mapRotationDeg)) / 22.5) % 8;
        }
    }
    /**
     * An icon for a highlighted waypoint. This icon embellishes a pre-existing ("base") icon with a surrounding ring and
     * background.
     */
    class MapWaypointHighlightIcon extends msfsSdk.AbstractMapWaypointIcon {
        /**
         * Constructor.
         * @param baseIcon This icon's base waypoint icon.
         * @param priority The render priority of this icon. Icons with higher priorities should be rendered above those
         * with lower priorities.
         * @param options Options with which to initialize this icon.
         */
        constructor(baseIcon, priority, options) {
            var _a, _b, _c, _d, _e, _f;
            super(baseIcon.waypoint, priority, baseIcon.size, { offset: baseIcon.offset, anchor: baseIcon.anchor });
            this.baseIcon = baseIcon;
            this.ringRadiusBuffer = msfsSdk.SubscribableUtils.toSubscribable((_a = options === null || options === void 0 ? void 0 : options.ringRadiusBuffer) !== null && _a !== void 0 ? _a : 0, true);
            this.strokeWidth = msfsSdk.SubscribableUtils.toSubscribable((_b = options === null || options === void 0 ? void 0 : options.strokeWidth) !== null && _b !== void 0 ? _b : 2, true);
            this.strokeColor = msfsSdk.SubscribableUtils.toSubscribable((_c = options === null || options === void 0 ? void 0 : options.strokeColor) !== null && _c !== void 0 ? _c : 'white', true);
            this.outlineWidth = msfsSdk.SubscribableUtils.toSubscribable((_d = options === null || options === void 0 ? void 0 : options.outlineWidth) !== null && _d !== void 0 ? _d : 0, true);
            this.outlineColor = msfsSdk.SubscribableUtils.toSubscribable((_e = options === null || options === void 0 ? void 0 : options.outlineColor) !== null && _e !== void 0 ? _e : 'black', true);
            this.bgColor = msfsSdk.SubscribableUtils.toSubscribable((_f = options === null || options === void 0 ? void 0 : options.bgColor) !== null && _f !== void 0 ? _f : '#3c3c3c', true);
        }
        /** @inheritdoc */
        drawIconAt(context, mapProjection, left, top) {
            const size = this.baseIcon.size.get();
            const radius = Math.hypot(size[0], size[1]) / 2 + this.ringRadiusBuffer.get();
            const x = left + size[0] / 2;
            const y = top + size[1] / 2;
            context.beginPath();
            context.arc(x, y, radius, 0, 2 * Math.PI);
            this.drawRingBackground(context);
            this.baseIcon.draw(context, mapProjection);
            this.drawRing(context);
        }
        /**
         * Draws the ring background for this icon.
         * @param context  A canvas rendering context.
         */
        drawRingBackground(context) {
            context.fillStyle = this.bgColor.get();
            context.fill();
        }
        /**
         * Draws the ring for this icon.
         * @param context  A canvas rendering context.
         */
        drawRing(context) {
            const outlineWidth = this.outlineWidth.get();
            const strokeWidth = this.strokeWidth.get();
            if (outlineWidth > 0) {
                this.applyStroke(context, (strokeWidth + 2 * outlineWidth), this.outlineColor.get());
            }
            if (strokeWidth > 0) {
                this.applyStroke(context, strokeWidth, this.strokeColor.get());
            }
        }
        /**
         * Applies a stroke to a canvas rendering context.
         * @param context A canvas rendering context.
         * @param lineWidth The width of the stroke.
         * @param strokeStyle The style of the stroke.
         */
        applyStroke(context, lineWidth, strokeStyle) {
            context.lineWidth = lineWidth;
            context.strokeStyle = strokeStyle;
            context.stroke();
        }
    }

    /**
     *
     */
    class MapWaypointDisplayBuilderClass {
        constructor() {
            this.factories = new Map();
        }
        /** @inheritdoc */
        withFactory(roles, icon, label) {
            msfsSdk.BitFlags.forEach(roles, (val, index) => {
                this.factories.set(1 << index, { icon, label });
            }, true);
            return this;
        }
        /** @inheritdoc */
        withNormalStyles(imgCache, iconStyleSelector, labelStyleSelector, runwayOutlineIconStyleSelector, runwayDesignationImgCache) {
            this.factories.set(exports.MapWaypointRenderRole.Normal, {
                icon: () => new WaypointIconFactory(imgCache, iconStyleSelector, runwayOutlineIconStyleSelector, runwayDesignationImgCache),
                label: () => new WaypointLabelFactory(labelStyleSelector)
            });
            return this;
        }
        /** @inheritdoc */
        withFlightPlanInactiveStyles(imgCache, iconStyleSelector, labelStyleSelector) {
            this.factories.set(exports.MapWaypointRenderRole.FlightPlanInactive, {
                icon: () => new WaypointIconFactory(imgCache, iconStyleSelector),
                label: () => new WaypointLabelFactory(labelStyleSelector)
            });
            return this;
        }
        /** @inheritdoc */
        withFlightPlanActiveStyles(imgCache, iconStyleSelector, labelStyleSelector) {
            this.factories.set(exports.MapWaypointRenderRole.FlightPlanActive, {
                icon: () => new WaypointIconFactory(imgCache, iconStyleSelector),
                label: () => new WaypointLabelFactory(labelStyleSelector)
            });
            return this;
        }
        /** @inheritdoc */
        withHighlightStyles(imgCache, iconStyleSelector, labelStyleSelector) {
            this.factories.set(exports.MapWaypointRenderRole.Highlight, {
                icon: () => new WaypointHighlightIconFactory(imgCache, iconStyleSelector),
                label: () => new WaypointLabelFactory(labelStyleSelector)
            });
            return this;
        }
        /** @inheritdoc */
        withVNavStyles(imgCache, iconStyleSelector, labelStyleSelector) {
            this.factories.set(exports.MapWaypointRenderRole.VNav, {
                icon: () => new VNavWaypointIconFactory(imgCache, iconStyleSelector),
                label: () => new VNavWaypointLabelFactory(labelStyleSelector)
            });
            return this;
        }
        /** @inheritdoc */
        withProcPreviewStyles(imgCache, iconStyleSelector, labelStyleSelector) {
            this.factories.set(exports.MapWaypointRenderRole.ProcedurePreview, {
                icon: () => new WaypointIconFactory(imgCache, iconStyleSelector),
                label: () => new WaypointLabelFactory(labelStyleSelector)
            });
            return this;
        }
        /** @inheritdoc */
        withProcTransitionPreviewStyles(imgCache, iconStyleSelector, labelStyleSelector) {
            this.factories.set(exports.MapWaypointRenderRole.ProcedureTransitionPreview, {
                icon: () => new WaypointIconFactory(imgCache, iconStyleSelector),
                label: () => new WaypointLabelFactory(labelStyleSelector)
            });
            return this;
        }
        /**
         * Applies this builder's configurations to a waypoint renderer.
         * @param renderer A waypoint renderer.
         */
        apply(renderer) {
            for (const [role, factories] of this.factories) {
                renderer.setIconFactory(role, factories.icon());
                renderer.setLabelFactory(role, factories.label());
            }
        }
    }
    /**
     * A waypoint icon factory.
     */
    class WaypointIconFactory {
        /**
         * Constructor.
         * @param imgCache The image cache from which this factory retrieves icon images.
         * @param styles A function which retrieves styles for icons.
         * @param runwayOutlineStyles A function which retrieves styles for runway outline icons. If not defined, this
         * factory will not generate icons for runway outline waypoints.
         * @param runwayDesignationImgCache The image cache from which this factory retrieves runway designation images. If
         * not defined, runway designations will not be rendered by this factory's icons.
         */
        constructor(imgCache, styles, runwayOutlineStyles, runwayDesignationImgCache) {
            this.imgCache = imgCache;
            this.styles = styles;
            this.runwayOutlineStyles = runwayOutlineStyles;
            this.runwayDesignationImgCache = runwayDesignationImgCache;
            this.cache = new Map();
        }
        /** @inheritdoc */
        getIcon(role, waypoint) {
            let existing = this.cache.get(waypoint.uid);
            if (existing === undefined) {
                existing = this.createIcon(waypoint);
                this.cache.set(waypoint.uid, existing);
            }
            return existing;
        }
        /**
         * Creates a new icon for a waypoint.
         * @param waypoint The waypoint for which to create an icon.
         * @returns a waypoint icon.
         */
        createIcon(waypoint) {
            if (waypoint instanceof MapRunwayOutlineWaypoint) {
                if (this.runwayOutlineStyles === undefined) {
                    return null;
                }
                const { priority, options } = this.runwayOutlineStyles(waypoint);
                return new MapRunwayOutlineIcon(waypoint, priority, options, this.runwayDesignationImgCache);
            }
            else if (waypoint instanceof MapRunwayLabelWaypoint) {
                return null;
            }
            const { priority, size } = this.styles(waypoint);
            const img = this.imgCache.getForWaypoint(waypoint);
            if (img) {
                if (waypoint instanceof AirportWaypoint) {
                    return new MapAirportIcon(waypoint, priority, img, size);
                }
                else if (msfsSdk.FacilityWaypointUtils.isFacilityWaypoint(waypoint) || waypoint instanceof msfsSdk.FlightPathWaypoint) {
                    return new msfsSdk.MapWaypointImageIcon(waypoint, priority, img, size);
                }
            }
            return null;
        }
    }
    /**
     * A waypoint label factory.
     */
    class WaypointLabelFactory {
        /**
         * Constructor.
         * @param styles A function which retrieves styles for labels.
         */
        constructor(styles) {
            this.styles = styles;
            this.cache = new Map();
        }
        /** @inheritdoc */
        getLabel(role, waypoint) {
            let existing = this.cache.get(waypoint.uid);
            if (existing === undefined) {
                existing = this.createLabel(waypoint);
                this.cache.set(waypoint.uid, existing);
            }
            return existing;
        }
        /**
         * Creates a new icon for a waypoint.
         * @param waypoint The waypoint for which to create an icon.
         * @returns a waypoint icon.
         */
        createLabel(waypoint) {
            const { priority, alwaysShow, options } = this.styles(waypoint);
            let text = undefined;
            if (msfsSdk.FacilityWaypointUtils.isFacilityWaypoint(waypoint)) {
                text = msfsSdk.ICAO.getIdent(waypoint.facility.get().icao);
            }
            else if (waypoint instanceof msfsSdk.FlightPathWaypoint || waypoint instanceof ProcedureTurnLegWaypoint) {
                text = waypoint.ident;
            }
            else if (waypoint instanceof MapRunwayLabelWaypoint) {
                text = waypoint.runway.designation;
            }
            if (text !== undefined) {
                return new msfsSdk.MapCullableLocationTextLabel(text, priority, waypoint.location, alwaysShow, options);
            }
            return null;
        }
    }
    /**
     * A waypoint icon factory for highlighted waypoints.
     */
    class WaypointHighlightIconFactory {
        /**
         * Constructor.
         * @param imgCache The image cache from which to retrieve icon images.
         * @param styles A function which retrieves styles for icons.
         */
        constructor(imgCache, styles) {
            this.imgCache = imgCache;
            this.styles = styles;
            this.cache = new Map();
        }
        /** @inheritdoc */
        getIcon(role, waypoint) {
            let existing = this.cache.get(waypoint.uid);
            if (existing === undefined) {
                existing = this.createIcon(waypoint);
                this.cache.set(waypoint.uid, existing);
            }
            return existing;
        }
        /**
         * Creates a new icon for a waypoint.
         * @param waypoint The waypoint for which to create an icon.
         * @returns A waypoint icon.
         */
        createIcon(waypoint) {
            const style = this.styles(waypoint);
            const baseIcon = this.createBaseIcon(waypoint, style);
            if (baseIcon) {
                return new MapWaypointHighlightIcon(baseIcon, baseIcon.priority, style.highlightOptions);
            }
            return null;
        }
        /**
         * Creates a new base icon for a waypoint.
         * @param waypoint The waypoint for which to create a base icon.
         * @param style The style to apply to the icon.
         * @returns A waypoint base icon.
         */
        createBaseIcon(waypoint, style) {
            const { priority, size } = style;
            const img = this.imgCache.getForWaypoint(waypoint);
            if (img === undefined) {
                return null;
            }
            if (waypoint instanceof AirportWaypoint) {
                return new MapAirportIcon(waypoint, priority, img, size);
            }
            else if (msfsSdk.FacilityWaypointUtils.isFacilityWaypoint(waypoint)) {
                return new msfsSdk.MapWaypointImageIcon(waypoint, priority, img, size);
            }
            return null;
        }
    }
    /**
     * A waypoint icon factory for VNAV waypoints.
     */
    class VNavWaypointIconFactory {
        /**
         * Constructor.
         * @param imgCache The image cache from which to retrieve icon images.
         * @param styles A function which retrieves styles for icons.
         */
        constructor(imgCache, styles) {
            this.imgCache = imgCache;
            this.styles = styles;
        }
        /** @inheritdoc */
        getIcon(role, waypoint) {
            return this.createIcon(waypoint);
        }
        /**
         * Creates a new icon for a waypoint.
         * @param waypoint The waypoint for which to create an icon.
         * @returns a waypoint icon.
         */
        createIcon(waypoint) {
            const { priority, size } = this.styles(waypoint);
            const img = this.imgCache.getForWaypoint(waypoint);
            if (img) {
                return new msfsSdk.MapWaypointImageIcon(waypoint, priority, img, size);
            }
            return null;
        }
    }
    /**
     * A waypoint label factory for VNAV waypoints.
     */
    class VNavWaypointLabelFactory {
        /**
         * Constructor.
         * @param styles A function which retrieves styles for labels.
         */
        constructor(styles) {
            this.styles = styles;
        }
        /** @inheritdoc */
        getLabel(role, waypoint) {
            return this.createLabel(waypoint);
        }
        /**
         * Creates a new icon for a waypoint.
         * @param waypoint The waypoint for which to create an icon.
         * @returns a waypoint icon.
         */
        createLabel(waypoint) {
            const { priority, alwaysShow, options } = this.styles(waypoint);
            if (waypoint instanceof msfsSdk.VNavWaypoint) {
                return new msfsSdk.MapCullableLocationTextLabel(waypoint.ident, priority, waypoint.location, alwaysShow, options);
            }
            return null;
        }
    }

    /**
     * A module for the map crosshair.
     */
    class MapCrosshairModule {
        constructor() {
            this.show = msfsSdk.Subject.create(false);
        }
    }

    /**
     *
     */
    class MapFlightPlanFocusModule {
        constructor() {
            /**
             * Whether focus is active.
             */
            this.isActive = msfsSdk.Subject.create(false);
            /**
             * Whether the flight plan has focus.
             */
            this.planHasFocus = msfsSdk.Subject.create(false);
            /**
             * The flight plan focus.
             */
            this.focus = msfsSdk.Subject.create(null);
            /**
             * A flight plan data provider for the plan which has focus, or `null`. A data provider is required to update
             * a flight plan leg focus after its component legs have been calculated for the first time.
             */
            this.dataProvider = msfsSdk.Subject.create(null);
        }
    }

    /**
     * A module describing the state of a Garmin autopilot.
     */
    class MapGarminAutopilotPropsModule extends msfsSdk.MapAutopilotPropsModule {
        constructor() {
            super(...arguments);
            /** Whether automatic adjustment of selected heading during a turn is active. */
            this.isTurnHdgAdjustActive = msfsSdk.Subject.create(false);
            /** Whether HDG sync mode is active. */
            this.isHdgSyncModeActive = msfsSdk.Subject.create(false);
            /** An event that is triggered when the selected heading is changed manually. */
            this.manualHeadingSelect = new msfsSdk.SubEvent();
        }
    }

    /**
     * A map module describing the integrity states of various data sources for Garmin maps.
     */
    class MapGarminDataIntegrityModule extends msfsSdk.MapDataIntegrityModule {
        constructor() {
            super(...arguments);
            /** Whether or not airplane position data is derived from dead reckoning. */
            this.isDeadReckoning = msfsSdk.Subject.create(false);
        }
    }

    /**
     * A module for map weather radar mode data.
     */
    class MapNexradModule {
        constructor() {
            /** Whether to show the NEXRAD overlay. */
            this.showNexrad = msfsSdk.Subject.create(false);
            /**
             * The color array for the NEXRAD overlay. Each entry `E_i` of the array is a tuple `[color, rate]` that defines a
             * color stop, where `color` is an RGBA color expressed as `R + G * 256 + B * 256^2 + A * 256^3` and `rate` is a
             * precipitation rate in millimeters per hour.
             *
             * In general, the color defined by `E_i` is applied to precipitation rates ranging from the rate defined by `E_i-1`
             * to the rate defined by `E_i`. There are two special cases. The color defined by `E_0` is applied to the
             * precipitation rates from zero to the rate defined by `E_0`. The color defined by `E_n-1`, where `n` is the length
             * of the array, is applied to the precipitation rates from the rate defined by `E_n-2` to positive infinity.
             */
            this.colors = msfsSdk.Subject.create(Array.from(msfsSdk.BingComponent.DEFAULT_WEATHER_COLORS));
        }
    }

    /**
     * A module describing manual panning of the map.
     */
    class MapPanningModule {
        constructor() {
            /** Whether panning is active. */
            this.isActive = msfsSdk.Subject.create(false);
            /** The desired map target. */
            this.target = msfsSdk.GeoPointSubject.create(new msfsSdk.GeoPoint(0, 0));
        }
    }

    /**
     * A module describing the map pointer.
     */
    class MapPointerModule {
        constructor() {
            /** Whether the pointer is active. */
            this.isActive = msfsSdk.Subject.create(false);
            /** The position of the pointer on the projected map, in pixel coordinates. */
            this.position = msfsSdk.Vec2Subject.create(new Float64Array(2));
            /** The desired map target. */
            this.target = msfsSdk.GeoPointSubject.create(new msfsSdk.GeoPoint(0, 0));
        }
    }

    /**
     * A module describing a flight plan procedure to be previewed.
     */
    class MapProcedurePreviewModule {
        constructor() {
            /** The procedure type previewed by the layer. */
            this.procedureType = msfsSdk.Subject.create(exports.ProcedureType.DEPARTURE);
            /** The flight plan containing the procedure to be previewed. */
            this.procedurePlan = msfsSdk.Subject.create(null);
            /** The flight plan containing the transitions to be previewed. */
            this.transitionPlan = msfsSdk.Subject.create(null);
        }
    }

    /**
     * A module describing the map range compass.
     */
    class MapRangeCompassModule {
        constructor() {
            /** Whether to show the range compass. */
            this.show = msfsSdk.Subject.create(true);
        }
    }

    /**
     * A module describing the map range ring.
     */
    class MapRangeRingModule {
        constructor() {
            /** Whether to show the range ring. */
            this.show = msfsSdk.Subject.create(true);
        }
    }

    /**
     * A module describing the display of the track vector.
     */
    class MapTrackVectorModule {
        constructor() {
            /** Whether to show the track vector. */
            this.show = msfsSdk.Subject.create(false);
            /** The track vector's lookahead time. */
            this.lookaheadTime = msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.SECOND.createNumber(60));
        }
    }

    /**
     * A module which defines display units.
     */
    class MapUnitsModule {
        /**
         * Constructor.
         * @param unitsSettingManager A display units user setting manager.
         */
        constructor(unitsSettingManager) {
            var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
            this.unitsSettingManager = unitsSettingManager;
            /** Distance units mode. */
            this.distanceMode = (_b = (_a = this.unitsSettingManager) === null || _a === void 0 ? void 0 : _a.getSetting('unitsDistance')) !== null && _b !== void 0 ? _b : msfsSdk.Subject.create(exports.UnitsDistanceSettingMode.Nautical);
            /** Altitude units mode. */
            this.altitudeMode = (_d = (_c = this.unitsSettingManager) === null || _c === void 0 ? void 0 : _c.getSetting('unitsAltitude')) !== null && _d !== void 0 ? _d : msfsSdk.Subject.create(exports.UnitsAltitudeSettingMode.Feet);
            /** Nav angle units mode. */
            this.navAngleMode = (_f = (_e = this.unitsSettingManager) === null || _e === void 0 ? void 0 : _e.getSetting('unitsNavAngle')) !== null && _f !== void 0 ? _f : msfsSdk.Subject.create(exports.UnitsNavAngleSettingMode.Magnetic);
            /** Nav angle units. */
            this.navAngle = (_h = (_g = this.unitsSettingManager) === null || _g === void 0 ? void 0 : _g.navAngleUnits) !== null && _h !== void 0 ? _h : msfsSdk.Subject.create(msfsSdk.BasicNavAngleUnit.create(true));
            /** Large distance units. */
            this.distanceLarge = (_k = (_j = this.unitsSettingManager) === null || _j === void 0 ? void 0 : _j.distanceUnitsLarge) !== null && _k !== void 0 ? _k : msfsSdk.Subject.create(msfsSdk.UnitType.NMILE);
            /** Small distance units. */
            this.distanceSmall = (_m = (_l = this.unitsSettingManager) === null || _l === void 0 ? void 0 : _l.distanceUnitsSmall) !== null && _m !== void 0 ? _m : msfsSdk.Subject.create(msfsSdk.UnitType.FOOT);
        }
    }

    /**
     * A module which defines a highlighted waypoint.
     */
    class MapWaypointHighlightModule {
        constructor() {
            /** The highlighted waypoint. */
            this.waypoint = msfsSdk.Subject.create(null);
        }
    }

    /**
     * A module describing the display of waypoints.
     */
    class MapWaypointsModule {
        constructor() {
            /** Whether to show airports. */
            this.airportShow = {
                [exports.AirportSize.Large]: msfsSdk.Subject.create(true),
                [exports.AirportSize.Medium]: msfsSdk.Subject.create(true),
                [exports.AirportSize.Small]: msfsSdk.Subject.create(true)
            };
            /** Whether to show VORs. */
            this.vorShow = msfsSdk.Subject.create(true);
            /** Whether to show NDBs. */
            this.ndbShow = msfsSdk.Subject.create(true);
            /** Whether to show intersections. */
            this.intShow = msfsSdk.Subject.create(true);
            /** Whether to show user waypoints. */
            this.userShow = msfsSdk.Subject.create(true);
            /** Whether to show runway outlines. */
            this.runwayShow = msfsSdk.Subject.create(true);
            /** Whether to show runway labels. */
            this.runwayLabelShow = msfsSdk.Subject.create(true);
            /** The minimum projected length of a runway, in pixels, required to show its label. */
            this.runwayLabelMinLength = msfsSdk.Subject.create(50);
        }
    }

    /**
     * A module describing the wind vector.
     */
    class MapWindVectorModule {
        /**
         * Creates a new instance of MapWindModule.
         * @param dataProvider A provider of wind data.
         */
        constructor(dataProvider) {
            this.dataProvider = dataProvider;
            /** Whether to show the wind vector. */
            this.show = msfsSdk.Subject.create(false);
            /** The current wind direction, in degrees true. */
            this.windDirection = this.dataProvider.windDirection;
            /** The current wind speed, in knots. */
            this.windSpeed = this.dataProvider.windSpeed;
            /** Whether wind data is in a failed state. */
            this.isDataFailed = this.dataProvider.isDataFailed;
        }
    }

    /**
     * A module which defines selected waypoint information for waypoint information maps.
     */
    class WaypointMapSelectionModule {
        constructor() {
            /** The selected waypoint. */
            this.waypoint = msfsSdk.Subject.create(null);
            /** The selected airport runway. */
            this.runway = msfsSdk.Subject.create(null);
        }
    }

    /* eslint-disable jsdoc/require-jsdoc */
    /**
     * A builder for Garmin maps.
     */
    class GarminMapBuilder {
        /**
         * Configures a map builder to add a module describing the player airplane's autopilot properties, and optionally
         * binds the module's properties to data received over the event bus.
         *
         * Adds the following...
         *
         * Modules:
         * * `[MapSystemKeys.AutopilotProps]: MapGarminAutopilotPropsModule`
         *
         * Controllers:
         * * `[MapSystemKeys.AutopilotProps]: MapGarminAutopilotPropsController` (optional)
         * @param mapBuilder The map builder to configure.
         * @param propertiesToBind Properties on the autopilot module to bind to data received over the event bus.
         * @param updateFreq The update frequency, in hertz, of the data bindings, or a subscribable which provides it. If
         * not defined, the data bindings will update every frame. Ignored if `propertiesToBind` is undefined.
         * @returns This builder, after it has been configured.
         */
        static autopilotProps(mapBuilder, propertiesToBind, updateFreq) {
            mapBuilder.withModule(msfsSdk.MapSystemKeys.AutopilotProps, () => new MapGarminAutopilotPropsModule());
            if (propertiesToBind !== undefined) {
                mapBuilder.withController(msfsSdk.MapSystemKeys.AutopilotProps, context => new MapGarminAutopilotPropsController(context, propertiesToBind, updateFreq));
            }
            return mapBuilder;
        }
        /**
         * Configures a map builder to generate a map with measurement unit support.
         *
         * Adds the module `[GarminMapKeys.Units]: MapUnitsModule`.
         * @param mapBuilder The map builder to configure.
         * @param settingManager A units setting manager to control the map's measurement units. If not defined, the map
         * will use a default set of measurement units.
         * @returns The map builder, after it has been configured.
         */
        static units(mapBuilder, settingManager) {
            return mapBuilder.withModule(GarminMapKeys.Units, () => new MapUnitsModule(settingManager));
        }
        /**
         * Configures a map builder to generate a map which supports multiple indexed ranges, with optional support for
         * controlling the map range with a user setting.
         *
         * Adds the following...
         *
         * Context properties:
         * * `[MapSystemKeys.RangeControl]: ResourceModerator<void>`
         * * `[GarminMapKeys.UseRangeSetting]: ResourceModerator<Subject<boolean>>` (only if user setting support is enabled)
         *
         * Modules:
         * * `[GarminMapKeys.Range]: MapIndexedRangeModule`
         *
         * Controllers:
         * * `[GarminMapKeys.RangeRTR]: MapRangeRTRController`
         * * `[GarminMapKeys.Range]: MapRangeController` (can be used to control map range)
         * * `'useRangeSettingDefault': MapSystemCustomController` (only if user setting support is enabled)
         * @param mapBuilder The map builder to configure.
         * @param nauticalRangeArray The map range array to use for nautical distance mode. If not defined, a range array
         * will not automatically be set when entering nautical distance mode.
         * @param metricRangeArray The map range array to use for metric distance mode. If not defined, a range array will
         * not automatically be set when entering metric distance mode.
         * @param settingManager A setting manager containing a user setting to control the map range. If not defined, range
         * will not be controlled by a user setting.
         * @param useRangeSettingByDefault Whether the range user setting should control the map range by default. Defaults
         * to `true`. Is ignored if `settingManager` is undefined.
         * @returns The map builder, after it has been configured.
         */
        static range(mapBuilder, nauticalRangeArray, metricRangeArray, settingManager, useRangeSettingByDefault = true) {
            const useSetting = settingManager === undefined ? undefined : msfsSdk.Subject.create(true);
            if (useSetting) {
                const defaultUseRangeSettingConsumer = {
                    priority: 0,
                    onAcquired: (useRangeSetting) => {
                        useRangeSetting.set(useRangeSettingByDefault);
                    },
                    onCeded: () => { }
                };
                let useRangeSettingDefaultController;
                mapBuilder
                    .withContext(GarminMapKeys.UseRangeSetting, () => new msfsSdk.ResourceModerator(useSetting))
                    .withController('useRangeSettingDefault', context => useRangeSettingDefaultController = new msfsSdk.MapSystemGenericController(context, {
                    onAfterMapRender: (contextArg) => {
                        contextArg[GarminMapKeys.UseRangeSetting].claim(defaultUseRangeSettingConsumer);
                    },
                    onMapDestroyed: () => {
                        useRangeSettingDefaultController.destroy();
                    },
                    onDestroyed: (contextArg) => {
                        contextArg[GarminMapKeys.UseRangeSetting].forfeit(defaultUseRangeSettingConsumer);
                    }
                }));
            }
            return mapBuilder
                .withRangeControlModerator()
                .withModule(GarminMapKeys.Range, () => new msfsSdk.MapIndexedRangeModule())
                .withController(GarminMapKeys.RangeRTR, context => new MapRangeRTRController(context))
                .withController(GarminMapKeys.Range, context => new MapRangeController(context, nauticalRangeArray, metricRangeArray, settingManager, useSetting));
        }
        /**
         * Configures a map builder to generate a map which supports different orientations, as enumerated by
         * {@link MapOrientation}. Each orientation defines a different rotation behavior, target offset, and range
         * endpoints.
         *
         * Adds the following...
         *
         * Context properties:
         * * `[MapSystemKeys.RotationControl]: ResourceModerator<void>`
         * * `[GarminMapKeys.OrientationControl]: ResourceModerator<void>`
         * * `[GarminMapKeys.DesiredOrientationControl]: ResourceModerator<void>`
         *
         * Modules:
         * * `[MapSystemKeys.Rotation]: MapRotationModule`
         * * `[GarminMapKeys.Orientation]: MapOrientationModule`
         * * `[GarminMapKeys.Range]: MapIndexedRangeModule`
         * * `[MapSystemKeys.OwnAirplaneProps]: MapOwnAirplanePropsModule`
         *
         * Controllers:
         * * `[MapSystemKeys.Rotation]: MapRotationController`
         * * `[GarminMapKeys.OrientationRTR]: MapOrientationRTRController`
         * * `[GarminMapKeys.Orientation]: MapOrientationModeController`
         * * `[GarminMapKeys.DesiredOrientation]: MapDesiredOrientationController`
         * @param mapBuilder The map builder to configure.
         * @param nominalTargetOffsets The nominal projected target offsets defined by each orientation. Each target offset
         * is a 2-tuple `[x, y]`, where each component is expressed relative to the width or height of the map's projected
         * window, *excluding* the dead zone. If an orientation does not have a defined offset, it will default to `[0, 0]`.
         * @param nominalRangeEndpoints The nominal range endpoints defined by each orientation. Each set of range endpoints
         * is a 4-tuple `[x1, y1, x2, y2]`, where each component is expressed relative to the width or height of the map's
         * projected window, *excluding* the dead zone. If an orientation does not have defined range endpoints, it will
         * default to `[0.5, 0.5, 0.5, 0]` (center to top-center).
         * @returns The map builder, after it has been configured.
         */
        static orientationBase(mapBuilder, nominalTargetOffsets, nominalRangeEndpoints) {
            mapBuilder
                .withRotation()
                .withContext(GarminMapKeys.RotationModeControl, () => new msfsSdk.ResourceModerator(undefined))
                .withContext(GarminMapKeys.OrientationControl, () => new msfsSdk.ResourceModerator(undefined))
                .withContext(GarminMapKeys.DesiredOrientationControl, () => new msfsSdk.ResourceModerator(undefined))
                .withModule(GarminMapKeys.Orientation, () => new MapOrientationModule())
                .withController(GarminMapKeys.OrientationRTR, context => new MapOrientationRTRController(context, nominalTargetOffsets, nominalRangeEndpoints))
                .withController(GarminMapKeys.Orientation, context => new MapOrientationModeController(context))
                .withModule(GarminMapKeys.Range, () => new msfsSdk.MapIndexedRangeModule())
                .withModule(msfsSdk.MapSystemKeys.OwnAirplaneProps, () => new msfsSdk.MapOwnAirplanePropsModule())
                .withController(GarminMapKeys.DesiredOrientation, context => new MapDesiredOrientationController(context));
            return mapBuilder;
        }
        /**
         * Configures a map builder to generate a map which supports different orientations, as enumerated by
         * {@link MapOrientation}. Each orientation defines a different rotation behavior, target offset, and range
         * endpoints.
         *
         * Adds the following...
         *
         * Context properties:
         * * `[MapSystemKeys.RotationControl]: ResourceModerator<void>`
         * * `[GarminMapKeys.OrientationControl]: ResourceModerator<void>`
         * * `[GarminMapKeys.DesiredOrientationControl]: ResourceModerator<void>`
         *
         * Modules:
         * * `[MapSystemKeys.Rotation]: MapRotationModule`
         * * `[GarminMapKeys.Orientation]: MapOrientationModule`
         * * `[GarminMapKeys.Range]: MapIndexedRangeModule`
         *
         * Controllers:
         * * `[MapSystemKeys.Rotation]: MapRotationController`
         * * `[GarminMapKeys.OrientationRTR]: MapOrientationRTRController`
         * * `[GarminMapKeys.Orientation]: MapOrientationModeController`
         * * `[GarminMapKeys.DesiredOrientation]: MapDesiredOrientationController`
         * * `[GarminMapKeys.OrientationSettings]: MapOrientationSettingsController` (only with user setting support)
         * @param mapBuilder The map builder to configure.
         * @param nominalTargetOffsets The nominal projected target offsets defined by each orientation. Each target offset
         * is a 2-tuple `[x, y]`, where each component is expressed relative to the width or height of the map's projected
         * window, *excluding* the dead zone. If an orientation does not have a defined offset, it will default to `[0, 0]`.
         * @param nominalRangeEndpoints The nominal range endpoints defined by each orientation. Each set of range endpoints
         * is a 4-tuple `[x1, y1, x2, y2]`, where each component is expressed relative to the width or height of the map's
         * projected window, *excluding* the dead zone. If an orientation does not have defined range endpoints, it will
         * default to `[0.5, 0.5, 0.5, 0]` (center to top-center).
         * @param settingManager A setting manager containing user settings used to control the map orientation. If not
         * defined, map orientation will not be bound to user settings.
         * @returns The map builder, after it has been configured.
         */
        static orientation(mapBuilder, nominalTargetOffsets, nominalRangeEndpoints, settingManager) {
            mapBuilder.with(GarminMapBuilder.orientationBase, nominalTargetOffsets, nominalRangeEndpoints);
            if (settingManager !== undefined) {
                mapBuilder.withController(GarminMapKeys.OrientationSettings, context => new MapOrientationSettingsController(context, settingManager));
            }
            return mapBuilder;
        }
        /**
         * Configures a map builder to generate a map which supports different weather map orientations, as enumerated by
         * {@link MapOrientation}. Each orientation defines a different rotation behavior, target offset, and range
         * endpoints.
         *
         * Adds the following...
         *
         * Context properties:
         * * `[MapSystemKeys.RotationControl]: ResourceModerator<void>`
         * * `[GarminMapKeys.OrientationControl]: ResourceModerator<void>`
         * * `[GarminMapKeys.DesiredOrientationControl]: ResourceModerator<void>`
         *
         * Modules:
         * * `[MapSystemKeys.Rotation]: MapRotationModule`
         * * `[GarminMapKeys.Orientation]: MapOrientationModule`
         * * `[GarminMapKeys.Range]: MapIndexedRangeModule`
         *
         * Controllers:
         * * `[MapSystemKeys.Rotation]: MapRotationController`
         * * `[GarminMapKeys.OrientationRTR]: MapOrientationRTRController`
         * * `[GarminMapKeys.Orientation]: MapOrientationModeController`
         * * `[GarminMapKeys.DesiredOrientation]: MapDesiredOrientationController`
         * * `[GarminMapKeys.OrientationSettings]: WeatherMapOrientationSettingsController` (only with user setting support)
         * @param mapBuilder The map builder to configure.
         * @param nominalTargetOffsets The nominal projected target offsets defined by each orientation. Each target offset
         * is a 2-tuple `[x, y]`, where each component is expressed relative to the width or height of the map's projected
         * window, *excluding* the dead zone. If an orientation does not have a defined offset, it will default to `[0, 0]`.
         * @param nominalRangeEndpoints The nominal range endpoints defined by each orientation. Each set of range endpoints
         * is a 4-tuple `[x1, y1, x2, y2]`, where each component is expressed relative to the width or height of the map's
         * projected window, *excluding* the dead zone. If an orientation does not have defined range endpoints, it will
         * default to `[0.5, 0.5, 0.5, 0]` (center to top-center).
         * @param settingManager A setting manager containing user settings used to control the map orientation. If not
         * defined, map orientation will not be bound to user settings.
         * @returns The map builder, after it has been configured.
         */
        static weatherOrientation(mapBuilder, nominalTargetOffsets, nominalRangeEndpoints, settingManager) {
            mapBuilder.with(GarminMapBuilder.orientationBase, nominalTargetOffsets, nominalRangeEndpoints);
            if (settingManager !== undefined) {
                mapBuilder.withController(GarminMapKeys.OrientationSettings, context => new WeatherMapOrientationSettingsController(context, settingManager));
            }
            return mapBuilder;
        }
        /**
         * Configures a map builder to generate a map which supports data integrity state. During loss of valid heading
         * information, the map will default to North Up orientation, and the player airplane icon will optionally be changed
         * to reflect this state. During loss of valid GPS signal, the map will stop attempting to follow the player
         * airplane, and the player airplane icon will be hidden.
         * @param mapBuilder The map builder to configure.
         * @param airplaneIconSrc A mutable subscribable which controls the player airplane icon's image source URI.
         * Required for this controller to change the player airplane icon.
         * @param airplaneIconAnchor A mutable subscribable which controls the anchor point of the player airplane icon.
         * Required for this controller to change the player airplane icon.
         * @param normalIconSrc The URI of the normal player airplane icon's image source, or a subscribable which provides
         * it. Required for the player airplane icon to change during loss of valid heading information.
         * @param normalIconAnchor The anchor point of the normal player airplane icon, as `[x, y]`, where each component is
         * relative to the width or height of the icon, or a subscribable which provides it. Required for the player airplane
         * icon to change during loss of valid heading information.
         * @param noHeadingIconSrc The URI of the no-heading player airplane icon's image source, or a subscribable which
         * provides it. Required for the player airplane icon to change during loss of valid heading information.
         * @param noHeadingIconAnchor The anchor point of the no-heading player airplane icon, as `[x, y]`, where each
         * component is relative to the width or height of the icon, or a subscribable which provides it. Required for the
         * player airplane icon to change during loss of valid heading information.
         * @returns The map builder, after it has been configured.
         */
        static dataIntegrity(mapBuilder, airplaneIconSrc, airplaneIconAnchor, normalIconSrc, normalIconAnchor, noHeadingIconSrc, noHeadingIconAnchor) {
            return mapBuilder
                .withModule(msfsSdk.MapSystemKeys.DataIntegrity, () => new MapGarminDataIntegrityModule())
                .withController(GarminMapKeys.DataIntegrityRTR, context => {
                const normalIconSrcToUse = typeof normalIconSrc === 'string' ? msfsSdk.Subject.create(normalIconSrc) : normalIconSrc;
                const normalIconAnchorToUse = normalIconAnchor instanceof Float64Array
                    ? msfsSdk.Subject.create(normalIconAnchor)
                    : normalIconAnchor;
                const noHeadingIconSrcToUse = typeof noHeadingIconSrc === 'string' ? msfsSdk.Subject.create(noHeadingIconSrc) : noHeadingIconSrc;
                const noHeadingIconAnchorToUse = noHeadingIconAnchor instanceof Float64Array
                    ? msfsSdk.Subject.create(noHeadingIconAnchor)
                    : noHeadingIconAnchor;
                return new MapDataIntegrityRTRController(context, airplaneIconSrc, airplaneIconAnchor, normalIconSrcToUse, normalIconAnchorToUse, noHeadingIconSrcToUse, noHeadingIconAnchorToUse);
            });
        }
        /**
         * Configures a map builder to generate a map which supports declutter modes, and optionally binds the declutter
         * mode to a user setting.
         * @param mapBuilder The map builder to configure.
         * @param settingManager A user setting manager containing the setting controlling declutter mode.
         * @returns The map builder, after it has been configured.
         */
        static declutter(mapBuilder, settingManager) {
            mapBuilder.withModule(GarminMapKeys.Declutter, () => new MapDeclutterModule());
            if ((settingManager === null || settingManager === void 0 ? void 0 : settingManager.tryGetSetting('mapDeclutter')) !== undefined) {
                mapBuilder.withBindings(GarminMapKeys.Declutter, context => {
                    return [
                        {
                            source: settingManager.getSetting('mapDeclutter'),
                            target: context.model.getModule(GarminMapKeys.Declutter).mode,
                            map: (setting) => {
                                switch (setting) {
                                    case exports.MapDeclutterSettingMode.Level3: return exports.MapDeclutterMode.Level3;
                                    case exports.MapDeclutterSettingMode.Level2: return exports.MapDeclutterMode.Level2;
                                    case exports.MapDeclutterSettingMode.Level1: return exports.MapDeclutterMode.Level1;
                                    default: return exports.MapDeclutterMode.All;
                                }
                            }
                        }
                    ];
                });
            }
            return mapBuilder;
        }
        /**
         * Configures a map builder to generate a map which supports terrain color modes, and optionally binds the modes to
         * user settings.
         *
         * Adds the following...
         *
         * Modules:
         * * `[GarminMapKeys.Range]: MapIndexedRangeModule` (only with user settings support)
         * * `[GarminMapKeys.Terrain]: MapTerrainModule`
         *
         * Controllers:
         * * `[MapSystemKeys.TerrainColors]: MapTerrainColorsController`
         * * `[GarminMapKeys.Terrain]: MapTerrainController` (only with user settings support)
         * @param mapBuilder The map builder to configure.
         * @param colors The terrain colors to use for each terrain mode. Ignored if `includeTerrain` is `false`.
         * @param settingManager A user setting manager containing settings which control terrain colors. If not defined,
         * terrain color mode will not be controlled by user settings.
         * @param terrainModeOptions Options with which to configure the terrain mode controller. If a `boolean` value is
         * provided in place of an options object, then it will be interpreted as the `allowRelative` option. Ignored if
         * terrain colors is not controlled by user settings.
         * @param groundRelativeBlendDuration The amount of time, in milliseconds, over which to blend the on-ground and
         * relative terrain mode colors when transitioning between the two. A blend transition is only possible if colors
         * are defined for both the on-ground and relative terrain modes, and the colors for both modes have the same number
         * of steps and are applied over the same elevation range. Defaults to 0 milliseconds.
         * @returns The map builder, after it has been configured.
         */
        static terrainColors(mapBuilder, colors, settingManager, terrainModeOptions, groundRelativeBlendDuration = 0) {
            mapBuilder
                .withModule(GarminMapKeys.Terrain, () => new MapTerrainModule())
                .withController(msfsSdk.MapSystemKeys.TerrainColors, context => new MapTerrainColorsController(context, colors !== null && colors !== void 0 ? colors : {}, groundRelativeBlendDuration));
            const setting = settingManager === null || settingManager === void 0 ? void 0 : settingManager.tryGetSetting('mapTerrainMode');
            if (setting !== undefined) {
                mapBuilder
                    .withModule(GarminMapKeys.Range, () => new msfsSdk.MapIndexedRangeModule())
                    .withController(GarminMapKeys.Terrain, context => {
                    return new MapTerrainController(context, settingManager, terrainModeOptions);
                });
            }
            return mapBuilder;
        }
        /**
         * Configures a map builder to generate a map which supports NEXRAD, and optionally binds the display of NEXRAD to
         * user settings.
         *
         * Requires the module `[MapSystemKeys.Weather]: MapWxrModule`.
         *
         * Adds the following...
         *
         * Modules:
         * * `[GarminMapKeys.Range]: MapIndexedRangeModule`
         * * `[GarminMapKeys.Nexrad]: MapNexradModule`
         *
         * Controllers:
         * * `[MapSystemKeys.Weather]: MapWxrController`
         * * `[GarminMapKeys.Nexrad]: MapNexradController`
         * @param mapBuilder The map builder to configure.
         * @param minRangeIndex The minimum range index, inclusive, at which NEXRAD is visible. Defaults to `0`.
         * @param settingManager A user setting manager containing settings which control NEXRAD. If not defined, NEXRAD will
         * not be controlled by user settings.
         * @param maxDeclutterMode The highest global declutter mode, inclusive, at which NEXRAD is visible. Defaults
         * to `MapDeclutterMode.Level2`. Ignored if NEXRAD user settings are not supported.
         * @param colors The color array for the NEXRAD overlay. If not defined, default colors will be applied.
         * @returns The map builder, after it has been configured.
         */
        static nexrad(mapBuilder, minRangeIndex = 0, settingManager, maxDeclutterMode, colors) {
            return mapBuilder
                .withModule(GarminMapKeys.Range, () => new msfsSdk.MapIndexedRangeModule())
                .withModule(GarminMapKeys.Nexrad, () => new MapNexradModule())
                .withController(msfsSdk.MapSystemKeys.Weather, context => new MapWxrController(context))
                .withController(GarminMapKeys.Nexrad, context => {
                return new MapNexradController(context, minRangeIndex, settingManager, maxDeclutterMode);
            })
                .withInit(GarminMapKeys.Nexrad, context => {
                if (colors !== undefined) {
                    context.model.getModule(GarminMapKeys.Nexrad).colors.set(colors);
                }
            });
        }
        /**
         * Configures a map builder to generate a map which displays a range ring.
         *
         * Requires the modules defined in {@link MapRangeRingLayerModules} with the exception of `'rangeRing'`.
         *
         * Adds the following...
         *
         * Modules:
         * * `[GarminMapKeys.RangeRing]: MapRangeRingModule`
         *
         * Layers:
         * * `[GarminMapKeys.RangeRing]: MapRangeRingLayer`
         * @param mapBuilder The map builder to configure.
         * @param options Styling options for the ring.
         * @param order The order to assign to the range ring layer. Layers with lower assigned order will be attached to the
         * map before and appear below layers with greater assigned order values. Defaults to the number of layers already
         * added to the map builder.
         * @returns The map builder, after it has been configured.
         */
        static rangeRing(mapBuilder, options, order) {
            return mapBuilder
                .withModule(GarminMapKeys.RangeRing, () => new MapRangeRingModule())
                .withLayer(GarminMapKeys.RangeRing, context => {
                return (msfsSdk.FSComponent.buildComponent(MapRangeRingLayer, Object.assign({ model: context.model, mapProjection: context.projection }, options)));
            }, order)
                .withInit(GarminMapKeys.RangeRing, context => { context.model.getModule(GarminMapKeys.RangeRing).show.set(true); });
        }
        /**
         * Configures a map builder to generate a map which displays a range compass. The range compass is displayed only in
         * Heading Up and Track Up orientation. If the map also supports a range ring, the range ring will be hidden while
         * the range compass is displayed.
         *
         * Requires the modules defined in {@link MapRangeCompassLayerModules} with the exception of `'rangeCompass'`.
         *
         * Adds the following...
         *
         * Modules:
         * * `[GarminMapKeys.RangeCompass]: MapRangeCompassModule`
         *
         * Layers:
         * * `[GarminMapKeys.RangeCompass]: MapRangeCompassLayer`
         *
         * Controllers:
         * * `[GarminMapKeys.RangeCompass]: MapRangeCompassController`
         * @param mapBuilder The map builder to configure.
         * @param options Styling options for the compass.
         * @param order The order to assign to the range compass layer. Layers with lower assigned order will be attached to
         * the map before and appear below layers with greater assigned order values. Defaults to the number of layers
         * already added to the map builder.
         * @returns The map builder, after it has been configured.
         */
        static rangeCompass(mapBuilder, options, order) {
            return mapBuilder
                .withModule(GarminMapKeys.RangeCompass, () => new MapRangeCompassModule())
                .withLayer(GarminMapKeys.RangeCompass, context => {
                return (msfsSdk.FSComponent.buildComponent(MapRangeCompassLayer, Object.assign({ model: context.model, mapProjection: context.projection, bus: context.bus }, options)));
            }, order)
                .withController(GarminMapKeys.RangeCompass, context => new MapRangeCompassController(context));
        }
        /**
         * Configures a map builder to generate a map which displays a crosshair at the projected target position when the
         * map is not following the player airplane. If the map does not have the module
         * `[MapSystemKeys.FollowAirplane]: MapFollowAirplaneModule`, the map is assumed to never follow the player airplane,
         * and the crosshair will always be visible.
         *
         * Adds the following...
         *
         * Modules:
         * * `[GarminMapKeys.Crosshair]: MapCrosshairModule`
         *
         * Layers:
         * * `[GarminMapKeys.Crosshair]: MapCrosshairLayer`
         *
         * Controllers:
         * * `[GarminMapKeys.Crosshair]: MapBindingsController`
         * @param mapBuilder The map builder to configure.
         * @returns The map builder, after it has been configured.
         */
        static crosshair(mapBuilder) {
            return mapBuilder
                .withModule(GarminMapKeys.Crosshair, () => new MapCrosshairModule())
                .withLayer(GarminMapKeys.Crosshair, context => {
                return (msfsSdk.FSComponent.buildComponent(MapCrosshairLayer, { model: context.model, mapProjection: context.projection }));
            })
                .withBindings(GarminMapKeys.Crosshair, context => {
                var _a, _b;
                return [{
                        source: (_b = (_a = context.model.getModule(msfsSdk.MapSystemKeys.FollowAirplane)) === null || _a === void 0 ? void 0 : _a.isFollowing) !== null && _b !== void 0 ? _b : msfsSdk.Subject.create(false),
                        target: context.model.getModule(GarminMapKeys.Crosshair).show,
                        map: (isFollowingAirplane) => !isFollowingAirplane
                    }];
            });
        }
        /**
         * Configures a map builder to generate a map which supports the display of waypoints located within the boundaries
         * of the map's projected window. Waypoints displayed in this manner are rendered by a {@link MapWaypointRenderer}
         * under the role {@link MapWaypointRenderRole.Normal}. Optionally binds the visibility of waypoints to user
         * settings.
         *
         * If a text layer has already been added to the builder, its order will be changed so that it is rendered above the
         * waypoint layer. Otherwise, a text layer will be added to the builder after the waypoint layer.
         *
         * Adds the following...
         *
         * Context properties:
         * * `[MapSystemKeys.TextManager]: MapCullableTextLabelManager`
         * * `[MapSystemKeys.WaypointRenderer]: MapWaypointRenderer`
         * * `[GarminMapKeys.WaypointDisplayBuilder]: MapWaypointDisplayBuilder`
         *
         * Modules:
         * * `[MapSystemKeys.NearestWaypoints]: MapWaypointsModule`
         * * `[GarminMapKeys.Range]: MapIndexedRangeModule` (only if user settings are supported)
         *
         * Layers:
         * * `[MapSystemKeys.NearestWaypoints]: MapWaypointsLayer`
         * * `[MapSystemKeys.TextLayer]: MapCullableTextLayer`
         *
         * Controllers:
         * * `[MapSystemKeys.WaypointRenderer]: MapSystemCustomController` (handles initialization and updating of the
         * waypoint renderer)
         * * `[GarminMapKeys.WaypointsVisibility]: MapWaypointsVisController` (only if user settings are supported)
         * @param mapBuilder The map builder to configure.
         * @param configure A function used to configure the display and styling of waypoint icons and labels.
         * @param supportRunwayOutlines Whether to support the rendering of airport runway outlines.
         * @param settingManager A setting manager containing the user settings controlling waypoint visibility. If not
         * defined, waypoint visibility will not be bound to user settings.
         * @param order The order to assign to the waypoint layer. Layers with lower assigned order will be attached to the
         * map before and appear below layers with greater assigned order values. Defaults to the number of layers already
         * added to the map builder.
         * @returns The map builder, after it has been configured.
         */
        static waypoints(mapBuilder, configure, supportRunwayOutlines, settingManager, order) {
            mapBuilder
                .withTextLayer(true)
                .withModule(msfsSdk.MapSystemKeys.NearestWaypoints, () => new MapWaypointsModule())
                .withContext(msfsSdk.MapSystemKeys.WaypointRenderer, context => new MapWaypointRenderer(context[msfsSdk.MapSystemKeys.TextManager]))
                .withContext(GarminMapKeys.WaypointDisplayBuilder, () => new MapWaypointDisplayBuilderClass());
            if (settingManager) {
                mapBuilder
                    .withModule(GarminMapKeys.Range, () => new msfsSdk.MapIndexedRangeModule())
                    .with(GarminMapBuilder.waypointVisSettings, settingManager);
            }
            const layerCount = mapBuilder.layerCount;
            return mapBuilder
                .withLayer(msfsSdk.MapSystemKeys.NearestWaypoints, (context) => {
                return (msfsSdk.FSComponent.buildComponent(MapWaypointsLayer, { model: context.model, mapProjection: context.projection, bus: context.bus, waypointRenderer: context[msfsSdk.MapSystemKeys.WaypointRenderer], supportRunwayOutlines: supportRunwayOutlines }));
            }, order)
                .withLayerOrder(msfsSdk.MapSystemKeys.TextLayer, order !== null && order !== void 0 ? order : layerCount)
                .withInit('waypointsLayerDisplayConfigure', context => {
                configure(context[GarminMapKeys.WaypointDisplayBuilder], context);
            })
                .withController(msfsSdk.MapSystemKeys.WaypointRenderer, context => new msfsSdk.MapSystemGenericController(context, {
                onAfterMapRender: () => { context[GarminMapKeys.WaypointDisplayBuilder].apply(context[msfsSdk.MapSystemKeys.WaypointRenderer]); },
                onAfterUpdated: () => { context[msfsSdk.MapSystemKeys.WaypointRenderer].update(context.projection); }
            }));
        }
        /**
         * Configures a map builder to bind the visibility of waypoints to user settings.
         *
         * Requires the modules defined in {@link MapWaypointsVisControllerModules}.
         *
         * Adds the controller `[GarminMapKeys.WaypointsVisibility]: MapWaypointsVisController`.
         * @param mapBuilder The map builder to configure.
         * @param settingManager A setting manager containing the user settings controlling waypoint visibility.
         * @param options Options with which to configure waypoint visibility.
         * @returns The map builder, after it has been configured.
         */
        static waypointVisSettings(mapBuilder, settingManager, options) {
            return mapBuilder.withController(GarminMapKeys.WaypointsVisibility, context => new MapWaypointsVisController(context, settingManager, options));
        }
        static flightPlans(mapBuilder, options, configure, order) {
            if (options.length === 0) {
                return mapBuilder;
            }
            mapBuilder
                .withTextLayer(true)
                .withContext(msfsSdk.MapSystemKeys.WaypointRenderer, context => new MapWaypointRenderer(context[msfsSdk.MapSystemKeys.TextManager]))
                .withContext(GarminMapKeys.WaypointDisplayBuilder, () => new MapWaypointDisplayBuilderClass());
            const layerCount = mapBuilder.layerCount;
            for (let i = 0; i < options.length; i++) {
                const { dataProvider, pathRenderer, drawEntirePlan } = options[i];
                mapBuilder.withLayer(`${msfsSdk.MapSystemKeys.FlightPlan}${i}`, context => {
                    return (msfsSdk.FSComponent.buildComponent(MapFlightPlanLayer, { model: context.model, mapProjection: context.projection, bus: context.bus, waypointRenderer: context[msfsSdk.MapSystemKeys.WaypointRenderer], dataProvider: dataProvider, pathRenderer: pathRenderer, drawEntirePlan: typeof drawEntirePlan !== 'object' ? msfsSdk.Subject.create(drawEntirePlan !== null && drawEntirePlan !== void 0 ? drawEntirePlan : false) : drawEntirePlan }));
                }, order !== null && order !== void 0 ? order : layerCount);
            }
            return mapBuilder
                .withLayer(GarminMapKeys.FlightPlanWaypoints, context => {
                return (msfsSdk.FSComponent.buildComponent(msfsSdk.MapSyncedCanvasLayer, { model: context.model, mapProjection: context.projection }));
            }, order !== null && order !== void 0 ? order : layerCount)
                .withLayerOrder(msfsSdk.MapSystemKeys.TextLayer, order !== null && order !== void 0 ? order : layerCount)
                .withInit('flightPlanLayerDisplayConfigure', context => {
                configure(context[GarminMapKeys.WaypointDisplayBuilder]);
            })
                .withController(msfsSdk.MapSystemKeys.WaypointRenderer, context => new msfsSdk.MapSystemGenericController(context, {
                onAfterMapRender: () => { context[GarminMapKeys.WaypointDisplayBuilder].apply(context[msfsSdk.MapSystemKeys.WaypointRenderer]); },
                onAfterUpdated: () => { context[msfsSdk.MapSystemKeys.WaypointRenderer].update(context.projection); }
            }))
                .withController(GarminMapKeys.FlightPlanWaypoints, context => new msfsSdk.MapSystemGenericController(context, {
                onAfterMapRender: () => {
                    const fplWaypointsLayer = context.getLayer(GarminMapKeys.FlightPlanWaypoints);
                    context[msfsSdk.MapSystemKeys.WaypointRenderer].setCanvasContext(exports.MapWaypointRenderRole.FlightPlanInactive, fplWaypointsLayer.display.context);
                    context[msfsSdk.MapSystemKeys.WaypointRenderer].setCanvasContext(exports.MapWaypointRenderRole.FlightPlanActive, fplWaypointsLayer.display.context);
                    context[msfsSdk.MapSystemKeys.WaypointRenderer].setCanvasContext(exports.MapWaypointRenderRole.VNav, fplWaypointsLayer.display.context);
                }
            }));
        }
        /**
         * Configures a map builder to generate a map which displays the active flight plan. The flight path and all
         * waypoints that are part of the flight plan are displayed. Waypoints displayed in this manner are rendered by a
         * {@link MapWaypointRenderer} under the roles {@link MapWaypointRenderRole.FlightPlanActive} or
         * {@link MapWaypointRenderRole.FlightPlanInactive}. Additionally, if VNAV is supported, TOD and BOD markers will
         * also be rendered under the role {@link MapWaypointRenderRole.VNav}.
         *
         * If a text layer has already been added to the builder, its order will be changed so that it is rendered above the
         * flight plan layers. Otherwise, a text layer will be added to the builder after the flight plan layers.
         * @param mapBuilder The map builder to configure.
         * @param flightPlanner The flight planner from which to retrieve the active flight plan.
         * @param pathRenderer The flight path renderer to use to the draw the flight plan.
         * @param drawEntirePlan Whether the entire flight plan should always be drawn.
         * @param configure A function used to configure the display and styling of flight plan waypoint icons and labels.
         * @param options Options with which to configure the display of the active flight plan. If a `boolean` value is
         * provided in place of an options object, then it will be interpreted as the `supportFocus` option.
         * @param order The order to assign to the flight plan layers. Layers with lower assigned order will be attached to
         * the map before and appear below layers with greater assigned order values. Defaults to the number of layers
         * already added to the map builder.
         * @returns The map builder, after it has been configured.
         */
        static activeFlightPlan(mapBuilder, flightPlanner, pathRenderer, drawEntirePlan, configure, options, order) {
            var _a, _b, _c;
            if (typeof options !== 'object') {
                options = {
                    supportFocus: options
                };
            }
            const lnavIndex = (_a = options.lnavIndex) !== null && _a !== void 0 ? _a : 0;
            const vnavIndex = (_b = options.vnavIndex) !== null && _b !== void 0 ? _b : 0;
            const supportFocus = (_c = options.supportFocus) !== null && _c !== void 0 ? _c : false;
            if (supportFocus) {
                // Because flight plan focus still leaves the DTO random flight plan visible when it is active, we need to
                // support drawing two flight plans at the same time under those circumstances.
                const primaryPlanProvider = new MapFlightPlannerPlanDataProvider(mapBuilder.bus, { flightPlanner, lnavIndex, vnavIndex });
                const dtoPlanProvider = new MapFlightPlannerPlanDataProvider(mapBuilder.bus, { flightPlanner, lnavIndex, vnavIndex });
                return mapBuilder
                    .with(GarminMapBuilder.flightPlans, [
                    { dataProvider: primaryPlanProvider, pathRenderer, drawEntirePlan },
                    { dataProvider: dtoPlanProvider, pathRenderer, drawEntirePlan: false }
                ], configure, order)
                    .withController('activeFlightPlanProvider', context => {
                    let controller;
                    let plannerSub;
                    let fplIndexSub;
                    let isFocusedSub;
                    return controller = new msfsSdk.MapSystemGenericController(context, {
                        onAfterMapRender: () => {
                            const focusModule = context.model.getModule(GarminMapKeys.FlightPlanFocus);
                            const plannerSubscribable = msfsSdk.SubscribableUtils.toSubscribable(flightPlanner, true);
                            const planProviderHandler = () => {
                                var _a;
                                const activePlanIndex = plannerSubscribable.get().activePlanIndex;
                                const isFlightPlanFocused = (_a = focusModule === null || focusModule === void 0 ? void 0 : focusModule.planHasFocus.get()) !== null && _a !== void 0 ? _a : false;
                                // Show the primary plan when a DTO random is not active or when it is focused.
                                primaryPlanProvider.setPlanIndex(activePlanIndex === FmsUtils.PRIMARY_PLAN_INDEX || isFlightPlanFocused ? FmsUtils.PRIMARY_PLAN_INDEX : -1);
                                // Only show the DTO random plan when a DTO random is active.
                                dtoPlanProvider.setPlanIndex(activePlanIndex === FmsUtils.DTO_RANDOM_PLAN_INDEX ? FmsUtils.DTO_RANDOM_PLAN_INDEX : -1);
                            };
                            isFocusedSub = focusModule === null || focusModule === void 0 ? void 0 : focusModule.planHasFocus.sub(planProviderHandler);
                            plannerSub = plannerSubscribable.sub(planner => {
                                fplIndexSub === null || fplIndexSub === void 0 ? void 0 : fplIndexSub.destroy();
                                fplIndexSub = planner.onEvent('fplIndexChanged').handle(planProviderHandler);
                                planProviderHandler();
                            }, true);
                        },
                        onMapDestroyed: () => {
                            controller.destroy();
                        },
                        onDestroyed: () => {
                            plannerSub === null || plannerSub === void 0 ? void 0 : plannerSub.destroy();
                            fplIndexSub === null || fplIndexSub === void 0 ? void 0 : fplIndexSub.destroy();
                            isFocusedSub === null || isFocusedSub === void 0 ? void 0 : isFocusedSub.destroy();
                            primaryPlanProvider.destroy();
                            dtoPlanProvider.destroy();
                        }
                    });
                });
            }
            else {
                const dataProvider = new MapActiveFlightPlanDataProvider(mapBuilder.bus, { flightPlanner, lnavIndex });
                return mapBuilder
                    .with(GarminMapBuilder.flightPlans, [{
                        dataProvider,
                        pathRenderer,
                        drawEntirePlan
                    }], configure, order)
                    .withDestroy('activeFlightPlanProvider', () => {
                    dataProvider.destroy();
                });
            }
        }
        /**
         * Configures a map builder to generate a map which supports displaying a highlighted waypoint, and optionally
         * drawing a line from the highlighted waypoint to the position of the player airplane.
         *
         * If a text layer has already been added to the builder, its order will be changed so that it is rendered above the
         * highlighted waypoint layers. Otherwise, a text layer will be added to the builder after the highlighted waypoint
         * layers.
         *
         * Adds the following...
         *
         * Context properties:
         * * `[MapSystemKeys.TextManager]: MapCullableTextLabelManager`
         * * `[MapSystemKeys.WaypointRenderer]: MapWaypointRenderer`
         * * `[GarminMapKeys.WaypointDisplayBuilder]: MapWaypointDisplayBuilder`
         *
         * Modules:
         * * `[GarminMapKeys.WaypointHighlight]: MapWaypointHighlightModule`
         *
         * Layers:
         * * `[GarminMapKeys.WaypointHighlightLine]: MapLineLayer` (only if line support is included)
         * * `[GarminMapKeys.WaypointHighlight]: MapWaypointHighlightLayer`
         * * `[MapSystemKeys.TextLayer]: MapCullableTextLayer`
         *
         * Controllers:
         * * `[MapSystemKeys.WaypointRenderer]: MapSystemCustomController` (handles initialization and updating of the
         * waypoint renderer)
         * @param mapBuilder The map builder to configure.
         * @param includeLine Whether to include support for drawing a line from the highlighted waypoint to the player
         * airplane.
         * @param configure A function used to configure the display and styling of highlighted waypoint icons and labels.
         * @param lineOptions Styling options for the waypoint highlight line. The default values are the same as for
         * {@link MapLineLayer}, except the `strokeDash` property defaults to `[5, 3, 2, 3]`. Ignored if `includeLine` is
         * `false`.
         * @param order The order to assign to the highlighted waypoint layers. Layers with lower assigned order will be
         * attached to the map before and appear below layers with greater assigned order values. Defaults to the number of
         * layers already added to the map builder.
         * @returns The map builder, after it has been configured.
         */
        static waypointHighlight(mapBuilder, includeLine, configure, lineOptions, order) {
            mapBuilder
                .withTextLayer(true)
                .withModule(GarminMapKeys.WaypointHighlight, () => new MapWaypointHighlightModule())
                .withContext(msfsSdk.MapSystemKeys.WaypointRenderer, context => new MapWaypointRenderer(context[msfsSdk.MapSystemKeys.TextManager]))
                .withContext(GarminMapKeys.WaypointDisplayBuilder, () => new MapWaypointDisplayBuilderClass());
            if (includeLine) {
                const waypointLocation = new msfsSdk.GeoPoint(0, 0);
                const waypointLocationSubject = msfsSdk.Subject.create(null);
                let waypointLocationSub;
                let controller;
                mapBuilder
                    .withLayer(GarminMapKeys.WaypointHighlightLine, context => {
                    var _a;
                    const options = Object.assign({}, lineOptions);
                    (_a = options.strokeDash) !== null && _a !== void 0 ? _a : (options.strokeDash = [5, 3, 2, 3]);
                    return (msfsSdk.FSComponent.buildComponent(msfsSdk.MapLineLayer, Object.assign({ model: context.model, mapProjection: context.projection, start: context.model.getModule(msfsSdk.MapSystemKeys.OwnAirplaneProps).position, end: waypointLocationSubject }, options)));
                })
                    .withController('waypointHighlightLineEndSubject', context => {
                    return controller = new msfsSdk.MapSystemGenericController(context, {
                        onAfterMapRender: (contextArg) => {
                            contextArg.model.getModule(GarminMapKeys.WaypointHighlight).waypoint.sub(waypoint => {
                                if (waypoint === null) {
                                    waypointLocationSub === null || waypointLocationSub === void 0 ? void 0 : waypointLocationSub.destroy();
                                    waypointLocationSub = undefined;
                                    waypointLocationSubject.set(null);
                                }
                                else {
                                    waypointLocationSub === null || waypointLocationSub === void 0 ? void 0 : waypointLocationSub.destroy();
                                    waypointLocationSub = waypoint.location.sub(location => {
                                        waypointLocation.set(location);
                                        if (waypointLocationSubject.get() === waypointLocation) {
                                            waypointLocationSubject.notify();
                                        }
                                        else {
                                            waypointLocationSubject.set(waypointLocation);
                                        }
                                    }, true);
                                }
                            }, true);
                        },
                        onMapDestroyed: () => {
                            controller.destroy();
                        },
                        onDestroyed: () => {
                            waypointLocationSub === null || waypointLocationSub === void 0 ? void 0 : waypointLocationSub.destroy();
                        }
                    });
                });
            }
            const layerCount = mapBuilder.layerCount;
            return mapBuilder
                .withLayer(GarminMapKeys.WaypointHighlight, (context) => {
                return (msfsSdk.FSComponent.buildComponent(MapWaypointHighlightLayer, { model: context.model, mapProjection: context.projection, waypointRenderer: context[msfsSdk.MapSystemKeys.WaypointRenderer] }));
            }, order)
                .withLayerOrder(msfsSdk.MapSystemKeys.TextLayer, order !== null && order !== void 0 ? order : layerCount)
                .withInit('waypointHighlightLayerDisplayConfigure', context => {
                configure(context[GarminMapKeys.WaypointDisplayBuilder]);
            })
                .withController(msfsSdk.MapSystemKeys.WaypointRenderer, context => new msfsSdk.MapSystemGenericController(context, {
                onAfterMapRender: () => { context[GarminMapKeys.WaypointDisplayBuilder].apply(context[msfsSdk.MapSystemKeys.WaypointRenderer]); },
                onAfterUpdated: () => { context[msfsSdk.MapSystemKeys.WaypointRenderer].update(context.projection); }
            }));
        }
        /**
         * Configures a map builder to generate a map which displays airspaces, and optionally binds the visibility of
         * airspaces to user settings.
         *
         * Adds the following...
         *
         * Context properties:
         * * `[MapSystemKeys.AirspaceManager]: GenericAirspaceRenderManager`
         *
         * Modules:
         * * `[MapSystemKeys.Airspace]: MapAirspaceModule`
         *
         * Layers:
         * * `[MapSystemKeys.Airspace]: MapAirspaceLayer`
         *
         * Controllers:
         * * `[GarminMapKeys.AirspaceVisibility]: MapAirspaceVisController` (only with user settings support)
         * @param mapBuilder The map builder to configure.
         * @param settingManager A setting manager containing the user settings controlling airspace visibility. If not
         * defined, airspace visibility will not be controlled by user settings.
         * @param order The order to assign to the airspace layer. Layers with lower assigned order will be attached to the
         * map before and appear below layers with greater assigned order values. Defaults to the number of layers already
         * added to the map builder.
         * @returns The map builder, after it has been configured.
         */
        static airspaces(mapBuilder, settingManager, order) {
            mapBuilder
                .withAirspaces(msfsSdk.DefaultLodBoundaryCache.getCache(), GarminAirspaceShowTypeMap.MAP, MapAirspaceRendering.selectRenderer, MapAirspaceRendering.renderOrder, undefined, order);
            if (settingManager !== undefined) {
                mapBuilder.with(GarminMapBuilder.airspaceVisSettings, settingManager);
            }
            return mapBuilder;
        }
        /**
         * Configures a map builder to bind the visibility of airspaces to user settings.
         *
         * Requires the modules defined in {@link MapAirspaceVisControllerModules}.
         *
         * Adds the controller `[GarminMapKeys.AirspaceVisibility]: MapAirspaceVisController`.
         * @param mapBuilder The map builder to configure.
         * @param settingManager A setting manager containing the user settings controlling airspace visibility.
         * @returns The map builder, after it has been configured.
         */
        static airspaceVisSettings(mapBuilder, settingManager) {
            return mapBuilder.withController(GarminMapKeys.AirspaceVisibility, context => new MapAirspaceVisController(context, settingManager));
        }
        /**
         * Configures a map builder to generate a map which displays TCAS intruders.
         *
         * Adds the following...
         *
         * Modules:
         * * `[GarminMapKeys.Range]: MapIndexedRangeModule`
         * * `[MapSystemKeys.OwnAirplaneProps]: MapOwnAirplanePropsModule`
         * * `[MapSystemKeys.FollowAirplane]: MapFollowAirplaneModule`
         * * `[MapSystemKeys.Traffic]: MapTrafficModule`
         * * `[GarminMapKeys.Traffic]: MapGarminTrafficModule`
         *
         * Layers:
         * * `[MapSystemKeys.Traffic]: MapSystemTrafficLayer`
         *
         * Controllers:
         * * `[MapSystemKeys.Traffic]: MapTrafficController`
         * * `[GarminMapKeys.Traffic]: MapGarminTrafficController` (only with user settings support)
         * @param mapBuilder The map builder to configure.
         * @param trafficSystem The traffic system from which to derive intruder data.
         * @param iconOptions Configuration options for intruder icons.
         * @param useOuterRangeAsOffScale Whether to use the outer traffic range defined in {@link MapGarminTrafficModule} as
         * the off-scale traffic range.
         * @param offScaleStatus A mutable subscribable to update with the layer's off-scale traffic status.
         * @param iconFactory A function which creates intruder icons for the traffic display. If not defined, a default icon
         * of type {@link MapTrafficIntruderIcon} is created for each intruder.
         * @param initCanvasStyles A function which initializes global canvas styles for the traffic display.
         * @param trafficSettingManager A setting manager containing user settings controlling the operation of the traffic
         * system. If not defined, the display of map traffic will not be controlled by those settings.
         * @param mapSettingManager A setting manager containing user settings controlling the display of traffic on maps. If
         * not defined, the display of map traffic will not be controlled by those settings.
         * @param order The order to assign to the traffic layer. Layers with lower assigned order will be attached to the
         * map before and appear below layers with greater assigned order values. Defaults to the number of layers already
         * added to the map builder.
         * @returns The map builder, after it has been configured.
         */
        static traffic(mapBuilder, trafficSystem, iconOptions, useOuterRangeAsOffScale, offScaleStatus, iconFactory, initCanvasStyles, trafficSettingManager, mapSettingManager, order) {
            const canvasFont = `${iconOptions.fontSize}px ${iconOptions.font}`;
            let offScaleOobOptions;
            if (offScaleStatus !== undefined) {
                offScaleOobOptions = (context) => {
                    const offScaleIntruders = msfsSdk.SetSubject.create();
                    const oobIntruders = msfsSdk.SetSubject.create();
                    const alertLevelSubs = new Map();
                    const offScaleTAs = msfsSdk.SetSubject.create();
                    const offScaleRAs = msfsSdk.SetSubject.create();
                    const handler = (set, type, intruder) => {
                        var _a;
                        if (type === msfsSdk.SubscribableSetEventType.Added) {
                            alertLevelSubs.set(intruder, intruder.alertLevel.sub(alertLevel => {
                                if (alertLevel === msfsSdk.TcasAlertLevel.ResolutionAdvisory) {
                                    offScaleRAs.add(intruder);
                                    offScaleTAs.delete(intruder);
                                }
                                else if (alertLevel === msfsSdk.TcasAlertLevel.TrafficAdvisory) {
                                    offScaleTAs.add(intruder);
                                    offScaleRAs.delete(intruder);
                                }
                                else {
                                    offScaleTAs.delete(intruder);
                                    offScaleRAs.delete(intruder);
                                }
                            }, true));
                        }
                        else {
                            (_a = alertLevelSubs.get(intruder)) === null || _a === void 0 ? void 0 : _a.destroy();
                            alertLevelSubs.delete(intruder);
                            offScaleTAs.delete(intruder);
                            offScaleRAs.delete(intruder);
                        }
                    };
                    offScaleIntruders.sub(handler);
                    oobIntruders.sub(handler);
                    const raTAHandler = () => {
                        if (offScaleRAs.get().size > 0) {
                            offScaleStatus.set(exports.MapTrafficOffScaleStatus.RA);
                        }
                        else if (offScaleTAs.get().size > 0) {
                            offScaleStatus.set(exports.MapTrafficOffScaleStatus.TA);
                        }
                        else {
                            offScaleStatus.set(exports.MapTrafficOffScaleStatus.None);
                        }
                    };
                    offScaleTAs.sub(raTAHandler);
                    offScaleRAs.sub(raTAHandler);
                    raTAHandler();
                    return {
                        offScaleIntruders,
                        oobIntruders,
                        oobOffset: context.deadZone
                    };
                };
            }
            iconFactory !== null && iconFactory !== void 0 ? iconFactory : (iconFactory = (intruder, context) => new MapTrafficIntruderIcon(intruder, context.model.getModule(msfsSdk.MapSystemKeys.Traffic), context.model.getModule(msfsSdk.MapSystemKeys.OwnAirplaneProps), context.model.getModule(GarminMapKeys.Traffic), context.model.getModule(msfsSdk.MapSystemKeys.FollowAirplane), iconOptions));
            initCanvasStyles !== null && initCanvasStyles !== void 0 ? initCanvasStyles : (initCanvasStyles = (canvasContext) => {
                canvasContext.textAlign = 'center';
                canvasContext.font = canvasFont;
            });
            mapBuilder
                .withModule(GarminMapKeys.Range, () => new msfsSdk.MapIndexedRangeModule())
                .withModule(msfsSdk.MapSystemKeys.FollowAirplane, () => new msfsSdk.MapFollowAirplaneModule())
                .withModule(GarminMapKeys.Traffic, () => new MapGarminTrafficModule(trafficSystem))
                .withTraffic(trafficSystem, iconFactory, initCanvasStyles, offScaleOobOptions, order)
                .withController(msfsSdk.MapSystemKeys.Traffic, context => {
                return new MapTrafficController(context, useOuterRangeAsOffScale);
            });
            if (trafficSettingManager !== undefined) {
                mapBuilder.withController(GarminMapKeys.Traffic, context => new MapGarminTrafficController(context, trafficSettingManager, mapSettingManager));
            }
            return mapBuilder;
        }
        /**
         * Configures a map builder to generate a map which supports multiple indexed traffic map ranges, with optional
         * support for controlling the map range with a user setting. At each range index there is an outer range and an
         * inner range, except for the first index, at which there is just an outer range. The inner range is always the
         * largest range in the range array that is less than the outer range.
         *
         * Requires the module `[GarminMapKeys.Traffic]: MapGarminTrafficModule`.
         *
         * Adds the following...
         *
         * Context properties:
         * * `[GarminMapKeys.UseRangeSetting]: ResourceModerator<Subject<boolean>>` (only if user setting support is enabled)
         *
         * Modules:
         * * `[GarminMapKeys.Range]: MapIndexedRangeModule`
         *
         * Controllers:
         * * `[GarminMapKeys.RangeRTR]: MapRangeRTRController`
         * * `[GarminMapKeys.TrafficRange]: TrafficMapRangeController` (can be used to control map range)
         * * `'useRangeSettingDefault': MapSystemCustomController` (only if user setting support is enabled)
         * @param mapBuilder The map builder to configure.
         * @param nauticalRangeArray The map range array to use for nautical distance mode. If not defined, a range array
         * will not automatically be set when entering nautical distance mode.
         * @param metricRangeArray The map range array to use for metric distance mode. If not defined, a range array will
         * not automatically be set when entering metric distance mode.
         * @param settingManager A setting manager containing a user setting to control the map range. If not defined, range
         * will not be controlled by a user setting.
         * @param useRangeSettingByDefault Whether the range user setting should control the map range by default. Defaults
         * to `true`. Is ignored if `settingManager` is undefined.
         * @returns The map builder, after it has been configured.
         */
        static trafficRange(mapBuilder, nauticalRangeArray, metricRangeArray, settingManager, useRangeSettingByDefault = true) {
            const useSetting = settingManager === undefined ? undefined : msfsSdk.Subject.create(true);
            if (useSetting) {
                const defaultUseRangeSettingConsumer = {
                    priority: 0,
                    onAcquired: (useRangeSetting) => {
                        useRangeSetting.set(useRangeSettingByDefault);
                    },
                    onCeded: () => { }
                };
                let useRangeSettingDefaultController;
                mapBuilder
                    .withContext(GarminMapKeys.UseRangeSetting, () => new msfsSdk.ResourceModerator(useSetting))
                    .withController('useRangeSettingDefault', context => useRangeSettingDefaultController = new msfsSdk.MapSystemGenericController(context, {
                    onAfterMapRender: (contextArg) => {
                        contextArg[GarminMapKeys.UseRangeSetting].claim(defaultUseRangeSettingConsumer);
                    },
                    onMapDestroyed: () => {
                        useRangeSettingDefaultController.destroy();
                    },
                    onDestroyed: (contextArg) => {
                        contextArg[GarminMapKeys.UseRangeSetting].forfeit(defaultUseRangeSettingConsumer);
                    }
                }));
            }
            return mapBuilder
                .withModule(GarminMapKeys.Range, () => new msfsSdk.MapIndexedRangeModule())
                .withController(GarminMapKeys.RangeRTR, context => new MapRangeRTRController(context))
                .withController(GarminMapKeys.TrafficRange, context => {
                return new TrafficMapRangeController(context, nauticalRangeArray, metricRangeArray, settingManager, useSetting);
            });
        }
        /**
         * Configures a map builder to generate a map which displays traffic range rings. There are two rings: an outer and
         * an inner ring. Each ring has tick marks at the 12 clock positions, with major ticks at the 4 cardinal positions.
         * Each ring also has an optional label which displays the range marked by the ring.
         *
         * Requires the modules defined in {@link TrafficMapRangeLayerModules}.
         *
         * Adds the layer `[GarminMapKeys.TrafficRange]: TrafficMapRangeLayer`.
         * @param mapBuilder The map builder to configure.
         * @param ringOptions Styling options for the rings.
         * @param order The order to assign to the ring layer. Layers with lower assigned order will be attached to the map
         * before and appear below layers with greater assigned order values. Defaults to the number of layers already added
         * to the map builder.
         * @returns The map builder, after it has been configured.
         */
        static trafficRangeRings(mapBuilder, ringOptions, order) {
            return mapBuilder.withLayer(GarminMapKeys.TrafficRange, context => {
                return (msfsSdk.FSComponent.buildComponent(TrafficMapRangeLayer, Object.assign({ model: context.model, mapProjection: context.projection }, ringOptions)));
            }, order);
        }
        /**
         * Configures a map builder to generate a map with pointer support. Activating the pointer allows the pointer to
         * control map panning and stops the map from actively rotating.
         *
         * If map target, orientation, or rotation control resource moderators exist on the map context, the panning RTR
         * controller will attempt to claim those resources with a priority of `100`. Otherwise, the controller assumes
         * nothing else controls the map target or rotation.
         *
         * Adds the following...
         *
         * Modules:
         * * `[GarminMapKeys.Panning]: MapPanningModule`
         * * `[GarminMapKeys.Pointer]: MapPointerModule`
         *
         * Layers:
         * * `[GarminMapKeys.Pointer]: MapPointerLayer`
         *
         * Controllers:
         * * `[GarminMapKeys.PanningRTR]: MapPanningRTRController`
         * * `[GarminMapKeys.Pointer]: MapPointerController` (can be used to control the behavior of the pointer)
         * * `[GarminMapKeys.PointerRTR]: MapPointerRTRController`
         * @param mapBuilder The map builder to configure.
         * @param pointerBoundsOffset The offset of the boundary surrounding the area in which the pointer can freely move,
         * from the edge of the projected map, excluding the dead zone, or a subscribable which provides it. Expressed as
         * `[left, top, right, bottom]`, relative to the width and height, as appropriate, of the projected map window. A
         * positive offset is directed toward the center of the map.
         * @param icon The pointer icon to render, as a VNode. If not default, a default icon will be rendered.
         * @param order The order to assign to the pointer layer. Layers with lower assigned order will be attached to the
         * map before and appear below layers with greater assigned order values. Defaults to the number of layers already
         * added to the map builder.
         * @returns The map builder, after it has been configured.
         */
        static pointer(mapBuilder, pointerBoundsOffset, icon, order) {
            return mapBuilder
                .withModule(GarminMapKeys.Panning, () => new MapPanningModule())
                .withModule(GarminMapKeys.Pointer, () => new MapPointerModule())
                .withLayer(GarminMapKeys.Pointer, (context) => {
                return (msfsSdk.FSComponent.buildComponent(MapPointerLayer, { model: context.model, mapProjection: context.projection }, icon));
            }, order)
                .withController(GarminMapKeys.PanningRTR, context => new MapPanningRTRController(context))
                .withController(GarminMapKeys.Pointer, context => new MapPointerController(context))
                .withController(GarminMapKeys.PointerRTR, context => {
                return new MapPointerRTRController(context, pointerBoundsOffset);
            });
        }
        /**
         * Configures a map builder to generate a map which displays a pointer information box when the pointer is active.
         *
         * Requires the modules defined in {@link MapPointerInfoLayerModules}.
         *
         * Adds the layer `[GarminMapKeys.PointerInfo]: MapPointerInfoLayer`.
         * @param mapBuilder The map builder to configure.
         * @param size The size of the pointer information box.
         * @param order The order to assign to the pointer information layer. Layers with lower assigned order will be
         * attached to the map before and appear below layers with greater assigned order values. Defaults to the number of
         * layers already added to the map builder.
         * @returns The map builder, after it has been configured.
         */
        static pointerInfo(mapBuilder, size, order) {
            return mapBuilder.withLayer(GarminMapKeys.PointerInfo, (context) => {
                return (msfsSdk.FSComponent.buildComponent(MapPointerInfoLayer, { model: context.model, mapProjection: context.projection, size: size }));
            }, order);
        }
        /**
         * Configures the map builder to generate a map which supports flight plan focus. Flight plan focus automatically
         * adjusts the map's target and range to place a portion of a flight plan into view.
         *
         * Adds the following...
         *
         * Modules:
         * * `[GarminMapKeys.FlightPlanFocus]: MapFlightPlanFocusModule`
         *
         * Controllers:
         * * `[GarminMapKeys.FlightPlanFocus]: MapFlightPlanFocusRTRController`
         * @param mapBuilder The map builder to configure.
         * @param nominalFocusMargins A subscribable which provides the nominal focus margins, as
         * `[left, top, right, bottom]` in pixels. The nominal margins define the offset of the boundaries of the focus
         * region relative to the map's projected window, *excluding* the dead zone. Positive values represent offsets
         * toward the center of the window. When the flight plan is focused, the focused elements of the plan are guaranteed
         * to be contained within the focus region.
         * @param defaultFocusRangeIndex The default map range index to apply when the flight plan focus consists of only a
         * single point in space.
         * @param focusDebounceDelay The debounce delay, in milliseconds, to apply to focus target calculations when the
         * flight plan focus changes. Defaults to 500 milliseconds.
         * @returns The map builder, after it has been configured.
         */
        static flightPlanFocus(mapBuilder, nominalFocusMargins, defaultFocusRangeIndex = 0, focusDebounceDelay = 500) {
            const definedNominalFocusMargins = nominalFocusMargins !== null && nominalFocusMargins !== void 0 ? nominalFocusMargins : msfsSdk.Subject.create(msfsSdk.VecNMath.create(4));
            return mapBuilder
                .withModule(GarminMapKeys.FlightPlanFocus, () => new MapFlightPlanFocusModule())
                .withController(GarminMapKeys.FlightPlanFocus, context => {
                return new MapFlightPlanFocusRTRController(context, 'isSubscribable' in definedNominalFocusMargins ? definedNominalFocusMargins : msfsSdk.Subject.create(definedNominalFocusMargins), defaultFocusRangeIndex, focusDebounceDelay);
            });
        }
        /**
         * Configures a map builder to generate a map with an altitude intercept arc, and optionally binds the display of the
         * arc to a user setting.
         *
         * Adds the following...
         *
         * Modules:
         * * `[MapSystemKeys.AltitudeArc]: MapAltitudeArcModule`
         *
         * Layers:
         * * `[MapSystemKeys.AltitudeArc]: MapAltitudeArcLayer`
         *
         * Controllers:
         * * `[MapSystemKeys.AltitudeArc]: MapBindingsController` (only if user settings are supported)
         * @param mapBuilder The map builder to configure.
         * @param options Options for the arc.
         * @param settingManager A setting manager containing user settings used to control the display of the arc. If not
         * defined, the display of the arc will not be bound to user settings.
         * @param order The order to assign to the altitude arc layer. Layers with lower assigned order will be attached to
         * the map before and appear below layers with greater assigned order values. Defaults to the number of layers
         * already added to the map builder.
         * @returns The map builder, after it has been configured.
         */
        static altitudeArc(mapBuilder, options, settingManager, order) {
            mapBuilder
                .withModule(msfsSdk.MapSystemKeys.AltitudeArc, () => new msfsSdk.MapAltitudeArcModule())
                .withLayer(msfsSdk.MapSystemKeys.AltitudeArc, context => {
                return (msfsSdk.FSComponent.buildComponent(msfsSdk.MapAltitudeArcLayer, Object.assign({ model: context.model, mapProjection: context.projection }, options)));
            }, order);
            if ((settingManager === null || settingManager === void 0 ? void 0 : settingManager.tryGetSetting('mapAltitudeArcShow')) !== undefined) {
                mapBuilder.withBindings(msfsSdk.MapSystemKeys.AltitudeArc, context => {
                    return [
                        {
                            source: settingManager.getSetting('mapAltitudeArcShow'),
                            target: context.model.getModule(msfsSdk.MapSystemKeys.AltitudeArc).show
                        }
                    ];
                });
            }
            return mapBuilder;
        }
        /**
         * Configures a map builder to generate a map with a track vector, and optionally binds the display options of the
         * vector to user settings.
         *
         * Adds the following...
         *
         * Modules:
         * * `[GarminMapKeys.TrackVector]: MapTrackVectorModule`
         *
         * Layers:
         * * `[GarminMapKeys.TrackVector]: MapTrackVectorLayer`
         *
         * Controllers:
         * * `[GarminMapKeys.TrackVector]: MapBindingsController` (only if user settings are supported)
         * @param mapBuilder The map builder to configure.
         * @param options Options for the track vector.
         * @param settingManager A setting manager containing user settings used to control the track vector. If not defined,
         * the track vector will not be bound to user settings.
         * @param order The order to assign to the track vector layer. Layers with lower assigned order will be attached to
         * the map before and appear below layers with greater assigned order values. Defaults to the number of layers
         * already added to the map builder.
         * @returns The map builder, after it has been configured.
         */
        static trackVector(mapBuilder, options, settingManager, order) {
            mapBuilder
                .withModule(GarminMapKeys.TrackVector, () => new MapTrackVectorModule())
                .withLayer(GarminMapKeys.TrackVector, context => {
                return (msfsSdk.FSComponent.buildComponent(MapTrackVectorLayer, Object.assign({ model: context.model, mapProjection: context.projection }, options)));
            }, order);
            const showSetting = settingManager === null || settingManager === void 0 ? void 0 : settingManager.tryGetSetting('mapTrackVectorShow');
            const lookaheadSetting = settingManager === null || settingManager === void 0 ? void 0 : settingManager.tryGetSetting('mapTrackVectorLookahead');
            if (showSetting !== undefined || lookaheadSetting !== undefined) {
                mapBuilder.withBindings(GarminMapKeys.TrackVector, context => {
                    const seconds = msfsSdk.UnitType.SECOND.createNumber(0);
                    const bindings = [];
                    if (showSetting !== undefined) {
                        bindings.push({
                            source: showSetting,
                            target: context.model.getModule(GarminMapKeys.TrackVector).show
                        });
                    }
                    if (lookaheadSetting !== undefined) {
                        bindings.push({
                            source: lookaheadSetting,
                            target: context.model.getModule(GarminMapKeys.TrackVector).lookaheadTime,
                            map: (source) => seconds.set(source)
                        });
                    }
                    return bindings;
                });
            }
            return mapBuilder;
        }
        /**
         * Configures a map builder to generate a map with a wind vector, and optionally binds the display options of the
         * vector to user settings.
         *
         * Adds the following...
         *
         * Modules:
         * * `[GarminMapKeys.WindVector]: MapWindVectorModule`
         *
         * Layers:
         * * `[GarminMapKeys.WindVector]: MapWindVectorLayer`
         *
         * Controllers:
         * * `[GarminMapKeys.WindVector]: MapWindVectorController` (only if user settings are supported)
         * @param mapBuilder The map builder to configure.
         * @param dataProvider A provider of wind data.
         * @param settingManager A setting manager containing user settings used to control the wind vector. If not defined,
         * the wind vector will not be bound to user settings.
         * @param order The order to assign to the wind vector layer. Layers with lower assigned order will be attached to
         * the map before and appear below layers with greater assigned order values. Defaults to the number of layers
         * already added to the map builder.
         * @returns The map builder, after it has been configured.
         */
        static windVector(mapBuilder, dataProvider, settingManager, order) {
            mapBuilder
                .withModule(GarminMapKeys.WindVector, () => new MapWindVectorModule(dataProvider))
                .withLayer(GarminMapKeys.WindVector, context => {
                return (msfsSdk.FSComponent.buildComponent(MapWindVectorLayer, { model: context.model, mapProjection: context.projection }));
            }, order);
            if (settingManager) {
                mapBuilder.withController(GarminMapKeys.WindVector, context => new MapWindVectorController(context, settingManager));
            }
            return mapBuilder;
        }
        /**
         * Configures a map builder to generate a map which displays a miniature compass.
         *
         * Adds the layer `GarminMapKeys.MiniCompass: MapMiniCompassLayer`.
         * @param mapBuilder The map builder to configure.
         * @param imgSrc The URI of the mini-compass's image asset.
         * @param order The order to assign to the mini-compass layer. Layers with lower assigned order will be attached to
         * the map before and appear below layers with greater assigned order values. Defaults to the number of layers
         * already added to the map builder.
         * @returns The map builder, after it has been configured.
         */
        static miniCompass(mapBuilder, imgSrc, order) {
            return mapBuilder.withLayer(GarminMapKeys.MiniCompass, context => {
                return (msfsSdk.FSComponent.buildComponent(MapMiniCompassLayer, { model: context.model, mapProjection: context.projection, imgSrc: imgSrc }));
            }, order);
        }
        /**
         * Configures a map builder to generate a map which supports a flight plan procedure preview.
         *
         * If a text layer has already been added to the builder, its order will be changed so that it is rendered above the
         * highlighted waypoint layers. Otherwise, a text layer will be added to the builder after the highlighted waypoint
         * layers.
         *
         * Adds the following...
         *
         * Context properties:
         * * `[MapSystemKeys.TextManager]: MapCullableTextLabelManager`
         * * `[MapSystemKeys.WaypointRenderer]: MapWaypointRenderer`
         * * `[GarminMapKeys.WaypointDisplayBuilder]: MapWaypointDisplayBuilder`
         *
         * Modules:
         * * `[GarminMapKeys.ProcedurePreview]: MapProcedurePreviewModule`
         *
         * Layers:
         * * `[GarminMapKeys.ProcedurePreview]: MapProcedurePreviewLayer`
         * * `[MapSystemKeys.TextLayer]: MapCullableTextLayer`
         *
         * Controllers:
         * * `[MapSystemKeys.WaypointRenderer]: MapSystemCustomController` (handles initialization and updating of the
         * waypoint renderer)
         * @param mapBuilder The map builder to configure.
         * @param pathRenderer The flight path renderer to use to render the procedure preview.
         * @param configure A function used to configure the display and styling of procedure preview waypoint icons and
         * labels.
         * @param order The order to assign to the highlighted waypoint layers. Layers with lower assigned order will be
         * attached to the map before and appear below layers with greater assigned order values. Defaults to the number of
         * layers already added to the map builder.
         * @returns The map builder, after it has been configured.
         */
        static procPreview(mapBuilder, pathRenderer, configure, order) {
            mapBuilder
                .withTextLayer(true)
                .withModule(GarminMapKeys.ProcedurePreview, () => new MapProcedurePreviewModule())
                .withContext(msfsSdk.MapSystemKeys.WaypointRenderer, context => new MapWaypointRenderer(context[msfsSdk.MapSystemKeys.TextManager]))
                .withContext(GarminMapKeys.WaypointDisplayBuilder, () => new MapWaypointDisplayBuilderClass());
            const layerCount = mapBuilder.layerCount;
            return mapBuilder
                .withLayer(GarminMapKeys.ProcedurePreview, (context) => {
                return (msfsSdk.FSComponent.buildComponent(MapProcedurePreviewLayer, { model: context.model, mapProjection: context.projection, bus: context.bus, waypointRenderer: context[msfsSdk.MapSystemKeys.WaypointRenderer], pathRenderer: pathRenderer }));
            }, order)
                .withLayerOrder(msfsSdk.MapSystemKeys.TextLayer, order !== null && order !== void 0 ? order : layerCount)
                .withInit('procPreviewLayerDisplayConfigure', context => {
                configure(context[GarminMapKeys.WaypointDisplayBuilder]);
            })
                .withController(msfsSdk.MapSystemKeys.WaypointRenderer, context => new msfsSdk.MapSystemGenericController(context, {
                onAfterMapRender: () => { context[GarminMapKeys.WaypointDisplayBuilder].apply(context[msfsSdk.MapSystemKeys.WaypointRenderer]); },
                onAfterUpdated: () => { context[msfsSdk.MapSystemKeys.WaypointRenderer].update(context.projection); }
            }));
        }
        /**
         * Configures a map builder to include an indicator group.
         * @param mapBuilder The map builder to configure.
         * @param key The key of the indicator group.
         * @param indicatorFactories An array of functions which create the indicators. The order of functions in the array
         * determines the order in which the created indicators will be added to the group.
         * @param callbacks Optional callback functions to register with the indicator group.
         * @param cssClass The CSS class(es) to apply to the root of the indicator group.
         * @param order The order to assign to the layer containing the indicator group. Layers with lower assigned order
         * will be attached to the map before and appear below layers with greater assigned order values. Defaults to the
         * number of layers already added to the map builder.
         * @returns The map builder, after it has been configured.
         */
        static indicatorGroup(mapBuilder, key, indicatorFactories, callbacks, cssClass, order) {
            return mapBuilder.withLayer(key, context => {
                return (msfsSdk.FSComponent.buildComponent(msfsSdk.MapGenericLayer, Object.assign({ model: context.model, mapProjection: context.projection }, callbacks, { class: cssClass }), indicatorFactories.map(factory => factory(context))));
            }, order);
        }
    }

    /**
     * Displays a map banner message.
     */
    class MapBannerIndicator extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.cssClassSet = msfsSdk.SetSubject.create(['map-banner']);
        }
        /** @inheritdoc */
        onAfterRender() {
            this.showSub = this.props.show.sub(show => {
                if (show) {
                    this.cssClassSet.add('map-banner-on');
                    this.cssClassSet.delete('map-banner-off');
                }
                else {
                    this.cssClassSet.delete('map-banner-on');
                    this.cssClassSet.add('map-banner-off');
                }
            }, true);
        }
        /** @inheritdoc */
        render() {
            if (this.props.class !== undefined) {
                if (typeof this.props.class === 'object') {
                    this.classSub = msfsSdk.FSComponent.bindCssClassSet(this.cssClassSet, this.props.class, MapBannerIndicator.RESERVED_CLASSES);
                }
                else {
                    const classesToAdd = msfsSdk.FSComponent.parseCssClassesFromString(this.props.class).filter(val => !MapBannerIndicator.RESERVED_CLASSES.includes(val));
                    for (const cssClass of classesToAdd) {
                        this.cssClassSet.add(cssClass);
                    }
                }
            }
            return (msfsSdk.FSComponent.buildComponent("div", { class: this.cssClassSet }, this.props.children));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b;
            super.destroy();
            (_a = this.showSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.classSub) === null || _b === void 0 ? void 0 : _b.destroy();
        }
    }
    MapBannerIndicator.RESERVED_CLASSES = ['map-banner', 'map-banner-on', 'map-banner-off'];

    /**
     * Displays a map detail level indication.
     */
    class MapDetailIndicator extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.rootRef = msfsSdk.FSComponent.createRef();
        }
        /** @inheritdoc */
        onAfterRender() {
            this.modeSub = this.props.declutterMode.sub(this.onDeclutterModeChanged.bind(this), true);
        }
        /**
         * A callback which is called when the declutter mode setting value changes.
         * @param mode The new declutter mode setting value.
         */
        onDeclutterModeChanged(mode) {
            this.rootRef.instance.classList.remove('detail-4', 'detail-3', 'detail-2', 'detail-1');
            this.rootRef.instance.classList.add(MapDetailIndicator.MODE_CLASSES[mode]);
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { ref: this.rootRef, class: 'map-detail' },
                this.props.showTitle ? msfsSdk.FSComponent.buildComponent("div", null, "Detail") : null,
                msfsSdk.FSComponent.buildComponent("div", { class: 'map-detail-icon' },
                    msfsSdk.FSComponent.buildComponent("div", { class: 'map-detail-block map-detail-top' }),
                    msfsSdk.FSComponent.buildComponent("div", { class: 'map-detail-block map-detail-middle' }),
                    msfsSdk.FSComponent.buildComponent("div", { class: 'map-detail-block map-detail-bottom' }),
                    msfsSdk.FSComponent.buildComponent("div", { class: 'map-detail-clip' }))));
        }
        /** @inheritdoc */
        destroy() {
            var _a;
            super.destroy();
            (_a = this.modeSub) === null || _a === void 0 ? void 0 : _a.destroy();
        }
    }
    MapDetailIndicator.MODE_CLASSES = {
        [exports.MapDeclutterMode.All]: 'detail-4',
        [exports.MapDeclutterMode.Level3]: 'detail-3',
        [exports.MapDeclutterMode.Level2]: 'detail-2',
        [exports.MapDeclutterMode.Level1]: 'detail-1',
    };

    /**
     * Displays a map orientation indication.
     */
    class MapOrientationIndicator extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.text = this.props.orientation.map(mode => {
                var _a;
                return (_a = this.props.text[mode]) !== null && _a !== void 0 ? _a : '';
            });
            this.rootStyle = msfsSdk.ObjectSubject.create({ visibility: '' });
        }
        /** @inheritdoc */
        onAfterRender() {
            this.isVisibleSub = this.props.isVisible.sub(isVisible => {
                this.rootStyle.set('visibility', isVisible ? '' : 'hidden');
            }, true);
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { style: this.rootStyle, class: 'map-orientation' }, this.text));
        }
        /** @inheritdoc */
        destroy() {
            var _a;
            super.destroy();
            this.text.destroy();
            (_a = this.isVisibleSub) === null || _a === void 0 ? void 0 : _a.destroy();
        }
    }

    /**
     * Displays a relative terrain mode status indication.
     */
    class MapRelativeTerrainStatusIndicator extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.rootStyle = msfsSdk.ObjectSubject.create({ display: '' });
            this.failedStyle = msfsSdk.ObjectSubject.create({ display: '' });
        }
        /** @inheritdoc */
        onAfterRender() {
            this.showSub = this.props.show.sub(show => {
                this.rootStyle.set('display', show ? '' : 'none');
            }, true);
            this.isFailedSub = this.props.isFailed.sub(isFailed => {
                this.failedStyle.set('display', isFailed ? '' : 'none');
            }, true);
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { style: this.rootStyle, class: 'map-rel-terrain-status' },
                msfsSdk.FSComponent.buildComponent("div", { class: 'map-rel-terrain-status-icon-container' },
                    msfsSdk.FSComponent.buildComponent("img", { src: this.props.iconFilePath, class: 'map-rel-terrain-status-icon' }),
                    msfsSdk.FSComponent.buildComponent("svg", { class: 'map-rel-terrain-status-failed', viewBox: '0 0 100 100', style: this.failedStyle },
                        msfsSdk.FSComponent.buildComponent("path", { class: 'map-rel-terrain-status-failed-cross map-rel-terrain-status-failed-cross-outline', d: 'M 0 0 L 100 100 M 0 100 L 100 0' }),
                        msfsSdk.FSComponent.buildComponent("path", { class: 'map-rel-terrain-status-failed-cross map-rel-terrain-status-failed-cross-stroke', d: 'M 0 0 L 100 100 M 0 100 L 100 0' })))));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b;
            super.destroy();
            (_a = this.showSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.isFailedSub) === null || _b === void 0 ? void 0 : _b.destroy();
        }
    }

    /**
     * Displays a terrain color scale.
     */
    class MapTerrainScaleIndicator extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.rootRef = msfsSdk.FSComponent.createRef();
            this.absLabelText = msfsSdk.ArrayUtils.create(9, () => msfsSdk.Subject.create(''));
            this.relLabelText = msfsSdk.ArrayUtils.create(3, () => msfsSdk.Subject.create(''));
            this.groundLabelText = msfsSdk.ArrayUtils.create(4, () => msfsSdk.Subject.create(''));
            this.unitText = msfsSdk.Subject.create('');
            this.unitTextShown = msfsSdk.Subject.create('');
        }
        /** @inheritdoc */
        onAfterRender() {
            this.unitTextPipe = this.unitText.pipe(this.unitTextShown, true);
            this.showSub = this.props.show.sub(this.updateDisplay.bind(this));
            this.terrainModeSub = this.props.terrainMode.sub(this.updateDisplay.bind(this), true);
            this.altitudeUnitsModeSub = this.props.altitudeUnitsMode.sub(this.updateUnits.bind(this), true);
        }
        /**
         * Updates the display of this indicator.
         */
        updateDisplay() {
            if (this.props.show.get()) {
                switch (this.props.terrainMode.get()) {
                    case exports.MapTerrainMode.None:
                        this.rootRef.instance.style.display = 'none';
                        this.rootRef.instance.classList.remove('terrain-abs', 'terrain-rel', 'terrain-ground');
                        break;
                    case exports.MapTerrainMode.Absolute:
                        this.rootRef.instance.style.display = '';
                        this.rootRef.instance.classList.remove('terrain-rel', 'terrain-ground');
                        this.rootRef.instance.classList.add('terrain-abs');
                        if (this.props.altitudeUnitsMode.get() === exports.UnitsAltitudeSettingMode.Meters || this.props.altitudeUnitsMode.get() === exports.UnitsAltitudeSettingMode.MetersMps) {
                            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                            this.unitTextPipe.resume(true);
                        }
                        else {
                            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                            this.unitTextPipe.pause();
                            this.unitTextShown.set('');
                        }
                        break;
                    case exports.MapTerrainMode.Relative:
                        this.rootRef.instance.style.display = '';
                        this.rootRef.instance.classList.remove('terrain-abs', 'terrain-ground');
                        this.rootRef.instance.classList.add('terrain-rel');
                        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                        this.unitTextPipe.resume(true);
                        break;
                    case exports.MapTerrainMode.Ground:
                        this.rootRef.instance.style.display = '';
                        this.rootRef.instance.classList.remove('terrain-abs', 'terrain-rel');
                        this.rootRef.instance.classList.add('terrain-ground');
                        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                        this.unitTextPipe.resume(true);
                        break;
                }
            }
            else {
                this.rootRef.instance.style.display = 'none';
                this.rootRef.instance.classList.remove('terrain-abs', 'terrain-rel', 'terrain-ground');
            }
        }
        /**
         * Updates this indicator's display units.
         * @param mode The current altitude display units mode.
         */
        updateUnits(mode) {
            // eslint-disable-next-line jsdoc/require-jsdoc
            let textSource;
            if (mode === exports.UnitsAltitudeSettingMode.Meters || mode === exports.UnitsAltitudeSettingMode.MetersMps) {
                textSource = MapTerrainScaleIndicator.LABEL_TEXT_MT;
                if (this.props.terrainMode.get() === exports.MapTerrainMode.Absolute) {
                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                    this.unitTextPipe.resume(true);
                }
            }
            else {
                textSource = MapTerrainScaleIndicator.LABEL_TEXT_FT;
                if (this.props.terrainMode.get() === exports.MapTerrainMode.Absolute) {
                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                    this.unitTextPipe.pause();
                    this.unitTextShown.set('');
                }
            }
            for (let i = 0; i < this.absLabelText.length; i++) {
                this.absLabelText[i].set(textSource.abs[i]);
            }
            for (let i = 0; i < this.relLabelText.length; i++) {
                this.relLabelText[i].set(textSource.rel[i]);
            }
            for (let i = 0; i < this.groundLabelText.length; i++) {
                this.groundLabelText[i].set(textSource.ground[i]);
            }
            this.unitText.set(textSource.unit);
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { ref: this.rootRef, class: 'map-terrainscale' },
                msfsSdk.FSComponent.buildComponent("div", { class: 'map-terrainscale-scale terrainscale-abs' },
                    msfsSdk.FSComponent.buildComponent("div", { class: 'map-terrainscale-color', style: 'background: #cccccc;' }),
                    msfsSdk.FSComponent.buildComponent("div", { class: 'map-terrainscale-color', style: 'background: #979797;' },
                        msfsSdk.FSComponent.buildComponent("div", { class: 'map-terrainscale-label' }, this.absLabelText[0])),
                    msfsSdk.FSComponent.buildComponent("div", { class: 'map-terrainscale-color', style: 'background: #8b3c05;' },
                        msfsSdk.FSComponent.buildComponent("div", { class: 'map-terrainscale-label' }, this.absLabelText[1])),
                    msfsSdk.FSComponent.buildComponent("div", { class: 'map-terrainscale-color', style: 'background: #8d4b16;' },
                        msfsSdk.FSComponent.buildComponent("div", { class: 'map-terrainscale-label' }, this.absLabelText[2])),
                    msfsSdk.FSComponent.buildComponent("div", { class: 'map-terrainscale-color', style: 'background: #966324;' },
                        msfsSdk.FSComponent.buildComponent("div", { class: 'map-terrainscale-label' }, this.absLabelText[3])),
                    msfsSdk.FSComponent.buildComponent("div", { class: 'map-terrainscale-color', style: 'background: #c49440;' },
                        msfsSdk.FSComponent.buildComponent("div", { class: 'map-terrainscale-label' }, this.absLabelText[4])),
                    msfsSdk.FSComponent.buildComponent("div", { class: 'map-terrainscale-color', style: 'background: #c8b146;' },
                        msfsSdk.FSComponent.buildComponent("div", { class: 'map-terrainscale-label' }, this.absLabelText[5])),
                    msfsSdk.FSComponent.buildComponent("div", { class: 'map-terrainscale-color', style: 'background: #41690f;' },
                        msfsSdk.FSComponent.buildComponent("div", { class: 'map-terrainscale-label' }, this.absLabelText[6])),
                    msfsSdk.FSComponent.buildComponent("div", { class: 'map-terrainscale-color', style: 'background: #509993;' },
                        msfsSdk.FSComponent.buildComponent("div", { class: 'map-terrainscale-label' }, this.absLabelText[7])),
                    msfsSdk.FSComponent.buildComponent("div", { class: 'map-terrainscale-color', style: 'background: #255469;' },
                        msfsSdk.FSComponent.buildComponent("div", { class: 'map-terrainscale-label' }, this.absLabelText[8]))),
                msfsSdk.FSComponent.buildComponent("div", { class: 'map-terrainscale-scale terrainscale-rel' },
                    msfsSdk.FSComponent.buildComponent("div", { class: 'map-terrainscale-color', style: 'background: #aa0000' }),
                    msfsSdk.FSComponent.buildComponent("div", { class: 'map-terrainscale-color', style: 'background: #d2d200;' },
                        msfsSdk.FSComponent.buildComponent("div", { class: 'map-terrainscale-label' }, this.relLabelText[0])),
                    msfsSdk.FSComponent.buildComponent("div", { class: 'map-terrainscale-color', style: 'background: #00a000;' },
                        msfsSdk.FSComponent.buildComponent("div", { class: 'map-terrainscale-label' }, this.relLabelText[1])),
                    msfsSdk.FSComponent.buildComponent("div", { class: 'map-terrainscale-color', style: 'background: #000000;' },
                        msfsSdk.FSComponent.buildComponent("div", { class: 'map-terrainscale-label' }, this.relLabelText[2]))),
                msfsSdk.FSComponent.buildComponent("div", { class: 'map-terrainscale-scale terrainscale-ground' },
                    msfsSdk.FSComponent.buildComponent("div", { class: 'map-terrainscale-color', style: 'background: #aa0000' }),
                    msfsSdk.FSComponent.buildComponent("div", { class: 'map-terrainscale-color', style: 'background: #000000;' },
                        msfsSdk.FSComponent.buildComponent("div", { class: 'map-terrainscale-label' }, this.groundLabelText[0])),
                    msfsSdk.FSComponent.buildComponent("div", { class: 'map-terrainscale-color', style: 'background: #000000;' },
                        msfsSdk.FSComponent.buildComponent("div", { class: 'map-terrainscale-label' }, this.groundLabelText[1])),
                    msfsSdk.FSComponent.buildComponent("div", { class: 'map-terrainscale-color', style: 'background: #000000;' },
                        msfsSdk.FSComponent.buildComponent("div", { class: 'map-terrainscale-label' }, this.groundLabelText[2])),
                    msfsSdk.FSComponent.buildComponent("div", { class: 'map-terrainscale-color', style: 'background: #000000;' },
                        msfsSdk.FSComponent.buildComponent("div", { class: 'map-terrainscale-label' }, this.groundLabelText[3]))),
                msfsSdk.FSComponent.buildComponent("div", { class: 'map-terrainscale-unit' }, this.unitTextShown)));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c, _d;
            (_a = this.showSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.terrainModeSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.altitudeUnitsModeSub) === null || _c === void 0 ? void 0 : _c.destroy();
            (_d = this.unitTextPipe) === null || _d === void 0 ? void 0 : _d.destroy();
            super.destroy();
        }
    }
    MapTerrainScaleIndicator.LABEL_TEXT_FT = {
        abs: [
            '27⁰',
            '10⁵',
            '8⁰',
            '6⁰',
            '3⁰',
            '2⁰',
            '500',
            '−500',
            '−2⁰'
        ],
        rel: [
            '−100',
            '−1000',
            '−2000'
        ],
        ground: [
            '400',
            '−100',
            '−1000',
            '−2000'
        ],
        unit: 'FT'
    };
    MapTerrainScaleIndicator.LABEL_TEXT_MT = {
        abs: [
            '8230',
            '3200',
            '2438',
            '1828',
            '914',
            '609',
            '152',
            '−152',
            '−610'
        ],
        rel: [
            '−30',
            '−305',
            '−610'
        ],
        ground: [
            '122',
            '−30',
            '−305',
            '−610'
        ],
        unit: 'MT'
    };

    /**
     * Displays a traffic failed indication.
     */
    class MapTrafficFailedIndicator extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.ref = msfsSdk.FSComponent.createRef();
            this.text = msfsSdk.Subject.create('');
        }
        /** @inheritdoc */
        onAfterRender() {
            const operatingModeSub = this.operatingModeSub = this.props.operatingMode.sub(this.onModeChanged.bind(this), false, true);
            this.showSub = this.props.show.sub(show => {
                if (show) {
                    operatingModeSub.resume(true);
                }
                else {
                    operatingModeSub.pause();
                    this.ref.instance.classList.add('failed-mode-off');
                }
            }, true);
        }
        /**
         * A callback which is called when the traffic system operating mode changes.
         * @param mode The new mode.
         */
        onModeChanged(mode) {
            switch (mode) {
                case msfsSdk.TcasOperatingMode.Off:
                    this.text.set('NO TRFC DATA');
                    this.ref.instance.classList.remove('failed-mode-off');
                    break;
                case msfsSdk.TcasOperatingMode.Failed:
                    this.text.set('TRFC FAIL');
                    this.ref.instance.classList.remove('failed-mode-off');
                    break;
                default:
                    this.text.set('');
                    this.ref.instance.classList.add('failed-mode-off');
            }
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { ref: this.ref, class: 'map-traffic-failed' }, this.text));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b;
            super.destroy();
            (_a = this.operatingModeSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.showSub) === null || _b === void 0 ? void 0 : _b.destroy();
        }
    }

    /**
     * Displays a traffic off-scale indication.
     */
    class MapTrafficOffScaleIndicator extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.ref = msfsSdk.FSComponent.createRef();
            this.text = this.props.status.map(status => {
                return MapTrafficOffScaleIndicator.TEXT[status];
            });
        }
        /** @inheritdoc */
        onAfterRender() {
            this.statusSub = this.props.status.sub(this.onModeChanged.bind(this), true);
        }
        /**
         * A callback which is called when the indicator mode changes.
         * @param mode The new mode.
         */
        onModeChanged(mode) {
            this.ref.instance.classList.remove('offscale-mode-off', 'offscale-mode-ta', 'offscale-mode-ra');
            this.ref.instance.classList.add(MapTrafficOffScaleIndicator.CLASSES[mode]);
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { ref: this.ref, class: 'map-traffic-offscale' }, this.text));
        }
        /** @inheritdoc */
        destroy() {
            var _a;
            super.destroy();
            (_a = this.statusSub) === null || _a === void 0 ? void 0 : _a.destroy();
        }
    }
    MapTrafficOffScaleIndicator.CLASSES = {
        [exports.MapTrafficOffScaleStatus.None]: 'offscale-mode-off',
        [exports.MapTrafficOffScaleStatus.TA]: 'offscale-mode-ta',
        [exports.MapTrafficOffScaleStatus.RA]: 'offscale-mode-ra'
    };
    MapTrafficOffScaleIndicator.TEXT = {
        [exports.MapTrafficOffScaleStatus.None]: '',
        [exports.MapTrafficOffScaleStatus.TA]: 'TA OFF SCALE',
        [exports.MapTrafficOffScaleStatus.RA]: 'RA OFF SCALE'
    };

    /**
     * Displays a traffic operating status and optional altitude restriction mode indications.
     */
    class MapTrafficStatusIndicator extends msfsSdk.DisplayComponent {
        constructor() {
            var _a;
            super(...arguments);
            this.rootStyle = msfsSdk.ObjectSubject.create({ display: '' });
            this.disabledStyle = msfsSdk.ObjectSubject.create({ display: '' });
            this.altModeStyle = msfsSdk.ObjectSubject.create({ display: '' });
            this.altitudeRestrictionText = (_a = this.props.altitudeRestrictionMode) === null || _a === void 0 ? void 0 : _a.map(mode => {
                return MapTrafficStatusIndicator.ALT_RESTRICTION_TEXT[mode];
            });
        }
        /** @inheritdoc */
        onAfterRender() {
            this.showSub = this.props.show.sub(show => {
                this.rootStyle.set('display', show ? '' : 'none');
            }, true);
            this.operatingModeSub = this.props.operatingMode.sub(mode => {
                const isDisabled = MapTrafficStatusIndicator.DISABLED_MODES.has(mode);
                this.disabledStyle.set('display', isDisabled ? 'inherit' : 'none');
                this.altModeStyle.set('display', isDisabled ? 'none' : 'inherit');
            }, true);
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { style: this.rootStyle, class: 'map-traffic-status' },
                this.props.altitudeRestrictionMode !== undefined
                    ? msfsSdk.FSComponent.buildComponent("div", { style: this.altModeStyle, class: 'traffic-status-alt' }, this.altitudeRestrictionText)
                    : null,
                msfsSdk.FSComponent.buildComponent("svg", { class: 'traffic-status-symbol', viewBox: '0 0 150 100' },
                    msfsSdk.FSComponent.buildComponent("path", { d: 'M 50 5 L 95 50 L 50 95 L 5 50 Z' }),
                    msfsSdk.FSComponent.buildComponent("path", { d: 'M 115 10 L 135 35 L 122.5 35 L 122.5 80 L 107.5 80 L 107.5 35 L 95 35 Z' }),
                    msfsSdk.FSComponent.buildComponent("g", { style: this.disabledStyle, class: 'traffic-status-disabled' },
                        msfsSdk.FSComponent.buildComponent("path", { class: 'traffic-status-disabledcross traffic-status-disabledcross-outline', d: 'M 5 0 L 145 100 M 5 100 L 145 0' }),
                        msfsSdk.FSComponent.buildComponent("path", { class: 'traffic-status-disabledcross traffic-status-disabledcross-stroke', d: 'M 5 0 L 145 100 M 5 100 L 145 0' })))));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c;
            super.destroy();
            (_a = this.showSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.operatingModeSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.altitudeRestrictionText) === null || _c === void 0 ? void 0 : _c.destroy();
        }
    }
    MapTrafficStatusIndicator.DISABLED_MODES = new Set([
        msfsSdk.TcasOperatingMode.Off,
        msfsSdk.TcasOperatingMode.Standby,
        msfsSdk.TcasOperatingMode.Failed,
        msfsSdk.TcasOperatingMode.Test
    ]);
    MapTrafficStatusIndicator.ALT_RESTRICTION_TEXT = {
        [exports.MapTrafficAltitudeRestrictionMode.Unrestricted]: 'UNRES',
        [exports.MapTrafficAltitudeRestrictionMode.Above]: 'ABOVE',
        [exports.MapTrafficAltitudeRestrictionMode.Normal]: 'NORM',
        [exports.MapTrafficAltitudeRestrictionMode.Below]: 'BELOW'
    };

    /**
     * Displays an ADS-B operating mode indication.
     */
    class TrafficMapAdsbModeIndicator extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.text = this.props.operatingMode.map(mode => { var _a; return (_a = this.props.text[mode]) !== null && _a !== void 0 ? _a : ''; });
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: 'traffic-map-adsb-opmode' }, this.text));
        }
        /** @inheritdoc */
        destroy() {
            super.destroy();
            this.text.destroy();
        }
    }

    /**
     * Displays an ADS-B Off banner indicator.
     */
    class TrafficMapAdsbOffBannerIndicator extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.show = msfsSdk.MappedSubject.create(([adsbMode, trafficMode]) => {
                return adsbMode === msfsSdk.AdsbOperatingMode.Standby
                    && trafficMode !== msfsSdk.TcasOperatingMode.Off
                    && trafficMode !== msfsSdk.TcasOperatingMode.Standby
                    && trafficMode !== msfsSdk.TcasOperatingMode.Failed;
            }, this.props.adsbOperatingMode, this.props.trafficOperatingMode);
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent(MapBannerIndicator, { show: this.show, class: 'traffic-map-banner-adsb-standby' }, "ADS-B TRFC OFF"));
        }
        /** @inheritdoc */
        destroy() {
            super.destroy();
            this.show.destroy();
        }
    }

    /**
     * Displays a traffic system altitude restriction mode indication.
     */
    class TrafficMapAltitudeModeIndicator extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.text = this.props.altitudeRestrictionMode.map(mode => { var _a; return (_a = this.props.text[mode]) !== null && _a !== void 0 ? _a : ''; });
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: 'traffic-map-altmode' }, this.text));
        }
        /** @inheritdoc */
        destroy() {
            super.destroy();
            this.text.destroy();
        }
    }

    /**
     * Displays a traffic system failed mode banner indicator.
     */
    class TrafficMapFailedBannerIndicator extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.show = this.props.operatingMode.map(mode => mode === msfsSdk.TcasOperatingMode.Off || mode === msfsSdk.TcasOperatingMode.Failed);
            this.text = this.props.operatingMode.map(mode => mode === msfsSdk.TcasOperatingMode.Failed ? 'FAILED' : 'NO DATA');
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent(MapBannerIndicator, { show: this.show, class: 'traffic-map-banner-failed' }, this.text));
        }
        /** @inheritdoc */
        destroy() {
            super.destroy();
            this.show.destroy();
            this.text.destroy();
        }
    }

    /**
     * Displays a traffic system operating mode indication.
     */
    class TrafficMapOperatingModeIndicator extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.text = this.props.operatingMode.map(mode => { var _a; return (_a = this.props.text[mode]) !== null && _a !== void 0 ? _a : ''; });
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: 'traffic-map-opmode' }, this.text));
        }
        /** @inheritdoc */
        destroy() {
            super.destroy();
            this.text.destroy();
        }
    }

    /**
     * Displays a traffic system standby mode banner indicator.
     */
    class TrafficMapStandbyBannerIndicator extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.show = this.props.operatingMode.map(mode => mode === msfsSdk.TcasOperatingMode.Standby);
            this.cssClassSet = msfsSdk.SetSubject.create(['traffic-map-banner-standby']);
        }
        /** @inheritdoc */
        onAfterRender() {
            this.isOnGroundSub = this.props.isOnGround.sub(isOnGround => {
                isOnGround
                    ? this.cssClassSet.add('traffic-map-banner-standby-onground')
                    : this.cssClassSet.delete('traffic-map-banner-standby-onground');
            }, true);
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent(MapBannerIndicator, { show: this.show, class: this.cssClassSet }, "STANDBY"));
        }
        /** @inheritdoc */
        destroy() {
            var _a;
            super.destroy();
            this.show.destroy();
            (_a = this.isOnGroundSub) === null || _a === void 0 ? void 0 : _a.destroy();
        }
    }

    /**
     * A utility class for generating next-generation (NXi, G3000, etc) Garmin map waypoint styles.
     */
    class NextGenMapWaypointStyles {
        /**
         * Creates a function which retrieves next-generation (NXi, G3000, etc) icon styles for normal waypoints.
         * @param basePriority The base icon render priority. Icon priorities are guaranteed to fall in the range
         * `[basePriority, basePriority + 1)`.
         * @param scale The scaling factor for the icons. The larger the value, the larger the rendered icons. Defaults to
         * `1`.
         * @returns A function which retrieves next-generation (NXi, G3000, etc) icon styles for normal waypoints.
         */
        static normalIconStyles(basePriority, scale = 1) {
            const airportPriority = {
                [exports.AirportSize.Large]: basePriority + 0.8,
                [exports.AirportSize.Medium]: basePriority + 0.79,
                [exports.AirportSize.Small]: basePriority + 0.78
            };
            const vorPriority = basePriority + 0.7;
            const ndbPriority = basePriority + 0.6;
            const intPriority = basePriority + 0.5;
            const userPriority = basePriority + 0.9;
            const airportSize = msfsSdk.Vec2Math.create(26 * scale, 26 * scale);
            const standardSize = msfsSdk.Vec2Math.create(32 * scale, 32 * scale);
            const airportStyle = {
                [exports.AirportSize.Large]: { priority: airportPriority[exports.AirportSize.Large], size: airportSize },
                [exports.AirportSize.Medium]: { priority: airportPriority[exports.AirportSize.Medium], size: airportSize },
                [exports.AirportSize.Small]: { priority: airportPriority[exports.AirportSize.Small], size: airportSize }
            };
            const vorStyle = { priority: vorPriority, size: standardSize };
            const ndbStyle = { priority: ndbPriority, size: standardSize };
            const intStyle = { priority: intPriority, size: standardSize };
            const userStyle = { priority: userPriority, size: standardSize };
            const defaultStyle = { priority: basePriority, size: standardSize };
            return (waypoint) => {
                if (waypoint instanceof AirportWaypoint) {
                    return airportStyle[waypoint.size];
                }
                else if (msfsSdk.FacilityWaypointUtils.isFacilityWaypoint(waypoint)) {
                    switch (msfsSdk.ICAO.getFacilityType(waypoint.facility.get().icao)) {
                        case msfsSdk.FacilityType.VOR:
                            return vorStyle;
                        case msfsSdk.FacilityType.NDB:
                            return ndbStyle;
                        case msfsSdk.FacilityType.Intersection:
                            return intStyle;
                        case msfsSdk.FacilityType.USR:
                            return userStyle;
                    }
                }
                return defaultStyle;
            };
        }
        /**
         * Creates a function which retrieves next-generation (NXi, G3000, etc) label styles for normal waypoints.
         * @param basePriority The base label render priority. Label priorities are guaranteed to fall in the range
         * `[basePriority, basePriority + 1)`.
         * @param fontType The type of font to use for the labels.
         * @param scale The scaling factor for the labels. The larger the value, the larger the rendered labels. Defaults to
         * `1`.
         * @returns A function which retrieves next-generation (NXi, G3000, etc) label styles for normal waypoints.
         */
        static normalLabelStyles(basePriority, fontType, scale = 1) {
            let font, largeFontSize, regularFontSize;
            if (fontType === 'Roboto') {
                font = 'Roboto';
                largeFontSize = 20 * scale;
                regularFontSize = 16 * scale;
            }
            else {
                font = 'DejaVuSans-SemiBold';
                largeFontSize = 17 * scale;
                regularFontSize = 14 * scale;
            }
            const airportPriority = {
                [exports.AirportSize.Large]: basePriority + 0.8,
                [exports.AirportSize.Medium]: basePriority + 0.79,
                [exports.AirportSize.Small]: basePriority + 0.78
            };
            const vorPriority = basePriority + 0.7;
            const ndbPriority = basePriority + 0.6;
            const intPriority = basePriority + 0.5;
            const userPriority = basePriority + 0.9;
            const runwayOutlinePriority = basePriority + 0.75;
            const airportOptions = {
                [exports.AirportSize.Large]: NextGenMapWaypointStyles.createNormalLabelOptions(msfsSdk.Vec2Math.create(0, -12 * scale), font, largeFontSize),
                [exports.AirportSize.Medium]: NextGenMapWaypointStyles.createNormalLabelOptions(msfsSdk.Vec2Math.create(0, -12 * scale), font, regularFontSize),
                [exports.AirportSize.Small]: NextGenMapWaypointStyles.createNormalLabelOptions(msfsSdk.Vec2Math.create(0, -12 * scale), font, regularFontSize)
            };
            const standardOptions = NextGenMapWaypointStyles.createNormalLabelOptions(msfsSdk.Vec2Math.create(0, -8 * scale), font, regularFontSize);
            const intOptions = NextGenMapWaypointStyles.createNormalLabelOptions(msfsSdk.Vec2Math.create(0, -5 * scale), font, regularFontSize);
            const runwayOutlineOptions = NextGenMapWaypointStyles.createRunwayLabelOptions(msfsSdk.Vec2Math.create(0, -5 * scale), font, largeFontSize, 7 * scale);
            const airportStyle = {
                [exports.AirportSize.Large]: { priority: airportPriority[exports.AirportSize.Large], alwaysShow: false, options: airportOptions[exports.AirportSize.Large] },
                [exports.AirportSize.Medium]: { priority: airportPriority[exports.AirportSize.Medium], alwaysShow: false, options: airportOptions[exports.AirportSize.Medium] },
                [exports.AirportSize.Small]: { priority: airportPriority[exports.AirportSize.Small], alwaysShow: false, options: airportOptions[exports.AirportSize.Small] }
            };
            const vorStyle = { priority: vorPriority, alwaysShow: false, options: standardOptions };
            const ndbStyle = { priority: ndbPriority, alwaysShow: false, options: standardOptions };
            const intStyle = { priority: intPriority, alwaysShow: false, options: intOptions };
            const userStyle = { priority: userPriority, alwaysShow: false, options: standardOptions };
            const runwayOutlineStyle = { priority: runwayOutlinePriority, alwaysShow: false, options: runwayOutlineOptions };
            const defaultStyle = { priority: basePriority, alwaysShow: false, options: standardOptions };
            return (waypoint) => {
                if (waypoint instanceof AirportWaypoint) {
                    return airportStyle[waypoint.size];
                }
                else if (waypoint instanceof MapRunwayLabelWaypoint) {
                    return runwayOutlineStyle;
                }
                else if (msfsSdk.FacilityWaypointUtils.isFacilityWaypoint(waypoint)) {
                    switch (msfsSdk.ICAO.getFacilityType(waypoint.facility.get().icao)) {
                        case msfsSdk.FacilityType.VOR:
                            return vorStyle;
                        case msfsSdk.FacilityType.NDB:
                            return ndbStyle;
                        case msfsSdk.FacilityType.Intersection:
                            return intStyle;
                        case msfsSdk.FacilityType.USR:
                            return userStyle;
                    }
                }
                return defaultStyle;
            };
        }
        /**
         * Creates initialization options for next-generation (NXi, G3000, etc) style waypoint labels rendered in a normal
         * role.
         * @param offset The label offset, in pixels.
         * @param font The name of the label font.
         * @param fontSize The font size of the label, in pixels.
         * @returns Initialization options for next-generation (NXi, G3000, etc) style waypoint labels rendered in a normal
         * role.
         */
        static createNormalLabelOptions(offset, font, fontSize) {
            return {
                anchor: msfsSdk.Vec2Math.create(0.5, 1),
                offset,
                font,
                fontSize,
                fontOutlineWidth: 6
            };
        }
        /**
         * Creates initialization options for next-generation (NXi, G3000, etc) style runway labels.
         * @param offset The label offset, in pixels.
         * @param font The name of the label font.
         * @param fontSize The font size of the label, in pixels.
         * @param borderRadius The border radius of the label, in pixels.
         * @returns Initialization options for next-generation (NXi, G3000, etc) style runway labels rendered in a normal
         * role.
         */
        static createRunwayLabelOptions(offset, font, fontSize, borderRadius) {
            return {
                anchor: msfsSdk.Vec2Math.create(0.5, 1),
                offset,
                font,
                fontSize,
                fontColor: '#123086',
                fontOutlineWidth: 0,
                showBg: true,
                bgPadding: msfsSdk.VecNMath.create(4, 1, 3, 1, 3),
                bgColor: 'white',
                bgOutlineWidth: 1,
                bgOutlineColor: '#123086',
                bgBorderRadius: borderRadius
            };
        }
        /**
         * Creates a function which retrieves next-generation (NXi, G3000, etc) icon styles for runway outline waypoints.
         * @param basePriority The base icon render priority. Icon priorities are guaranteed to fall in the range
         * `[basePriority, basePriority + 1)`.
         * @returns A function which retrieves next-generation (NXi, G3000, etc) icon styles for normal waypoints.
         */
        static runwayOutlineIconStyles(basePriority) {
            const priority = basePriority;
            const hardStyle = { priority, options: { fillStyle: '#afafaf' } };
            const softStyle = { priority, options: { fillStyle: '#006400' } };
            const waterStyle = { priority, options: { fillStyle: 'transparent' } };
            return (waypoint) => {
                switch (waypoint.surfaceCategory) {
                    case msfsSdk.RunwaySurfaceCategory.Hard:
                        return hardStyle;
                    case msfsSdk.RunwaySurfaceCategory.Soft:
                        return softStyle;
                    case msfsSdk.RunwaySurfaceCategory.Water:
                        return waterStyle;
                    default:
                        return hardStyle;
                }
            };
        }
        /**
         * Creates a function which retrieves next-generation (NXi, G3000, etc) icon styles for flight plan waypoints.
         * @param active Whether to retrieve styles for active flight plan waypoints.
         * @param basePriority The base icon render priority. Icon priorities are guaranteed to fall in the range
         * `[basePriority, basePriority + 1)`.
         * @param scale The scaling factor for the icons. The larger the value, the larger the rendered icons. Defaults to
         * `1`.
         * @returns A function which retrieves next-generation (NXi, G3000, etc) icon styles for flight plan waypoints.
         */
        static flightPlanIconStyles(active, basePriority, scale = 1) {
            const airportPriority = {
                [exports.AirportSize.Large]: basePriority + 0.8,
                [exports.AirportSize.Medium]: basePriority + 0.79,
                [exports.AirportSize.Small]: basePriority + 0.78
            };
            const vorPriority = basePriority + 0.7;
            const ndbPriority = basePriority + 0.6;
            const intPriority = basePriority + 0.5;
            const rwyPriority = basePriority + 0.4;
            const userPriority = basePriority + 0.9;
            const fpPriority = basePriority + 0.1;
            const airportSize = msfsSdk.Vec2Math.create(26 * scale, 26 * scale);
            const standardSize = msfsSdk.Vec2Math.create(32 * scale, 32 * scale);
            const fpIconSize = msfsSdk.Vec2Math.create(8 * scale, 8 * scale);
            const airportStyle = {
                [exports.AirportSize.Large]: { priority: airportPriority[exports.AirportSize.Large], size: airportSize },
                [exports.AirportSize.Medium]: { priority: airportPriority[exports.AirportSize.Medium], size: airportSize },
                [exports.AirportSize.Small]: { priority: airportPriority[exports.AirportSize.Small], size: airportSize }
            };
            const vorStyle = { priority: vorPriority, size: standardSize };
            const ndbStyle = { priority: ndbPriority, size: standardSize };
            const intStyle = { priority: intPriority, size: standardSize };
            const rwyStyle = { priority: rwyPriority, size: standardSize };
            const userStyle = { priority: userPriority, size: standardSize };
            const fpStyle = { priority: fpPriority, size: fpIconSize };
            const defaultStyle = { priority: basePriority, size: standardSize };
            return (waypoint) => {
                if (waypoint instanceof AirportWaypoint) {
                    return airportStyle[waypoint.size];
                }
                else if (msfsSdk.FacilityWaypointUtils.isFacilityWaypoint(waypoint)) {
                    switch (msfsSdk.ICAO.getFacilityType(waypoint.facility.get().icao)) {
                        case msfsSdk.FacilityType.VOR:
                            return vorStyle;
                        case msfsSdk.FacilityType.NDB:
                            return ndbStyle;
                        case msfsSdk.FacilityType.Intersection:
                            return intStyle;
                        case msfsSdk.FacilityType.RWY:
                            return rwyStyle;
                        case msfsSdk.FacilityType.USR:
                            return userStyle;
                    }
                }
                else if (waypoint instanceof msfsSdk.FlightPathWaypoint) {
                    return fpStyle;
                }
                return defaultStyle;
            };
        }
        /**
         * Creates a function which retrieves next-generation (NXi, G3000, etc) label styles for flight plan waypoints.
         * @param active Whether to retrieve styles for active flight plan waypoints.
         * @param basePriority The base label render priority. Label priorities are guaranteed to fall in the range
         * `[basePriority, basePriority + 1)`.
         * @param fontType The type of font to use for the labels.
         * @param scale The scaling factor for the labels. The larger the value, the larger the rendered labels. Defaults to
         * `1`.
         * @returns A function which retrieves next-generation (NXi, G3000, etc) label styles for flight plan waypoints.
         */
        static flightPlanLabelStyles(active, basePriority, fontType, scale = 1) {
            let font, largeFontSize, regularFontSize;
            if (fontType === 'Roboto') {
                font = 'Roboto-Bold';
                largeFontSize = 20 * scale;
                regularFontSize = 16 * scale;
            }
            else {
                font = 'DejaVuSans-SemiBold';
                largeFontSize = 17 * scale;
                regularFontSize = 17 * scale;
            }
            const createLabelOptions = active
                ? NextGenMapWaypointStyles.createFplActiveLabelOptions
                : NextGenMapWaypointStyles.createFplInactiveLabelOptions;
            const airportPriority = {
                [exports.AirportSize.Large]: basePriority + 0.8,
                [exports.AirportSize.Medium]: basePriority + 0.79,
                [exports.AirportSize.Small]: basePriority + 0.78
            };
            const vorPriority = basePriority + 0.7;
            const ndbPriority = basePriority + 0.6;
            const intPriority = basePriority + 0.5;
            const rwyPriority = basePriority + 0.4;
            const userPriority = basePriority + 0.9;
            const fpPriority = basePriority + 0.1;
            const airportOptions = {
                [exports.AirportSize.Large]: createLabelOptions(msfsSdk.Vec2Math.create(0, -15 * scale), font, largeFontSize),
                [exports.AirportSize.Medium]: createLabelOptions(msfsSdk.Vec2Math.create(0, -15 * scale), font, regularFontSize),
                [exports.AirportSize.Small]: createLabelOptions(msfsSdk.Vec2Math.create(0, -15 * scale), font, regularFontSize)
            };
            const vorOptions = createLabelOptions(msfsSdk.Vec2Math.create(0, -11 * scale), font, regularFontSize);
            const ndbOptions = createLabelOptions(msfsSdk.Vec2Math.create(0, -11 * scale), font, regularFontSize);
            const userOptions = createLabelOptions(msfsSdk.Vec2Math.create(0, -12 * scale), font, regularFontSize);
            const smallOptions = createLabelOptions(msfsSdk.Vec2Math.create(0, -8 * scale), font, regularFontSize);
            const airportStyle = {
                [exports.AirportSize.Large]: { priority: airportPriority[exports.AirportSize.Large], alwaysShow: true, options: airportOptions[exports.AirportSize.Large] },
                [exports.AirportSize.Medium]: { priority: airportPriority[exports.AirportSize.Medium], alwaysShow: true, options: airportOptions[exports.AirportSize.Medium] },
                [exports.AirportSize.Small]: { priority: airportPriority[exports.AirportSize.Small], alwaysShow: true, options: airportOptions[exports.AirportSize.Small] }
            };
            const vorStyle = { priority: vorPriority, alwaysShow: true, options: vorOptions };
            const ndbStyle = { priority: ndbPriority, alwaysShow: true, options: ndbOptions };
            const intStyle = { priority: intPriority, alwaysShow: true, options: smallOptions };
            const rwyStyle = { priority: rwyPriority, alwaysShow: true, options: smallOptions };
            const userStyle = { priority: userPriority, alwaysShow: true, options: userOptions };
            const fpStyle = { priority: fpPriority, alwaysShow: true, options: smallOptions };
            const defaultStyle = { priority: basePriority, alwaysShow: true, options: smallOptions };
            return (waypoint) => {
                if (waypoint instanceof AirportWaypoint) {
                    return airportStyle[waypoint.size];
                }
                else if (msfsSdk.FacilityWaypointUtils.isFacilityWaypoint(waypoint)) {
                    switch (msfsSdk.ICAO.getFacilityType(waypoint.facility.get().icao)) {
                        case msfsSdk.FacilityType.VOR:
                            return vorStyle;
                        case msfsSdk.FacilityType.NDB:
                            return ndbStyle;
                        case msfsSdk.FacilityType.Intersection:
                            return intStyle;
                        case msfsSdk.FacilityType.RWY:
                            return rwyStyle;
                        case msfsSdk.FacilityType.USR:
                            return userStyle;
                    }
                }
                else if (waypoint instanceof msfsSdk.FlightPathWaypoint || waypoint instanceof ProcedureTurnLegWaypoint) {
                    return fpStyle;
                }
                return defaultStyle;
            };
        }
        /**
         * Creates initialization options for next-generation (NXi, G3000, etc) style waypoint labels rendered in an inactive
         * flight plan role.
         * @param offset The label offset, in pixels.
         * @param font The name of the label font.
         * @param fontSize The font size of the label, in pixels.
         * @returns Initialization options for next-generation (NXi, G3000, etc) style waypoint labels rendered in an
         * inactive flight plan role.
         */
        static createFplInactiveLabelOptions(offset, font, fontSize) {
            return {
                anchor: msfsSdk.Vec2Math.create(0, 1),
                offset,
                font,
                fontSize,
                fontColor: 'black',
                fontOutlineWidth: 0,
                showBg: true,
                bgPadding: msfsSdk.VecNMath.create(4, 1, 1, 1, 1),
                bgColor: 'white',
                bgOutlineWidth: 1,
                bgOutlineColor: 'black'
            };
        }
        /**
         * Creates initialization options for next-generation (NXi, G3000, etc) style waypoint labels rendered in an active
         * flight plan role.
         * @param offset The label offset, in pixels.
         * @param font The name of the label font.
         * @param fontSize The font size of the label, in pixels.
         * @returns Initialization options for next-generation (NXi, G3000, etc) style waypoint labels rendered in an active
         * flight plan role.
         */
        static createFplActiveLabelOptions(offset, font, fontSize) {
            return {
                anchor: msfsSdk.Vec2Math.create(0, 1),
                offset,
                font,
                fontSize,
                fontColor: 'magenta',
                fontOutlineWidth: 0,
                showBg: true,
                bgPadding: msfsSdk.VecNMath.create(4, 1, 1, 1, 1),
                bgOutlineWidth: 1
            };
        }
        /**
         * Creates a function which retrieves next-generation (NXi, G3000, etc) icon styles for highlighted waypoints.
         * @param basePriority The base icon render priority. Icon priorities are guaranteed to fall in the range
         * `[basePriority, basePriority + 1)`.
         * @param scale The scaling factor for the icons. The larger the value, the larger the rendered icons. Defaults to
         * `1`.
         * @returns A function which retrieves next-generation (NXi, G3000, etc) icon styles for highlighted waypoints.
         */
        static highlightIconStyles(basePriority, scale = 1) {
            const baseHighlightOptions = {
                strokeWidth: 2,
                strokeColor: 'white',
                outlineWidth: 0,
                outlineColor: 'black',
                bgColor: '#3c3c3c'
            };
            const airportHighlightRingRadiusBuffer = -5 * scale;
            const standardHighlightRingRadiusBuffer = -8 * scale;
            const airportPriority = {
                [exports.AirportSize.Large]: basePriority + 0.8,
                [exports.AirportSize.Medium]: basePriority + 0.79,
                [exports.AirportSize.Small]: basePriority + 0.78
            };
            const vorPriority = basePriority + 0.7;
            const ndbPriority = basePriority + 0.6;
            const intPriority = basePriority + 0.5;
            const userPriority = basePriority + 0.9;
            const airportSize = msfsSdk.Vec2Math.create(26 * scale, 26 * scale);
            const standardSize = msfsSdk.Vec2Math.create(32 * scale, 32 * scale);
            const airportStyle = {
                [exports.AirportSize.Large]: {
                    priority: airportPriority[exports.AirportSize.Large],
                    size: airportSize,
                    highlightOptions: Object.assign({ ringRadiusBuffer: airportHighlightRingRadiusBuffer }, baseHighlightOptions)
                },
                [exports.AirportSize.Medium]: {
                    priority: airportPriority[exports.AirportSize.Medium],
                    size: airportSize,
                    highlightOptions: Object.assign({ ringRadiusBuffer: airportHighlightRingRadiusBuffer }, baseHighlightOptions)
                },
                [exports.AirportSize.Small]: {
                    priority: airportPriority[exports.AirportSize.Small],
                    size: airportSize,
                    highlightOptions: Object.assign({ ringRadiusBuffer: airportHighlightRingRadiusBuffer }, baseHighlightOptions)
                },
            };
            const vorStyle = {
                priority: vorPriority,
                size: standardSize,
                highlightOptions: Object.assign({ ringRadiusBuffer: standardHighlightRingRadiusBuffer }, baseHighlightOptions)
            };
            const ndbStyle = {
                priority: ndbPriority,
                size: standardSize,
                highlightOptions: Object.assign({ ringRadiusBuffer: standardHighlightRingRadiusBuffer }, baseHighlightOptions)
            };
            const intStyle = {
                priority: intPriority,
                size: standardSize,
                highlightOptions: Object.assign({ ringRadiusBuffer: standardHighlightRingRadiusBuffer }, baseHighlightOptions)
            };
            const userStyle = {
                priority: userPriority,
                size: standardSize,
                highlightOptions: Object.assign({ ringRadiusBuffer: standardHighlightRingRadiusBuffer }, baseHighlightOptions)
            };
            const defaultStyle = {
                priority: basePriority,
                size: standardSize,
                highlightOptions: Object.assign({ ringRadiusBuffer: standardHighlightRingRadiusBuffer }, baseHighlightOptions)
            };
            return (waypoint) => {
                if (waypoint instanceof AirportWaypoint) {
                    return airportStyle[waypoint.size];
                }
                else if (msfsSdk.FacilityWaypointUtils.isFacilityWaypoint(waypoint)) {
                    switch (msfsSdk.ICAO.getFacilityType(waypoint.facility.get().icao)) {
                        case msfsSdk.FacilityType.VOR:
                            return vorStyle;
                        case msfsSdk.FacilityType.NDB:
                            return ndbStyle;
                        case msfsSdk.FacilityType.Intersection:
                            return intStyle;
                        case msfsSdk.FacilityType.USR:
                            return userStyle;
                    }
                }
                return defaultStyle;
            };
        }
        /**
         * Creates a function which retrieves next-generation (NXi, G3000, etc) label styles for highlighted waypoints.
         * @param basePriority The base label render priority. Label priorities are guaranteed to fall in the range
         * `[basePriority, basePriority + 1)`.
         * @param fontType The type of font to use for the labels.
         * @param scale The scaling factor for the labels. The larger the value, the larger the rendered label. Defaults to
         * `1`.
         * @returns A function which retrieves next-generation (NXi, G3000, etc) label styles for highlighted waypoints.
         */
        static highlightLabelStyles(basePriority, fontType, scale = 1) {
            let font, largeFontSize, regularFontSize;
            if (fontType === 'Roboto') {
                font = 'Roboto-Bold';
                largeFontSize = 20 * scale;
                regularFontSize = 16 * scale;
            }
            else {
                font = 'DejaVuSans-SemiBold';
                largeFontSize = 17 * scale;
                regularFontSize = 17 * scale;
            }
            const airportPriority = {
                [exports.AirportSize.Large]: basePriority + 0.8,
                [exports.AirportSize.Medium]: basePriority + 0.79,
                [exports.AirportSize.Small]: basePriority + 0.78
            };
            const vorPriority = basePriority + 0.7;
            const ndbPriority = basePriority + 0.6;
            const intPriority = basePriority + 0.5;
            const userPriority = basePriority + 0.9;
            const airportOptions = {
                [exports.AirportSize.Large]: NextGenMapWaypointStyles.createHighlightLabelOptions(msfsSdk.Vec2Math.create(0, -15 * scale), font, largeFontSize),
                [exports.AirportSize.Medium]: NextGenMapWaypointStyles.createHighlightLabelOptions(msfsSdk.Vec2Math.create(0, -15 * scale), font, regularFontSize),
                [exports.AirportSize.Small]: NextGenMapWaypointStyles.createHighlightLabelOptions(msfsSdk.Vec2Math.create(0, -15 * scale), font, regularFontSize)
            };
            const vorOptions = NextGenMapWaypointStyles.createHighlightLabelOptions(msfsSdk.Vec2Math.create(0, -11 * scale), font, regularFontSize);
            const ndbOptions = NextGenMapWaypointStyles.createHighlightLabelOptions(msfsSdk.Vec2Math.create(0, -11 * scale), font, regularFontSize);
            const intOptions = NextGenMapWaypointStyles.createHighlightLabelOptions(msfsSdk.Vec2Math.create(0, -8 * scale), font, regularFontSize);
            const userOptions = NextGenMapWaypointStyles.createHighlightLabelOptions(msfsSdk.Vec2Math.create(0, -12 * scale), font, regularFontSize);
            const airportStyle = {
                [exports.AirportSize.Large]: { priority: airportPriority[exports.AirportSize.Large], alwaysShow: true, options: airportOptions[exports.AirportSize.Large] },
                [exports.AirportSize.Medium]: { priority: airportPriority[exports.AirportSize.Medium], alwaysShow: true, options: airportOptions[exports.AirportSize.Medium] },
                [exports.AirportSize.Small]: { priority: airportPriority[exports.AirportSize.Small], alwaysShow: true, options: airportOptions[exports.AirportSize.Small] }
            };
            const vorStyle = { priority: vorPriority, alwaysShow: true, options: vorOptions };
            const ndbStyle = { priority: ndbPriority, alwaysShow: true, options: ndbOptions };
            const intStyle = { priority: intPriority, alwaysShow: true, options: intOptions };
            const userStyle = { priority: userPriority, alwaysShow: true, options: userOptions };
            const defaultStyle = { priority: basePriority, alwaysShow: false, options: intOptions };
            return (waypoint) => {
                if (waypoint instanceof AirportWaypoint) {
                    return airportStyle[waypoint.size];
                }
                else if (msfsSdk.FacilityWaypointUtils.isFacilityWaypoint(waypoint)) {
                    switch (msfsSdk.ICAO.getFacilityType(waypoint.facility.get().icao)) {
                        case msfsSdk.FacilityType.VOR:
                            return vorStyle;
                        case msfsSdk.FacilityType.NDB:
                            return ndbStyle;
                        case msfsSdk.FacilityType.Intersection:
                            return intStyle;
                        case msfsSdk.FacilityType.USR:
                            return userStyle;
                    }
                }
                return defaultStyle;
            };
        }
        /**
         * Creates initialization options for next-generation (NXi, G3000, etc) style waypoint labels rendered in a highlight
         * role.
         * @param offset The label offset, in pixels.
         * @param font The name of the label font.
         * @param fontSize The font size of the label, in pixels.
         * @returns Initialization options for next-generation (NXi, G3000, etc) style waypoint labels rendered in a highlight
         * role.
         */
        static createHighlightLabelOptions(offset, font, fontSize) {
            return {
                anchor: msfsSdk.Vec2Math.create(0.5, 1),
                offset,
                font,
                fontSize,
                fontColor: 'black',
                fontOutlineWidth: 0,
                showBg: true,
                bgPadding: msfsSdk.VecNMath.create(4, 1, 1, 1, 1),
                bgColor: 'white',
                bgOutlineWidth: 1,
                bgOutlineColor: 'black'
            };
        }
        /**
         * Creates a function which retrieves next-generation (NXi, G3000, etc) icon styles for VNAV waypoints.
         * @param basePriority The base icon render priority. Icon priorities are guaranteed to fall in the range
         * `[basePriority, basePriority + 1)`.
         * @param scale The scaling factor for the icons. The larger the value, the larger the rendered icons. Defaults to
         * `1`.
         * @returns A function which retrieves next-generation (NXi, G3000, etc) icon styles for VNAV waypoints.
         */
        static vnavIconStyles(basePriority, scale = 1) {
            const vnavStyle = { priority: basePriority, size: msfsSdk.Vec2Math.create(32 * scale, 32 * scale) };
            return () => {
                return vnavStyle;
            };
        }
        /**
         * Creates a function which retrieves next-generation (NXi, G3000, etc) label styles for VNAV waypoints.
         * @param basePriority The base label render priority. Label priorities are guaranteed to fall in the range
         * `[basePriority, basePriority + 1)`.
         * @param fontType The type of font to use for the labels.
         * @param scale The scaling factor for the labels. The larger the value, the larger the rendered labels. Defaults to
         * `1`.
         * @returns A function which retrieves next-generation (NXi, G3000, etc) label styles for VNAV waypoints.
         */
        static vnavLabelStyles(basePriority, fontType, scale = 1) {
            let font, fontSize;
            if (fontType === 'Roboto') {
                font = 'Roboto';
                fontSize = 16 * scale;
            }
            else {
                font = 'DejaVuSans-SemiBold';
                fontSize = 14 * scale;
            }
            const vnavStyle = {
                priority: basePriority,
                alwaysShow: true,
                options: NextGenMapWaypointStyles.createNormalLabelOptions(msfsSdk.Vec2Math.create(0, -8 * scale), font, fontSize)
            };
            return () => {
                return vnavStyle;
            };
        }
        /**
         * Creates a function which retrieves next-generation (NXi, G3000, etc) icon styles for procedure preview waypoints.
         * @param basePriority The base icon render priority. Icon priorities are guaranteed to fall in the range
         * `[basePriority, basePriority + 1)`.
         * @param scale The scaling factor for the icons. The larger the value, the larger the rendered icons. Defaults to
         * `1`.
         * @returns A function which retrieves next-generation (NXi, G3000, etc) icon styles for procedure preview waypoints.
         */
        static procPreviewIconStyles(basePriority, scale = 1) {
            return NextGenMapWaypointStyles.flightPlanIconStyles(false, basePriority, scale);
        }
        /**
         * Creates a function which retrieves next-generation (NXi, G3000, etc) label styles for procedure preview waypoints.
         * @param basePriority The base label render priority. Label priorities are guaranteed to fall in the range
         * `[basePriority, basePriority + 1)`.
         * @param fontType The type of font to use for the labels.
         * @param scale The scaling factor for the labels. The larger the value, the larger the rendered labels. Defaults to
         * `1`.
         * @returns A function which retrieves next-generation (NXi, G3000, etc) label styles for procedure preview waypoints.
         */
        static procPreviewLabelStyles(basePriority, fontType, scale = 1) {
            return NextGenMapWaypointStyles.flightPlanLabelStyles(false, basePriority, fontType, scale);
        }
        /**
         * Creates a function which retrieves next-generation (NXi, G3000, etc) icon styles for procedure transition preview
         * waypoints.
         * @param basePriority The base icon render priority. Icon priorities are guaranteed to fall in the range
         * `[basePriority, basePriority + 1)`.
         * @param scale The scaling factor for the icons. The larger the value, the larger the rendered icons. Defaults to
         * `1`.
         * @returns A function which retrieves next-generation (NXi, G3000, etc) icon styles for procedure transition
         * preview waypoints.
         */
        static procTransitionPreviewIconStyles(basePriority, scale = 1) {
            return NextGenMapWaypointStyles.normalIconStyles(basePriority, scale);
        }
        /**
         * Creates a function which retrieves next-generation (NXi, G3000, etc) label styles for procedure transition preview
         * waypoints.
         * @param basePriority The base label render priority. Label priorities are guaranteed to fall in the range
         * `[basePriority, basePriority + 1)`.
         * @param fontType The type of font to use for the labels.
         * @param scale The scaling factor for the labels. The larger the value, the larger the rendered labels. Defaults to
         * `1`.
         * @returns A function which retrieves next-generation (NXi, G3000, etc) label styles for procedure preview transition
         * waypoints.
         */
        static procTransitionPreviewLabelStyles(basePriority, fontType, scale = 1) {
            return NextGenMapWaypointStyles.normalLabelStyles(basePriority, fontType, scale);
        }
    }

    /* eslint-disable jsdoc/require-jsdoc */
    /**
     * A builder for next-generation (NXi, G3000, etc) Garmin maps.
     */
    class NextGenGarminMapBuilder {
        /**
         * Configures a map builder to generate a map which supports the display of waypoints located within the boundaries
         * of the map's projected window. Waypoints displayed in this manner are rendered by a {@link MapWaypointRenderer}
         * under the role {@link MapWaypointRenderRole.Normal}. Optionally binds the visibility of waypoints to user
         * settings.
         *
         * If a text layer has already been added to the builder, its order will be changed so that it is rendered above the
         * waypoint layer. Otherwise, a text layer will be added to the builder after the waypoint layer.
         *
         * Adds the following...
         *
         * Context properties:
         * * `[MapSystemKeys.TextManager]: MapCullableTextLabelManager`
         * * `[MapSystemKeys.WaypointRenderer]: MapWaypointRenderer`
         * * `[GarminMapKeys.WaypointDisplayBuilder]: MapWaypointDisplayBuilder`
         *
         * Modules:
         * * `[MapSystemKeys.NearestWaypoints]: MapWaypointsModule`
         * * `[GarminMapKeys.Range]: MapIndexedRangeModule` (only if user settings are supported)
         *
         * Layers:
         * * `[MapSystemKeys.NearestWaypoints]: MapWaypointsLayer`
         * * `[MapSystemKeys.TextLayer]: MapCullableTextLayer`
         *
         * Controllers:
         * * `[MapSystemKeys.WaypointRenderer]: MapSystemCustomController` (handles initialization and updating of the
         * waypoint renderer)
         * * `[GarminMapKeys.WaypointsVisibility]: MapWaypointsVisController` (only if user settings are supported)
         * * `[GarminMapKeys.RunwayVisibility]: MapSymbolVisController` (only if runway outlines are supported)
         * * `[GarminMapKeys.RunwayLabelVisibility]: MapSymbolVisController` (only if runway outlines are supported)
         * @param mapBuilder The map builder to configure.
         * @param configure A function used to configure the display and styling of waypoint icons and labels.
         * @param supportRunwayOutlines Whether to support the rendering of airport runway outlines.
         * @param settingManager A setting manager containing the user settings controlling waypoint visibility. If not
         * defined, waypoint visibility will not be bound to user settings.
         * @param order The order to assign to the waypoint layer. Layers with lower assigned order will be attached to the
         * map before and appear below layers with greater assigned order values. Defaults to the number of layers already
         * added to the map builder.
         * @returns The map builder, after it has been configured.
         */
        static waypoints(mapBuilder, configure, supportRunwayOutlines, settingManager, order) {
            mapBuilder
                .with(GarminMapBuilder.waypoints, configure, supportRunwayOutlines, settingManager, order);
            if (supportRunwayOutlines && settingManager) {
                const trueSubject = msfsSdk.Subject.create(true);
                const maxSafeIntegerSubject = msfsSdk.Subject.create(Number.MAX_SAFE_INTEGER);
                mapBuilder
                    .withController(GarminMapKeys.RunwayVisibility, context => {
                    return new MapSymbolVisController(context, trueSubject, maxSafeIntegerSubject, exports.MapDeclutterMode.Level2, context.model.getModule(msfsSdk.MapSystemKeys.NearestWaypoints).runwayShow);
                })
                    .withController(GarminMapKeys.RunwayLabelVisibility, context => {
                    return new MapSymbolVisController(context, trueSubject, maxSafeIntegerSubject, exports.MapDeclutterMode.Level2, context.model.getModule(msfsSdk.MapSystemKeys.NearestWaypoints).runwayLabelShow);
                });
            }
            return mapBuilder;
        }
    }

    /* eslint-disable jsdoc/require-jsdoc */
    /**
     * Builds next-generation (NXi, G3000, etc) Garmin Connext weather maps.
     */
    class NextGenConnextMapBuilder {
        /**
         * Configures a map builder to generate a next-generation (NXi, G3000, etc) Garmin Connext weather map.
         *
         * The controller `[GarminMapKeys.Range]: MapRangeController` is added to the map context and can be used to control
         * the range of the map.
         *
         * If the map pointer is supported, the controller `[GarminMapKeys.Pointer]: MapPointerController` is added to the
         * map context and can be used to control the pointer.
         *
         * The map builder will **not** be configured to apply a custom projected size, dead zone, or to automatically update
         * the map.
         * @param mapBuilder The map builder to configure.
         * @param options Options for configuring the map.
         * @returns The builder, after it has been configured.
         */
        static build(mapBuilder, options) {
            var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3;
            var _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16;
            options = Object.assign({}, options); // so we don't mutate the object that was passed in.
            (_a = options.targetOffsets) !== null && _a !== void 0 ? _a : (options.targetOffsets = {});
            (_b = (_4 = options.targetOffsets)[_5 = exports.MapOrientation.NorthUp]) !== null && _b !== void 0 ? _b : (_4[_5] = msfsSdk.Vec2Math.create());
            (_c = (_6 = options.targetOffsets)[_7 = exports.MapOrientation.HeadingUp]) !== null && _c !== void 0 ? _c : (_6[_7] = msfsSdk.Vec2Math.create(0, 0.17));
            (_d = (_8 = options.targetOffsets)[_9 = exports.MapOrientation.TrackUp]) !== null && _d !== void 0 ? _d : (_8[_9] = msfsSdk.Vec2Math.create(0, 0.17));
            (_e = options.rangeEndpoints) !== null && _e !== void 0 ? _e : (options.rangeEndpoints = {});
            (_f = (_10 = options.rangeEndpoints)[_11 = exports.MapOrientation.NorthUp]) !== null && _f !== void 0 ? _f : (_10[_11] = msfsSdk.VecNMath.create(4, 0.5, 0.5, 0.5, 0.25));
            (_g = (_12 = options.rangeEndpoints)[_13 = exports.MapOrientation.HeadingUp]) !== null && _g !== void 0 ? _g : (_12[_13] = msfsSdk.VecNMath.create(4, 0.5, 0.67, 0.5, 0.33));
            (_h = (_14 = options.rangeEndpoints)[_15 = exports.MapOrientation.TrackUp]) !== null && _h !== void 0 ? _h : (_14[_15] = msfsSdk.VecNMath.create(4, 0.5, 0.67, 0.5, 0.33));
            (_j = options.supportDataIntegrity) !== null && _j !== void 0 ? _j : (options.supportDataIntegrity = true);
            (_k = options.useRangeUserSettingByDefault) !== null && _k !== void 0 ? _k : (options.useRangeUserSettingByDefault = true);
            (_l = options.useOrientationUserSettings) !== null && _l !== void 0 ? _l : (options.useOrientationUserSettings = true);
            (_m = options.radarOverlayMinRangeIndex) !== null && _m !== void 0 ? _m : (options.radarOverlayMinRangeIndex = 13);
            (_o = options.useRadarOverlayUserSettings) !== null && _o !== void 0 ? _o : (options.useRadarOverlayUserSettings = true);
            (_p = (_16 = options.rangeRingOptions).labelRadial) !== null && _p !== void 0 ? _p : (_16.labelRadial = 225);
            (_q = options.includeAirspaces) !== null && _q !== void 0 ? _q : (options.includeAirspaces = true);
            (_r = options.useAirspaceVisUserSettings) !== null && _r !== void 0 ? _r : (options.useAirspaceVisUserSettings = true);
            (_s = options.includeRunwayOutlines) !== null && _s !== void 0 ? _s : (options.includeRunwayOutlines = false);
            (_t = options.useWaypointVisUserSettings) !== null && _t !== void 0 ? _t : (options.useWaypointVisUserSettings = true);
            (_u = options.includeTrackVector) !== null && _u !== void 0 ? _u : (options.includeTrackVector = true);
            (_v = options.useTrackVectorUserSettings) !== null && _v !== void 0 ? _v : (options.useTrackVectorUserSettings = true);
            (_w = options.includeAltitudeArc) !== null && _w !== void 0 ? _w : (options.includeAltitudeArc = true);
            (_x = options.useAltitudeArcUserSettings) !== null && _x !== void 0 ? _x : (options.useAltitudeArcUserSettings = true);
            (_y = options.includeWindVector) !== null && _y !== void 0 ? _y : (options.includeWindVector = true);
            (_z = options.useWindVectorUserSettings) !== null && _z !== void 0 ? _z : (options.useWindVectorUserSettings = true);
            (_0 = options.includeOrientationIndicator) !== null && _0 !== void 0 ? _0 : (options.includeOrientationIndicator = true);
            (_1 = options.includeRangeIndicator) !== null && _1 !== void 0 ? _1 : (options.includeRangeIndicator = false);
            mapBuilder
                .withModule(GarminMapKeys.Units, () => new MapUnitsModule(options.unitsSettingManager))
                .with(GarminMapBuilder.range, (_2 = options.nauticalRangeArray) !== null && _2 !== void 0 ? _2 : MapUtils.nextGenMapRanges(exports.UnitsDistanceSettingMode.Nautical), (_3 = options.metricRangeArray) !== null && _3 !== void 0 ? _3 : MapUtils.nextGenMapRanges(exports.UnitsDistanceSettingMode.Metric), options.settingManager, options.useRangeUserSettingByDefault)
                .with(GarminMapBuilder.weatherOrientation, options.targetOffsets, options.rangeEndpoints, options.useOrientationUserSettings ? options.settingManager : undefined)
                .withBing(options.bingId, options.bingDelay)
                .withInit(GarminMapKeys.Terrain, context => {
                const def = MapUtils.noTerrainEarthColors();
                const terrainColorsModule = context.model.getModule(msfsSdk.MapSystemKeys.TerrainColors);
                terrainColorsModule.colorsElevationRange.set(def.elevationRange);
                terrainColorsModule.colors.set(def.colors);
            })
                .with(GarminMapBuilder.nexrad, options.radarOverlayMinRangeIndex, options.useRadarOverlayUserSettings ? options.settingManager : undefined, undefined, MapUtils.connextPrecipRadarColors());
            if (options.includeAirspaces) {
                mapBuilder.with(GarminMapBuilder.airspaces, options.useAirspaceVisUserSettings ? options.settingManager : undefined);
            }
            mapBuilder.with(NextGenGarminMapBuilder.waypoints, (builder) => {
                builder.withNormalStyles(options.waypointIconImageCache, NextGenMapWaypointStyles.normalIconStyles(1, options.waypointStyleScale), NextGenMapWaypointStyles.normalLabelStyles(1, options.waypointStyleFontType, options.waypointStyleScale), NextGenMapWaypointStyles.runwayOutlineIconStyles(1), options.runwayDesignationImageCache);
            }, options.includeRunwayOutlines, options.useWaypointVisUserSettings ? options.settingManager : undefined);
            if (options.flightPlanner) {
                mapBuilder.with(GarminMapBuilder.activeFlightPlan, options.flightPlanner, new DefaultFlightPathPlanRenderer(), false, (builder) => {
                    builder
                        .withFlightPlanInactiveStyles(options.waypointIconImageCache, NextGenMapWaypointStyles.flightPlanIconStyles(false, 2, options.waypointStyleScale), NextGenMapWaypointStyles.flightPlanLabelStyles(false, 2, options.waypointStyleFontType, options.waypointStyleScale))
                        .withFlightPlanActiveStyles(options.waypointIconImageCache, NextGenMapWaypointStyles.flightPlanIconStyles(true, 3, options.waypointStyleScale), NextGenMapWaypointStyles.flightPlanLabelStyles(true, 3, options.waypointStyleFontType, options.waypointStyleScale))
                        .withVNavStyles(options.waypointIconImageCache, NextGenMapWaypointStyles.vnavIconStyles(4, options.waypointStyleScale), NextGenMapWaypointStyles.vnavLabelStyles(4, options.waypointStyleFontType, options.waypointStyleScale));
                }, {
                    lnavIndex: options.lnavIndex,
                    vnavIndex: options.vnavIndex,
                    supportFocus: false
                });
            }
            mapBuilder
                .with(GarminMapBuilder.waypointHighlight, false, (builder) => {
                builder.withHighlightStyles(options.waypointIconImageCache, NextGenMapWaypointStyles.highlightIconStyles(5, options.waypointStyleScale), NextGenMapWaypointStyles.highlightLabelStyles(5, options.waypointStyleFontType, options.waypointStyleScale));
            });
            mapBuilder
                .with(GarminMapBuilder.rangeRing, options.rangeRingOptions)
                .with(GarminMapBuilder.rangeCompass, options.rangeCompassOptions);
            if (options.includeTrackVector) {
                mapBuilder.with(GarminMapBuilder.trackVector, {
                    arcTurnRateThreshold: 0.25,
                    arcMaxLookaheadTime: msfsSdk.UnitType.SECOND.createNumber(60)
                }, options.useTrackVectorUserSettings ? options.settingManager : undefined);
            }
            if (options.includeAltitudeArc) {
                mapBuilder.with(GarminMapBuilder.altitudeArc, {
                    renderMethod: 'svg',
                    verticalSpeedPrecision: msfsSdk.UnitType.FPM.createNumber(50),
                    verticalSpeedThreshold: msfsSdk.UnitType.FPM.createNumber(150),
                    altitudeDeviationThreshold: msfsSdk.UnitType.FOOT.createNumber(150)
                }, options.useAltitudeArcUserSettings ? options.settingManager : undefined);
            }
            mapBuilder.with(GarminMapBuilder.crosshair);
            let airplaneIconSrc = options.airplaneIconSrc;
            let airplaneIconAnchor = options.airplaneIconAnchor;
            if (options.supportDataIntegrity && options.noHeadingAirplaneIconSrc !== undefined && options.noHeadingAirplaneIconAnchor !== undefined) {
                airplaneIconSrc = msfsSdk.Subject.create('');
                airplaneIconAnchor = msfsSdk.Vec2Subject.create(msfsSdk.Vec2Math.create());
            }
            const airplanePropBindings = [
                'position', 'hdgTrue', 'magVar', 'isOnGround',
                ...(options.includeTrackVector || options.includeAltitudeArc
                    ? ['groundSpeed', 'trackTrue']
                    : []),
                ...(options.includeTrackVector
                    ? ['turnRate']
                    : []),
                ...(options.includeAltitudeArc
                    ? ['altitude', 'verticalSpeed']
                    : [])
            ];
            mapBuilder
                .withOwnAirplaneIcon(options.airplaneIconSize, airplaneIconSrc, airplaneIconAnchor)
                .withOwnAirplanePropBindings(airplanePropBindings, options.dataUpdateFreq)
                .withFollowAirplane();
            const autopilotPropBindings = [
                'selectedHeading', 'manualHeadingSelect',
                ...(options.includeAltitudeArc
                    ? ['selectedAltitude']
                    : [])
            ];
            mapBuilder.with(GarminMapBuilder.autopilotProps, autopilotPropBindings, options.dataUpdateFreq);
            if (options.supportDataIntegrity) {
                mapBuilder.withLayer(GarminMapKeys.DeadReckoning, context => {
                    return (msfsSdk.FSComponent.buildComponent(MapDeadReckoningLayer, { model: context.model, mapProjection: context.projection, airplaneIconSize: options.airplaneIconSize, airplaneIconAnchor: airplaneIconAnchor }));
                });
            }
            if (options.miniCompassImgSrc !== undefined) {
                mapBuilder.with(GarminMapBuilder.miniCompass, options.miniCompassImgSrc);
            }
            if (options.includeWindVector && options.windDataProvider) {
                mapBuilder.with(GarminMapBuilder.windVector, options.windDataProvider, options.useWindVectorUserSettings ? options.settingManager : undefined);
            }
            // Top-left indicators
            if (options.includeOrientationIndicator || options.includeRangeIndicator) {
                const orientationRef = msfsSdk.FSComponent.createRef();
                const rangeRef = msfsSdk.FSComponent.createRef();
                const factories = [];
                if (options.includeOrientationIndicator) {
                    factories.push((context) => {
                        var _a, _b;
                        const orientationModule = context.model.getModule(GarminMapKeys.Orientation);
                        const pointerModule = context.model.getModule(GarminMapKeys.Pointer);
                        const focusModule = context.model.getModule(GarminMapKeys.FlightPlanFocus);
                        const isVisible = msfsSdk.MappedSubject.create(([isPointerActive, isFocusActive]) => !isPointerActive && !isFocusActive, (_a = pointerModule === null || pointerModule === void 0 ? void 0 : pointerModule.isActive) !== null && _a !== void 0 ? _a : msfsSdk.Subject.create(false), (_b = focusModule === null || focusModule === void 0 ? void 0 : focusModule.isActive) !== null && _b !== void 0 ? _b : msfsSdk.Subject.create(false));
                        return (msfsSdk.FSComponent.buildComponent(MapOrientationIndicator, { ref: orientationRef, orientation: orientationModule.orientation, text: {
                                [exports.MapOrientation.NorthUp]: 'NORTH UP',
                                [exports.MapOrientation.HeadingUp]: 'HDG UP',
                                [exports.MapOrientation.TrackUp]: 'TRK UP',
                            }, isVisible: isVisible }));
                    });
                }
                if (options.includeRangeIndicator) {
                    factories.push((context) => {
                        const rangeModule = context.model.getModule(GarminMapKeys.Range);
                        const unitsModule = context.model.getModule(GarminMapKeys.Units);
                        return (msfsSdk.FSComponent.buildComponent(MapRangeDisplay, { ref: rangeRef, range: rangeModule.nominalRange, displayUnit: unitsModule.distanceLarge }));
                    });
                }
                mapBuilder.with(GarminMapBuilder.indicatorGroup, GarminMapKeys.TopLeftIndicators, factories, {
                    onDetached: () => {
                        var _a, _b;
                        (_a = orientationRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
                        (_b = rangeRef.getOrDefault()) === null || _b === void 0 ? void 0 : _b.destroy();
                    }
                }, 'map-indicator-group-top-left');
            }
            // Center indicators
            if (options.supportDataIntegrity) {
                const noGpsRef = msfsSdk.FSComponent.createRef();
                const factories = [];
                if (options.supportDataIntegrity) {
                    factories.push((context) => {
                        var _a;
                        const dataIntegrityModule = context.model.getModule(msfsSdk.MapSystemKeys.DataIntegrity);
                        return (msfsSdk.FSComponent.buildComponent(MapBannerIndicator, { ref: noGpsRef, show: dataIntegrityModule.gpsSignalValid.map(isValid => !isValid), class: 'map-banner-no-gps' }, (_a = options.noGpsBannerText) !== null && _a !== void 0 ? _a : 'NO GPS POSITION'));
                    });
                }
                mapBuilder.with(GarminMapBuilder.indicatorGroup, GarminMapKeys.CenterIndicators, factories, {
                    onDetached: () => {
                        var _a;
                        (_a = noGpsRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
                    }
                }, 'map-indicator-group-center');
            }
            if (options.pointerBoundsOffset !== undefined) {
                mapBuilder.with(GarminMapBuilder.pointer, options.pointerBoundsOffset);
                if (options.pointerInfoSize !== undefined) {
                    mapBuilder.with(GarminMapBuilder.pointerInfo, options.pointerInfoSize);
                }
            }
            if (options.supportDataIntegrity) {
                let airplaneIconSrcToUse;
                let airplaneIconAnchorToUse;
                if (options.noHeadingAirplaneIconSrc !== undefined && options.noHeadingAirplaneIconAnchor !== undefined) {
                    airplaneIconSrcToUse = airplaneIconSrc;
                    airplaneIconAnchorToUse = airplaneIconAnchor;
                }
                mapBuilder.with(GarminMapBuilder.dataIntegrity, airplaneIconSrcToUse, airplaneIconAnchorToUse, options.airplaneIconSrc, options.airplaneIconAnchor, options.noHeadingAirplaneIconSrc, options.noHeadingAirplaneIconAnchor);
            }
            return mapBuilder;
        }
    }

    /* eslint-disable jsdoc/require-jsdoc */
    /**
     * Builds next-generation (NXi, G3000, etc) Garmin HSI maps.
     */
    class NextGenHsiMapBuilder {
        /**
         * Configures a map builder to generate a next-generation (NXi, G3000, etc) Garmin HSI map.
         *
         * The controller `[GarminMapKeys.Range]: MapRangeController` is added to the map context and can be used to control
         * the range of the map.
         *
         * The map builder will **not** be configured to apply a custom projected size, dead zone, or to automatically update
         * the map.
         * @param mapBuilder The map builder to configure.
         * @param options Options for configuring the map.
         * @returns The builder, after it has been configured.
         */
        static build(mapBuilder, options) {
            var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1;
            options = Object.assign({}, options); // so we don't mutate the object that was passed in.
            (_a = options.targetOffset) !== null && _a !== void 0 ? _a : (options.targetOffset = msfsSdk.Vec2Math.create());
            (_b = options.rangeEndpoints) !== null && _b !== void 0 ? _b : (options.rangeEndpoints = msfsSdk.VecNMath.create(4, 0.5, 0.5, 0.5, 0));
            (_c = options.supportDataIntegrity) !== null && _c !== void 0 ? _c : (options.supportDataIntegrity = true);
            (_d = options.useRangeUserSettingByDefault) !== null && _d !== void 0 ? _d : (options.useRangeUserSettingByDefault = true);
            (_e = options.useDeclutterUserSetting) !== null && _e !== void 0 ? _e : (options.useDeclutterUserSetting = true);
            (_f = options.useTerrainUserSettings) !== null && _f !== void 0 ? _f : (options.useTerrainUserSettings = true);
            (_g = options.allowRelativeTerrainMode) !== null && _g !== void 0 ? _g : (options.allowRelativeTerrainMode = true);
            (_h = options.groundRelativeTerrainBlendDuration) !== null && _h !== void 0 ? _h : (options.groundRelativeTerrainBlendDuration = 10000);
            (_j = options.nexradMinRangeIndex) !== null && _j !== void 0 ? _j : (options.nexradMinRangeIndex = 13);
            (_k = options.useNexradUserSettings) !== null && _k !== void 0 ? _k : (options.useNexradUserSettings = true);
            (_l = options.includeAirspaces) !== null && _l !== void 0 ? _l : (options.includeAirspaces = true);
            (_m = options.useAirspaceVisUserSettings) !== null && _m !== void 0 ? _m : (options.useAirspaceVisUserSettings = true);
            (_o = options.includeRunwayOutlines) !== null && _o !== void 0 ? _o : (options.includeRunwayOutlines = false);
            (_p = options.useWaypointVisUserSettings) !== null && _p !== void 0 ? _p : (options.useWaypointVisUserSettings = true);
            (_q = options.includeTrackVector) !== null && _q !== void 0 ? _q : (options.includeTrackVector = true);
            (_r = options.useTrackVectorUserSettings) !== null && _r !== void 0 ? _r : (options.useTrackVectorUserSettings = true);
            (_s = options.includeAltitudeArc) !== null && _s !== void 0 ? _s : (options.includeAltitudeArc = true);
            (_t = options.useAltitudeArcUserSettings) !== null && _t !== void 0 ? _t : (options.useAltitudeArcUserSettings = true);
            (_u = options.includeRangeIndicator) !== null && _u !== void 0 ? _u : (options.includeRangeIndicator = true);
            (_v = options.includeDetailIndicator) !== null && _v !== void 0 ? _v : (options.includeDetailIndicator = true);
            (_w = options.includeTrafficOffScaleIndicator) !== null && _w !== void 0 ? _w : (options.includeTrafficOffScaleIndicator = true);
            (_x = options.includeTrafficStatusIndicator) !== null && _x !== void 0 ? _x : (options.includeTrafficStatusIndicator = true);
            mapBuilder
                .withModule(GarminMapKeys.Units, () => new MapUnitsModule(options.unitsSettingManager))
                .with(GarminMapBuilder.range, (_y = options.nauticalRangeArray) !== null && _y !== void 0 ? _y : MapUtils.nextGenMapRanges(exports.UnitsDistanceSettingMode.Nautical), (_z = options.metricRangeArray) !== null && _z !== void 0 ? _z : MapUtils.nextGenMapRanges(exports.UnitsDistanceSettingMode.Metric), options.settingManager, options.useRangeUserSettingByDefault)
                .with(GarminMapBuilder.orientation, { [exports.MapOrientation.HeadingUp]: options.targetOffset }, { [exports.MapOrientation.HeadingUp]: options.rangeEndpoints })
                .withController('hsiMapOrientation', context => {
                const orientationModule = context.model.getModule(GarminMapKeys.Orientation);
                const orientationControlConsumer = {
                    priority: Number.MAX_SAFE_INTEGER,
                    onAcquired: () => {
                        orientationModule.orientation.set(exports.MapOrientation.HeadingUp);
                    },
                    onCeded: () => { }
                };
                let controller;
                return controller = new msfsSdk.MapSystemGenericController(context, {
                    onAfterMapRender: (contextArg) => {
                        contextArg[GarminMapKeys.OrientationControl].claim(orientationControlConsumer);
                    },
                    onMapDestroyed: () => {
                        controller.destroy();
                    },
                    onDestroyed: (contextArg) => {
                        contextArg[GarminMapKeys.OrientationControl].forfeit(orientationControlConsumer);
                    }
                });
            })
                .with(GarminMapBuilder.declutter, options.useDeclutterUserSetting ? options.settingManager : undefined)
                .withBing(options.bingId, options.bingDelay)
                .with(GarminMapBuilder.terrainColors, {
                [exports.MapTerrainMode.None]: MapUtils.noTerrainEarthColors(),
                [exports.MapTerrainMode.Absolute]: MapUtils.absoluteTerrainEarthColors(),
                [exports.MapTerrainMode.Relative]: MapUtils.relativeTerrainEarthColors(),
                [exports.MapTerrainMode.Ground]: MapUtils.groundTerrainEarthColors()
            }, options.useTerrainUserSettings ? options.settingManager : undefined, options.allowRelativeTerrainMode, options.groundRelativeTerrainBlendDuration)
                .with(GarminMapBuilder.nexrad, options.nexradMinRangeIndex, options.useNexradUserSettings ? options.settingManager : undefined, exports.MapDeclutterMode.Level2, MapUtils.connextPrecipRadarColors());
            if (options.includeAirspaces) {
                mapBuilder.with(GarminMapBuilder.airspaces, options.useAirspaceVisUserSettings ? options.settingManager : undefined);
            }
            mapBuilder.with(NextGenGarminMapBuilder.waypoints, (builder) => {
                builder.withNormalStyles(options.waypointIconImageCache, NextGenMapWaypointStyles.normalIconStyles(1, options.waypointStyleScale), NextGenMapWaypointStyles.normalLabelStyles(1, options.waypointStyleFontType, options.waypointStyleScale), NextGenMapWaypointStyles.runwayOutlineIconStyles(1), options.runwayDesignationImageCache);
            }, options.includeRunwayOutlines, options.useWaypointVisUserSettings ? options.settingManager : undefined);
            if (options.flightPlanner) {
                mapBuilder.with(GarminMapBuilder.activeFlightPlan, options.flightPlanner, new DefaultFlightPathPlanRenderer(), false, (builder) => {
                    builder
                        .withFlightPlanInactiveStyles(options.waypointIconImageCache, NextGenMapWaypointStyles.flightPlanIconStyles(false, 2, options.waypointStyleScale), NextGenMapWaypointStyles.flightPlanLabelStyles(false, 2, options.waypointStyleFontType, options.waypointStyleScale))
                        .withFlightPlanActiveStyles(options.waypointIconImageCache, NextGenMapWaypointStyles.flightPlanIconStyles(true, 3, options.waypointStyleScale), NextGenMapWaypointStyles.flightPlanLabelStyles(true, 3, options.waypointStyleFontType, options.waypointStyleScale))
                        .withVNavStyles(options.waypointIconImageCache, NextGenMapWaypointStyles.vnavIconStyles(4, options.waypointStyleScale), NextGenMapWaypointStyles.vnavLabelStyles(4, options.waypointStyleFontType, options.waypointStyleScale));
                }, {
                    lnavIndex: options.lnavIndex,
                    vnavIndex: options.vnavIndex,
                    supportFocus: false
                });
            }
            mapBuilder
                .with(GarminMapBuilder.waypointHighlight, false, (builder) => {
                builder.withHighlightStyles(options.waypointIconImageCache, NextGenMapWaypointStyles.highlightIconStyles(5, options.waypointStyleScale), NextGenMapWaypointStyles.highlightLabelStyles(5, options.waypointStyleFontType, options.waypointStyleScale));
            });
            if (options.includeTrackVector) {
                mapBuilder.with(GarminMapBuilder.trackVector, {
                    arcTurnRateThreshold: 0.25,
                    arcMaxLookaheadTime: msfsSdk.UnitType.SECOND.createNumber(60)
                }, options.useTrackVectorUserSettings ? options.settingManager : undefined);
            }
            if (options.includeAltitudeArc) {
                mapBuilder
                    .withAutopilotProps(['selectedAltitude'], options.dataUpdateFreq)
                    .with(GarminMapBuilder.altitudeArc, {
                    renderMethod: 'canvas',
                    verticalSpeedPrecision: msfsSdk.UnitType.FPM.createNumber(50),
                    verticalSpeedThreshold: msfsSdk.UnitType.FPM.createNumber(150),
                    altitudeDeviationThreshold: msfsSdk.UnitType.FOOT.createNumber(150)
                }, options.useAltitudeArcUserSettings ? options.settingManager : undefined);
            }
            mapBuilder.with(GarminMapBuilder.crosshair);
            const supportTraffic = options.trafficSystem !== undefined
                && options.trafficIconOptions !== undefined;
            let offScale;
            if (supportTraffic && options.includeTrafficOffScaleIndicator) {
                offScale = msfsSdk.Subject.create(exports.MapTrafficOffScaleStatus.None);
            }
            if (supportTraffic) {
                const iconOptions = Object.assign({}, options.trafficIconOptions);
                (_0 = iconOptions.drawOffScale) !== null && _0 !== void 0 ? _0 : (iconOptions.drawOffScale = false);
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                (_1 = iconOptions.supportAdsbVector) !== null && _1 !== void 0 ? _1 : (iconOptions.supportAdsbVector = options.trafficSystem.adsb !== null);
                mapBuilder
                    .withController('hsiMapTrafficOuterRange', context => {
                    let controller;
                    let rangePipe;
                    return controller = new msfsSdk.MapSystemGenericController(context, {
                        onAfterMapRender: (contextArg) => {
                            rangePipe = contextArg.model.getModule(GarminMapKeys.Range).nominalRangeIndex.pipe(contextArg.model.getModule(GarminMapKeys.Traffic).outerRangeIndex);
                        },
                        onMapDestroyed: () => {
                            controller.destroy();
                        },
                        onDestroyed: () => {
                            rangePipe === null || rangePipe === void 0 ? void 0 : rangePipe.destroy();
                        }
                    });
                })
                    .with(GarminMapBuilder.traffic, 
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                options.trafficSystem, iconOptions, true, offScale, undefined, undefined, options.trafficSettingManager, options.settingManager);
            }
            let airplaneIconSrc = options.airplaneIconSrc;
            let airplaneIconAnchor = options.airplaneIconAnchor;
            if (options.supportDataIntegrity && options.noHeadingAirplaneIconSrc !== undefined && options.noHeadingAirplaneIconAnchor !== undefined) {
                airplaneIconSrc = msfsSdk.Subject.create('');
                airplaneIconAnchor = msfsSdk.Vec2Subject.create(msfsSdk.Vec2Math.create());
            }
            const airplanePropBindings = [
                'position', 'hdgTrue', 'magVar', 'isOnGround',
                ...(options.includeTrackVector || options.includeAltitudeArc
                    ? ['groundSpeed', 'trackTrue']
                    : []),
                ...(options.includeTrackVector
                    ? ['turnRate']
                    : []),
                ...(options.includeAltitudeArc
                    ? ['altitude', 'verticalSpeed']
                    : [])
            ];
            mapBuilder
                .withOwnAirplaneIcon(options.airplaneIconSize, airplaneIconSrc, airplaneIconAnchor)
                .withOwnAirplanePropBindings(airplanePropBindings, options.dataUpdateFreq)
                .withFollowAirplane()
                .withInit(msfsSdk.MapSystemKeys.OwnAirplaneIconOrientation, context => {
                context.model.getModule(msfsSdk.MapSystemKeys.OwnAirplaneIcon).orientation.set(msfsSdk.MapOwnAirplaneIconOrientation.MapUp);
            });
            if (options.supportDataIntegrity) {
                mapBuilder.withLayer(GarminMapKeys.DeadReckoning, context => {
                    return (msfsSdk.FSComponent.buildComponent(MapDeadReckoningLayer, { model: context.model, mapProjection: context.projection, airplaneIconSize: options.airplaneIconSize, airplaneIconAnchor: airplaneIconAnchor }));
                });
            }
            // Bottom-left indicators
            if (options.includeDetailIndicator || options.includeRangeIndicator) {
                const detailRef = msfsSdk.FSComponent.createRef();
                const rangeRef = msfsSdk.FSComponent.createRef();
                const factories = [];
                if (options.includeDetailIndicator) {
                    factories.push((context) => {
                        const declutterModule = context.model.getModule(GarminMapKeys.Declutter);
                        return (msfsSdk.FSComponent.buildComponent(MapDetailIndicator, { ref: detailRef, declutterMode: declutterModule.mode, showTitle: false }));
                    });
                }
                if (options.includeRangeIndicator) {
                    factories.push((context) => {
                        const rangeModule = context.model.getModule(GarminMapKeys.Range);
                        const unitsModule = context.model.getModule(GarminMapKeys.Units);
                        return (msfsSdk.FSComponent.buildComponent(MapRangeDisplay, { ref: rangeRef, range: rangeModule.nominalRange, displayUnit: unitsModule.distanceLarge }));
                    });
                }
                mapBuilder.with(GarminMapBuilder.indicatorGroup, GarminMapKeys.BottomLeftIndicators, factories, {
                    onDetached: () => {
                        var _a, _b;
                        (_a = detailRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
                        (_b = rangeRef.getOrDefault()) === null || _b === void 0 ? void 0 : _b.destroy();
                    }
                }, 'map-indicator-group-bottom-left');
            }
            // Bottom-center indicators
            if (supportTraffic && (options.includeTrafficOffScaleIndicator || options.includeTrafficStatusIndicator)) {
                const statusRef = msfsSdk.FSComponent.createRef();
                const offScaleRef = msfsSdk.FSComponent.createRef();
                const factories = [];
                if (options.includeTrafficOffScaleIndicator) {
                    factories.push(() => {
                        return (msfsSdk.FSComponent.buildComponent(MapTrafficOffScaleIndicator, { ref: offScaleRef, status: offScale }));
                    });
                }
                if (options.includeTrafficStatusIndicator) {
                    factories.push((context) => {
                        const trafficModule = context.model.getModule(GarminMapKeys.Traffic);
                        return (msfsSdk.FSComponent.buildComponent(MapTrafficStatusIndicator, { ref: statusRef, show: trafficModule.show, operatingMode: trafficModule.operatingMode }));
                    });
                }
                mapBuilder.with(GarminMapBuilder.indicatorGroup, GarminMapKeys.BottomCenterIndicators, factories, {
                    onDetached: () => {
                        var _a, _b;
                        (_a = statusRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
                        (_b = offScaleRef.getOrDefault()) === null || _b === void 0 ? void 0 : _b.destroy();
                    }
                }, 'map-indicator-group-bottom-center');
            }
            if (options.supportDataIntegrity) {
                let airplaneIconSrcToUse;
                let airplaneIconAnchorToUse;
                if (options.noHeadingAirplaneIconSrc !== undefined && options.noHeadingAirplaneIconAnchor !== undefined) {
                    airplaneIconSrcToUse = airplaneIconSrc;
                    airplaneIconAnchorToUse = airplaneIconAnchor;
                }
                mapBuilder.with(GarminMapBuilder.dataIntegrity, airplaneIconSrcToUse, airplaneIconAnchorToUse, options.airplaneIconSrc, options.airplaneIconAnchor, options.noHeadingAirplaneIconSrc, options.noHeadingAirplaneIconAnchor);
            }
            return mapBuilder;
        }
    }

    /* eslint-disable jsdoc/require-jsdoc */
    /**
     * Builds next-generation (NXi, G3000, etc) Garmin navigation maps.
     */
    class NextGenNavMapBuilder {
        /**
         * Configures a map builder to generate a next-generation (NXi, G3000, etc) Garmin navigation map.
         *
         * The controller `[GarminMapKeys.Range]: MapRangeController` is added to the map context and can be used to control
         * the range of the map.
         *
         * If flight plan focus is supported, the module `[GarminMapKeys.FlightPlanFocus]: MapFlightPlanFocusModule` is added
         * to the map model and can be used to control the focus.
         *
         * If the map pointer is supported, the controller `[GarminMapKeys.Pointer]: MapPointerController` is added to the
         * map context and can be used to control the pointer.
         *
         * The map builder will **not** be configured to apply a custom projected size, dead zone, or to automatically update
         * the map.
         * @param mapBuilder The map builder to configure.
         * @param options Options for configuring the map.
         * @returns The builder, after it has been configured.
         */
        static build(mapBuilder, options) {
            var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19;
            var _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32;
            options = Object.assign({}, options); // so we don't mutate the object that was passed in.
            (_a = options.targetOffsets) !== null && _a !== void 0 ? _a : (options.targetOffsets = {});
            (_b = (_20 = options.targetOffsets)[_21 = exports.MapOrientation.NorthUp]) !== null && _b !== void 0 ? _b : (_20[_21] = msfsSdk.Vec2Math.create());
            (_c = (_22 = options.targetOffsets)[_23 = exports.MapOrientation.HeadingUp]) !== null && _c !== void 0 ? _c : (_22[_23] = msfsSdk.Vec2Math.create(0, 0.17));
            (_d = (_24 = options.targetOffsets)[_25 = exports.MapOrientation.TrackUp]) !== null && _d !== void 0 ? _d : (_24[_25] = msfsSdk.Vec2Math.create(0, 0.17));
            (_e = options.rangeEndpoints) !== null && _e !== void 0 ? _e : (options.rangeEndpoints = {});
            (_f = (_26 = options.rangeEndpoints)[_27 = exports.MapOrientation.NorthUp]) !== null && _f !== void 0 ? _f : (_26[_27] = msfsSdk.VecNMath.create(4, 0.5, 0.5, 0.5, 0.25));
            (_g = (_28 = options.rangeEndpoints)[_29 = exports.MapOrientation.HeadingUp]) !== null && _g !== void 0 ? _g : (_28[_29] = msfsSdk.VecNMath.create(4, 0.5, 0.67, 0.5, 0.33));
            (_h = (_30 = options.rangeEndpoints)[_31 = exports.MapOrientation.TrackUp]) !== null && _h !== void 0 ? _h : (_30[_31] = msfsSdk.VecNMath.create(4, 0.5, 0.67, 0.5, 0.33));
            (_j = options.supportDataIntegrity) !== null && _j !== void 0 ? _j : (options.supportDataIntegrity = true);
            (_k = options.useRangeUserSettingByDefault) !== null && _k !== void 0 ? _k : (options.useRangeUserSettingByDefault = true);
            (_l = options.useOrientationUserSettings) !== null && _l !== void 0 ? _l : (options.useOrientationUserSettings = true);
            (_m = options.useDeclutterUserSetting) !== null && _m !== void 0 ? _m : (options.useDeclutterUserSetting = true);
            (_o = options.useTerrainUserSettings) !== null && _o !== void 0 ? _o : (options.useTerrainUserSettings = true);
            (_p = options.allowRelativeTerrainMode) !== null && _p !== void 0 ? _p : (options.allowRelativeTerrainMode = true);
            (_q = options.groundRelativeTerrainBlendDuration) !== null && _q !== void 0 ? _q : (options.groundRelativeTerrainBlendDuration = 10000);
            (_r = options.nexradMinRangeIndex) !== null && _r !== void 0 ? _r : (options.nexradMinRangeIndex = 13);
            (_s = options.useNexradUserSettings) !== null && _s !== void 0 ? _s : (options.useNexradUserSettings = true);
            (_t = options.airplaneIconOrientation) !== null && _t !== void 0 ? _t : (options.airplaneIconOrientation = msfsSdk.MapOwnAirplaneIconOrientation.HeadingUp);
            (_u = (_32 = options.rangeRingOptions).labelRadial) !== null && _u !== void 0 ? _u : (_32.labelRadial = 225);
            (_v = options.includeAirspaces) !== null && _v !== void 0 ? _v : (options.includeAirspaces = true);
            (_w = options.useAirspaceVisUserSettings) !== null && _w !== void 0 ? _w : (options.useAirspaceVisUserSettings = true);
            (_x = options.includeRunwayOutlines) !== null && _x !== void 0 ? _x : (options.includeRunwayOutlines = false);
            (_y = options.useWaypointVisUserSettings) !== null && _y !== void 0 ? _y : (options.useWaypointVisUserSettings = true);
            (_z = options.drawEntirePlan) !== null && _z !== void 0 ? _z : (options.drawEntirePlan = false);
            (_0 = options.supportFlightPlanFocus) !== null && _0 !== void 0 ? _0 : (options.supportFlightPlanFocus = false);
            (_1 = options.defaultFocusRangeIndex) !== null && _1 !== void 0 ? _1 : (options.defaultFocusRangeIndex = 17);
            (_2 = options.includeTrackVector) !== null && _2 !== void 0 ? _2 : (options.includeTrackVector = true);
            (_3 = options.useTrackVectorUserSettings) !== null && _3 !== void 0 ? _3 : (options.useTrackVectorUserSettings = true);
            (_4 = options.includeAltitudeArc) !== null && _4 !== void 0 ? _4 : (options.includeAltitudeArc = true);
            (_5 = options.useAltitudeArcUserSettings) !== null && _5 !== void 0 ? _5 : (options.useAltitudeArcUserSettings = true);
            (_6 = options.includeWindVector) !== null && _6 !== void 0 ? _6 : (options.includeWindVector = true);
            (_7 = options.useWindVectorUserSettings) !== null && _7 !== void 0 ? _7 : (options.useWindVectorUserSettings = true);
            (_8 = options.includeOrientationIndicator) !== null && _8 !== void 0 ? _8 : (options.includeOrientationIndicator = true);
            (_9 = options.includeRangeIndicator) !== null && _9 !== void 0 ? _9 : (options.includeRangeIndicator = false);
            (_10 = options.includeDetailIndicator) !== null && _10 !== void 0 ? _10 : (options.includeDetailIndicator = true);
            (_11 = options.showDetailIndicatorTitle) !== null && _11 !== void 0 ? _11 : (options.showDetailIndicatorTitle = true);
            (_12 = options.includeTrafficOffScaleIndicator) !== null && _12 !== void 0 ? _12 : (options.includeTrafficOffScaleIndicator = true);
            (_13 = options.includeTrafficFailedIndicator) !== null && _13 !== void 0 ? _13 : (options.includeTrafficFailedIndicator = true);
            (_14 = options.includeTerrainScale) !== null && _14 !== void 0 ? _14 : (options.includeTerrainScale = true);
            (_15 = options.includeTrafficStatusIndicator) !== null && _15 !== void 0 ? _15 : (options.includeTrafficStatusIndicator = true);
            (_16 = options.showTrafficAltRestriction) !== null && _16 !== void 0 ? _16 : (options.showTrafficAltRestriction = true);
            mapBuilder
                .withModule(GarminMapKeys.Units, () => new MapUnitsModule(options.unitsSettingManager))
                .with(GarminMapBuilder.range, (_17 = options.nauticalRangeArray) !== null && _17 !== void 0 ? _17 : MapUtils.nextGenMapRanges(exports.UnitsDistanceSettingMode.Nautical), (_18 = options.metricRangeArray) !== null && _18 !== void 0 ? _18 : MapUtils.nextGenMapRanges(exports.UnitsDistanceSettingMode.Metric), options.settingManager, options.useRangeUserSettingByDefault)
                .with(GarminMapBuilder.orientation, options.targetOffsets, options.rangeEndpoints, options.useOrientationUserSettings ? options.settingManager : undefined)
                .with(GarminMapBuilder.declutter, options.useDeclutterUserSetting ? options.settingManager : undefined)
                .withBing(options.bingId, options.bingDelay)
                .with(GarminMapBuilder.terrainColors, {
                [exports.MapTerrainMode.None]: MapUtils.noTerrainEarthColors(),
                [exports.MapTerrainMode.Absolute]: MapUtils.absoluteTerrainEarthColors(),
                [exports.MapTerrainMode.Relative]: MapUtils.relativeTerrainEarthColors(),
                [exports.MapTerrainMode.Ground]: MapUtils.groundTerrainEarthColors()
            }, options.useTerrainUserSettings ? options.settingManager : undefined, options.allowRelativeTerrainMode, options.groundRelativeTerrainBlendDuration)
                .with(GarminMapBuilder.nexrad, options.nexradMinRangeIndex, options.useNexradUserSettings ? options.settingManager : undefined, exports.MapDeclutterMode.Level2, MapUtils.connextPrecipRadarColors());
            if (options.includeAirspaces) {
                mapBuilder.with(GarminMapBuilder.airspaces, options.useAirspaceVisUserSettings ? options.settingManager : undefined);
            }
            mapBuilder.with(NextGenGarminMapBuilder.waypoints, (builder) => {
                builder.withNormalStyles(options.waypointIconImageCache, NextGenMapWaypointStyles.normalIconStyles(1, options.waypointStyleScale), NextGenMapWaypointStyles.normalLabelStyles(1, options.waypointStyleFontType, options.waypointStyleScale), NextGenMapWaypointStyles.runwayOutlineIconStyles(1), options.runwayDesignationImageCache);
            }, options.includeRunwayOutlines, options.useWaypointVisUserSettings ? options.settingManager : undefined);
            if (options.flightPlanner) {
                if (options.supportFlightPlanFocus) {
                    mapBuilder.with(GarminMapBuilder.flightPlanFocus, options.nominalFocusMargins, options.defaultFocusRangeIndex);
                }
                mapBuilder.with(GarminMapBuilder.activeFlightPlan, options.flightPlanner, new DefaultFlightPathPlanRenderer(), options.drawEntirePlan, (builder) => {
                    builder
                        .withFlightPlanInactiveStyles(options.waypointIconImageCache, NextGenMapWaypointStyles.flightPlanIconStyles(false, 2, options.waypointStyleScale), NextGenMapWaypointStyles.flightPlanLabelStyles(false, 2, options.waypointStyleFontType, options.waypointStyleScale))
                        .withFlightPlanActiveStyles(options.waypointIconImageCache, NextGenMapWaypointStyles.flightPlanIconStyles(true, 3, options.waypointStyleScale), NextGenMapWaypointStyles.flightPlanLabelStyles(true, 3, options.waypointStyleFontType, options.waypointStyleScale))
                        .withVNavStyles(options.waypointIconImageCache, NextGenMapWaypointStyles.vnavIconStyles(4, options.waypointStyleScale), NextGenMapWaypointStyles.vnavLabelStyles(4, options.waypointStyleFontType, options.waypointStyleScale));
                }, {
                    lnavIndex: options.lnavIndex,
                    vnavIndex: options.vnavIndex,
                    supportFocus: options.supportFlightPlanFocus
                });
            }
            mapBuilder
                .with(GarminMapBuilder.waypointHighlight, false, (builder) => {
                builder.withHighlightStyles(options.waypointIconImageCache, NextGenMapWaypointStyles.highlightIconStyles(5, options.waypointStyleScale), NextGenMapWaypointStyles.highlightLabelStyles(5, options.waypointStyleFontType, options.waypointStyleScale));
            });
            mapBuilder
                .with(GarminMapBuilder.rangeRing, options.rangeRingOptions)
                .with(GarminMapBuilder.rangeCompass, options.rangeCompassOptions);
            if (options.includeTrackVector) {
                mapBuilder.with(GarminMapBuilder.trackVector, {
                    arcTurnRateThreshold: 0.25,
                    arcMaxLookaheadTime: msfsSdk.UnitType.SECOND.createNumber(60)
                }, options.useTrackVectorUserSettings ? options.settingManager : undefined);
            }
            if (options.includeAltitudeArc) {
                mapBuilder.with(GarminMapBuilder.altitudeArc, {
                    renderMethod: 'svg',
                    verticalSpeedPrecision: msfsSdk.UnitType.FPM.createNumber(50),
                    verticalSpeedThreshold: msfsSdk.UnitType.FPM.createNumber(150),
                    altitudeDeviationThreshold: msfsSdk.UnitType.FOOT.createNumber(150)
                }, options.useAltitudeArcUserSettings ? options.settingManager : undefined);
            }
            mapBuilder.with(GarminMapBuilder.crosshair);
            const supportTraffic = options.trafficSystem !== undefined
                && options.trafficIconOptions !== undefined;
            let offScale;
            if (supportTraffic && options.includeTrafficOffScaleIndicator) {
                offScale = msfsSdk.Subject.create(exports.MapTrafficOffScaleStatus.None);
            }
            if (supportTraffic) {
                const iconOptions = Object.assign({}, options.trafficIconOptions);
                iconOptions.drawOffScale = false;
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                (_19 = iconOptions.supportAdsbVector) !== null && _19 !== void 0 ? _19 : (iconOptions.supportAdsbVector = options.trafficSystem.adsb !== null);
                mapBuilder.with(GarminMapBuilder.traffic, 
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                options.trafficSystem, iconOptions, false, offScale, undefined, undefined, options.trafficSettingManager, options.settingManager);
            }
            let airplaneIconSrc = options.airplaneIconSrc;
            let airplaneIconAnchor = options.airplaneIconAnchor;
            if (options.supportDataIntegrity && options.noHeadingAirplaneIconSrc !== undefined && options.noHeadingAirplaneIconAnchor !== undefined) {
                airplaneIconSrc = msfsSdk.Subject.create('');
                airplaneIconAnchor = msfsSdk.Vec2Subject.create(msfsSdk.Vec2Math.create());
            }
            const airplanePropBindings = [
                'position', 'hdgTrue', 'magVar', 'isOnGround',
                ...(options.includeTrackVector || options.includeAltitudeArc
                    ? ['groundSpeed', 'trackTrue']
                    : []),
                ...(options.includeTrackVector
                    ? ['turnRate']
                    : []),
                ...(options.includeAltitudeArc
                    ? ['altitude', 'verticalSpeed']
                    : [])
            ];
            mapBuilder
                .withOwnAirplaneIcon(options.airplaneIconSize, airplaneIconSrc, airplaneIconAnchor)
                .withOwnAirplaneIconOrientation(options.airplaneIconOrientation)
                .withOwnAirplanePropBindings(airplanePropBindings, options.dataUpdateFreq)
                .withFollowAirplane();
            const autopilotPropBindings = [
                'selectedHeading', 'manualHeadingSelect',
                ...(options.includeAltitudeArc
                    ? ['selectedAltitude']
                    : [])
            ];
            mapBuilder.with(GarminMapBuilder.autopilotProps, autopilotPropBindings, options.dataUpdateFreq);
            if (options.supportDataIntegrity) {
                mapBuilder.withLayer(GarminMapKeys.DeadReckoning, context => {
                    return (msfsSdk.FSComponent.buildComponent(MapDeadReckoningLayer, { model: context.model, mapProjection: context.projection, airplaneIconSize: options.airplaneIconSize, airplaneIconAnchor: airplaneIconAnchor }));
                });
            }
            if (options.miniCompassImgSrc !== undefined) {
                mapBuilder.with(GarminMapBuilder.miniCompass, options.miniCompassImgSrc);
            }
            if (options.includeWindVector && options.windDataProvider) {
                mapBuilder.with(GarminMapBuilder.windVector, options.windDataProvider, options.useWindVectorUserSettings ? options.settingManager : undefined);
            }
            // Top-left indicators
            if (options.includeOrientationIndicator || options.includeRangeIndicator) {
                const orientationRef = msfsSdk.FSComponent.createRef();
                const rangeRef = msfsSdk.FSComponent.createRef();
                const factories = [];
                if (options.includeOrientationIndicator) {
                    factories.push((context) => {
                        var _a, _b;
                        const orientationModule = context.model.getModule(GarminMapKeys.Orientation);
                        const pointerModule = context.model.getModule(GarminMapKeys.Pointer);
                        const focusModule = context.model.getModule(GarminMapKeys.FlightPlanFocus);
                        const isVisible = msfsSdk.MappedSubject.create(([isPointerActive, isFocusActive]) => !isPointerActive && !isFocusActive, (_a = pointerModule === null || pointerModule === void 0 ? void 0 : pointerModule.isActive) !== null && _a !== void 0 ? _a : msfsSdk.Subject.create(false), (_b = focusModule === null || focusModule === void 0 ? void 0 : focusModule.isActive) !== null && _b !== void 0 ? _b : msfsSdk.Subject.create(false));
                        return (msfsSdk.FSComponent.buildComponent(MapOrientationIndicator, { ref: orientationRef, orientation: orientationModule.orientation, text: {
                                [exports.MapOrientation.NorthUp]: 'NORTH UP',
                                [exports.MapOrientation.HeadingUp]: 'HDG UP',
                                [exports.MapOrientation.TrackUp]: 'TRK UP',
                            }, isVisible: isVisible }));
                    });
                }
                if (options.includeRangeIndicator) {
                    factories.push((context) => {
                        const rangeModule = context.model.getModule(GarminMapKeys.Range);
                        const unitsModule = context.model.getModule(GarminMapKeys.Units);
                        return (msfsSdk.FSComponent.buildComponent(MapRangeDisplay, { ref: rangeRef, range: rangeModule.nominalRange, displayUnit: unitsModule.distanceLarge }));
                    });
                }
                mapBuilder.with(GarminMapBuilder.indicatorGroup, GarminMapKeys.TopLeftIndicators, factories, {
                    onDetached: () => {
                        var _a, _b;
                        (_a = orientationRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
                        (_b = rangeRef.getOrDefault()) === null || _b === void 0 ? void 0 : _b.destroy();
                    }
                }, 'map-indicator-group-top-left');
            }
            // Bottom-left indicators
            if (options.includeDetailIndicator || (supportTraffic && (options.includeTrafficOffScaleIndicator || options.includeTrafficFailedIndicator))) {
                const detailRef = msfsSdk.FSComponent.createRef();
                const offScaleRef = msfsSdk.FSComponent.createRef();
                const failedRef = msfsSdk.FSComponent.createRef();
                const factories = [];
                if (options.includeDetailIndicator) {
                    factories.push((context) => {
                        const declutterModule = context.model.getModule(GarminMapKeys.Declutter);
                        return (msfsSdk.FSComponent.buildComponent(MapDetailIndicator, { ref: detailRef, declutterMode: declutterModule.mode, showTitle: options.showDetailIndicatorTitle }));
                    });
                }
                if (supportTraffic) {
                    if (options.includeTrafficOffScaleIndicator) {
                        factories.push(() => {
                            return (msfsSdk.FSComponent.buildComponent(MapTrafficOffScaleIndicator, { ref: offScaleRef, status: offScale }));
                        });
                    }
                    if (options.includeTrafficFailedIndicator) {
                        factories.push((context) => {
                            const trafficModule = context.model.getModule(GarminMapKeys.Traffic);
                            return (msfsSdk.FSComponent.buildComponent(MapTrafficFailedIndicator, { ref: failedRef, show: trafficModule.show, operatingMode: trafficModule.operatingMode }));
                        });
                    }
                }
                mapBuilder.with(GarminMapBuilder.indicatorGroup, GarminMapKeys.BottomLeftIndicators, factories, {
                    onDetached: () => {
                        var _a, _b, _c;
                        (_a = detailRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
                        (_b = offScaleRef.getOrDefault()) === null || _b === void 0 ? void 0 : _b.destroy();
                        (_c = failedRef.getOrDefault()) === null || _c === void 0 ? void 0 : _c.destroy();
                    }
                }, 'map-indicator-group-bottom-left');
            }
            // Bottom-right indicators
            if (options.includeTerrainScale || options.relativeTerrainStatusIndicatorIconPath || (supportTraffic && options.includeTrafficStatusIndicator)) {
                const scaleRef = msfsSdk.FSComponent.createRef();
                const trafficRef = msfsSdk.FSComponent.createRef();
                const relTerrainRef = msfsSdk.FSComponent.createRef();
                let showRelTerrain = undefined;
                let trafficRelTerrainContainerStyle = undefined;
                const factories = [];
                if (options.relativeTerrainStatusIndicatorIconPath || (supportTraffic && options.includeTrafficStatusIndicator)) {
                    factories.push((context) => {
                        var _a;
                        const trafficModule = supportTraffic && options.includeTrafficStatusIndicator
                            ? context.model.getModule(GarminMapKeys.Traffic)
                            : undefined;
                        const terrainModule = context.model.getModule(GarminMapKeys.Terrain);
                        if (options.relativeTerrainStatusIndicatorIconPath) {
                            showRelTerrain = msfsSdk.MappedSubject.create(([mode, isRelFailed]) => mode === exports.MapTerrainMode.Relative || mode === exports.MapTerrainMode.Ground || isRelFailed, terrainModule.terrainMode, terrainModule.isRelativeModeFailed);
                        }
                        trafficRelTerrainContainerStyle = msfsSdk.MappedSubject.create(([showTraffic, showTerrain]) => showTraffic || showTerrain ? '' : 'display: none;', (_a = trafficModule === null || trafficModule === void 0 ? void 0 : trafficModule.show) !== null && _a !== void 0 ? _a : msfsSdk.Subject.create(false), showRelTerrain !== null && showRelTerrain !== void 0 ? showRelTerrain : msfsSdk.Subject.create(false));
                        return (msfsSdk.FSComponent.buildComponent("div", { class: 'map-traffic-rel-terrain-indicator-container', style: trafficRelTerrainContainerStyle },
                            trafficModule !== undefined && (msfsSdk.FSComponent.buildComponent(MapTrafficStatusIndicator, { ref: trafficRef, show: trafficModule.show, operatingMode: trafficModule.operatingMode, altitudeRestrictionMode: options.showTrafficAltRestriction ? trafficModule.altitudeRestrictionMode : undefined })),
                            showRelTerrain !== undefined && (msfsSdk.FSComponent.buildComponent(MapRelativeTerrainStatusIndicator, { ref: relTerrainRef, iconFilePath: options.relativeTerrainStatusIndicatorIconPath, show: showRelTerrain, isFailed: terrainModule.isRelativeModeFailed }))));
                    });
                }
                if (options.includeTerrainScale) {
                    factories.push((context) => {
                        const terrainModule = context.model.getModule(GarminMapKeys.Terrain);
                        const unitsModule = context.model.getModule(GarminMapKeys.Units);
                        return (msfsSdk.FSComponent.buildComponent(MapTerrainScaleIndicator, { ref: scaleRef, show: terrainModule.showScale, terrainMode: terrainModule.terrainMode, altitudeUnitsMode: unitsModule.altitudeMode }));
                    });
                }
                mapBuilder.with(GarminMapBuilder.indicatorGroup, GarminMapKeys.BottomRightIndicators, factories, {
                    onDetached: () => {
                        var _a, _b, _c;
                        (_a = scaleRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
                        (_b = trafficRef.getOrDefault()) === null || _b === void 0 ? void 0 : _b.destroy();
                        (_c = relTerrainRef.getOrDefault()) === null || _c === void 0 ? void 0 : _c.destroy();
                        showRelTerrain === null || showRelTerrain === void 0 ? void 0 : showRelTerrain.destroy();
                        trafficRelTerrainContainerStyle === null || trafficRelTerrainContainerStyle === void 0 ? void 0 : trafficRelTerrainContainerStyle.destroy();
                    }
                }, 'map-indicator-group-bottom-right');
            }
            // Center indicators
            if (options.supportDataIntegrity) {
                const noGpsRef = msfsSdk.FSComponent.createRef();
                const factories = [];
                if (options.supportDataIntegrity) {
                    factories.push((context) => {
                        var _a;
                        const dataIntegrityModule = context.model.getModule(msfsSdk.MapSystemKeys.DataIntegrity);
                        return (msfsSdk.FSComponent.buildComponent(MapBannerIndicator, { ref: noGpsRef, show: dataIntegrityModule.gpsSignalValid.map(isValid => !isValid), class: 'map-banner-no-gps' }, (_a = options.noGpsBannerText) !== null && _a !== void 0 ? _a : 'NO GPS POSITION'));
                    });
                }
                mapBuilder.with(GarminMapBuilder.indicatorGroup, GarminMapKeys.CenterIndicators, factories, {
                    onDetached: () => {
                        var _a;
                        (_a = noGpsRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
                    }
                }, 'map-indicator-group-center');
            }
            if (options.pointerBoundsOffset !== undefined) {
                mapBuilder.with(GarminMapBuilder.pointer, options.pointerBoundsOffset);
                if (options.pointerInfoSize !== undefined) {
                    mapBuilder.with(GarminMapBuilder.pointerInfo, options.pointerInfoSize);
                }
            }
            if (options.supportDataIntegrity) {
                let airplaneIconSrcToUse;
                let airplaneIconAnchorToUse;
                if (options.noHeadingAirplaneIconSrc !== undefined && options.noHeadingAirplaneIconAnchor !== undefined) {
                    airplaneIconSrcToUse = airplaneIconSrc;
                    airplaneIconAnchorToUse = airplaneIconAnchor;
                }
                mapBuilder.with(GarminMapBuilder.dataIntegrity, airplaneIconSrcToUse, airplaneIconAnchorToUse, options.airplaneIconSrc, options.airplaneIconAnchor, options.noHeadingAirplaneIconSrc, options.noHeadingAirplaneIconAnchor);
            }
            return mapBuilder;
        }
    }

    /* eslint-disable jsdoc/require-jsdoc */
    class NextGenNearestMapBuilder {
        /**
         * Configures a map builder to generate a next-generation (NXi, G3000, etc) Garmin nearest waypoint map.
         *
         * The module `[GarminMapKeys.WaypointHighlight]: MapWaypointHighlightModule` is added to the map model and can be
         * used to control the highlighted waypoint.
         *
         * The controller `[GarminMapKeys.Range]: MapRangeController` is added to the map context and can be used to control
         * the range of the map.
         *
         * If the map pointer is supported, the controller `[GarminMapKeys.Pointer]: MapPointerController` is added to the
         * map context and can be used to control the pointer.
         *
         * The map builder will **not** be configured to apply a custom projected size, dead zone, or to automatically update
         * the map.
         * @param mapBuilder The map builder to configure.
         * @param options Options for configuring the map.
         * @returns The builder, after it has been configured.
         */
        static build(mapBuilder, options) {
            var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17;
            var _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30;
            options = Object.assign({}, options); // so we don't mutate the object that was passed in.
            (_a = options.defaultNoTargetRangeIndex) !== null && _a !== void 0 ? _a : (options.defaultNoTargetRangeIndex = null);
            (_b = options.targetOffsets) !== null && _b !== void 0 ? _b : (options.targetOffsets = {});
            (_c = (_18 = options.targetOffsets)[_19 = exports.MapOrientation.NorthUp]) !== null && _c !== void 0 ? _c : (_18[_19] = msfsSdk.Vec2Math.create());
            (_d = (_20 = options.targetOffsets)[_21 = exports.MapOrientation.HeadingUp]) !== null && _d !== void 0 ? _d : (_20[_21] = msfsSdk.Vec2Math.create(0, 0.17));
            (_e = (_22 = options.targetOffsets)[_23 = exports.MapOrientation.TrackUp]) !== null && _e !== void 0 ? _e : (_22[_23] = msfsSdk.Vec2Math.create(0, 0.17));
            (_f = options.rangeEndpoints) !== null && _f !== void 0 ? _f : (options.rangeEndpoints = {});
            (_g = (_24 = options.rangeEndpoints)[_25 = exports.MapOrientation.NorthUp]) !== null && _g !== void 0 ? _g : (_24[_25] = msfsSdk.VecNMath.create(4, 0.5, 0.5, 0.5, 0.25));
            (_h = (_26 = options.rangeEndpoints)[_27 = exports.MapOrientation.HeadingUp]) !== null && _h !== void 0 ? _h : (_26[_27] = msfsSdk.VecNMath.create(4, 0.5, 0.67, 0.5, 0.33));
            (_j = (_28 = options.rangeEndpoints)[_29 = exports.MapOrientation.TrackUp]) !== null && _j !== void 0 ? _j : (_28[_29] = msfsSdk.VecNMath.create(4, 0.5, 0.67, 0.5, 0.33));
            (_k = options.supportDataIntegrity) !== null && _k !== void 0 ? _k : (options.supportDataIntegrity = true);
            (_l = options.useRangeUserSettingByDefault) !== null && _l !== void 0 ? _l : (options.useRangeUserSettingByDefault = true);
            (_m = options.useOrientationUserSettings) !== null && _m !== void 0 ? _m : (options.useOrientationUserSettings = true);
            (_o = options.useDeclutterUserSetting) !== null && _o !== void 0 ? _o : (options.useDeclutterUserSetting = true);
            (_p = options.useTerrainUserSettings) !== null && _p !== void 0 ? _p : (options.useTerrainUserSettings = true);
            (_q = options.allowRelativeTerrainMode) !== null && _q !== void 0 ? _q : (options.allowRelativeTerrainMode = false);
            (_r = options.groundRelativeTerrainBlendDuration) !== null && _r !== void 0 ? _r : (options.groundRelativeTerrainBlendDuration = 10000);
            (_s = options.nexradMinRangeIndex) !== null && _s !== void 0 ? _s : (options.nexradMinRangeIndex = 13);
            (_t = options.useNexradUserSettings) !== null && _t !== void 0 ? _t : (options.useNexradUserSettings = false);
            (_u = options.airplaneIconOrientation) !== null && _u !== void 0 ? _u : (options.airplaneIconOrientation = msfsSdk.MapOwnAirplaneIconOrientation.HeadingUp);
            (_v = (_30 = options.rangeRingOptions).labelRadial) !== null && _v !== void 0 ? _v : (_30.labelRadial = 225);
            (_w = options.includeAirspaces) !== null && _w !== void 0 ? _w : (options.includeAirspaces = true);
            (_x = options.useAirspaceVisUserSettings) !== null && _x !== void 0 ? _x : (options.useAirspaceVisUserSettings = true);
            (_y = options.includeRunwayOutlines) !== null && _y !== void 0 ? _y : (options.includeRunwayOutlines = false);
            (_z = options.useWaypointVisUserSettings) !== null && _z !== void 0 ? _z : (options.useWaypointVisUserSettings = true);
            (_0 = options.includeTrackVector) !== null && _0 !== void 0 ? _0 : (options.includeTrackVector = true);
            (_1 = options.useTrackVectorUserSettings) !== null && _1 !== void 0 ? _1 : (options.useTrackVectorUserSettings = true);
            (_2 = options.includeAltitudeArc) !== null && _2 !== void 0 ? _2 : (options.includeAltitudeArc = true);
            (_3 = options.useAltitudeArcUserSettings) !== null && _3 !== void 0 ? _3 : (options.useAltitudeArcUserSettings = true);
            (_4 = options.includeWindVector) !== null && _4 !== void 0 ? _4 : (options.includeWindVector = true);
            (_5 = options.useWindVectorUserSettings) !== null && _5 !== void 0 ? _5 : (options.useWindVectorUserSettings = true);
            (_6 = options.includeOrientationIndicator) !== null && _6 !== void 0 ? _6 : (options.includeOrientationIndicator = true);
            (_7 = options.includeRangeIndicator) !== null && _7 !== void 0 ? _7 : (options.includeRangeIndicator = false);
            (_8 = options.includeDetailIndicator) !== null && _8 !== void 0 ? _8 : (options.includeDetailIndicator = true);
            (_9 = options.showDetailIndicatorTitle) !== null && _9 !== void 0 ? _9 : (options.showDetailIndicatorTitle = true);
            (_10 = options.includeTrafficOffScaleIndicator) !== null && _10 !== void 0 ? _10 : (options.includeTrafficOffScaleIndicator = true);
            (_11 = options.includeTrafficFailedIndicator) !== null && _11 !== void 0 ? _11 : (options.includeTrafficFailedIndicator = true);
            (_12 = options.includeTerrainScale) !== null && _12 !== void 0 ? _12 : (options.includeTerrainScale = true);
            (_13 = options.includeTrafficStatusIndicator) !== null && _13 !== void 0 ? _13 : (options.includeTrafficStatusIndicator = true);
            (_14 = options.showTrafficAltRestriction) !== null && _14 !== void 0 ? _14 : (options.showTrafficAltRestriction = true);
            mapBuilder
                .withModule(GarminMapKeys.Units, () => new MapUnitsModule(options.unitsSettingManager))
                .with(GarminMapBuilder.range, (_15 = options.nauticalRangeArray) !== null && _15 !== void 0 ? _15 : MapUtils.nextGenMapRanges(exports.UnitsDistanceSettingMode.Nautical), (_16 = options.metricRangeArray) !== null && _16 !== void 0 ? _16 : MapUtils.nextGenMapRanges(exports.UnitsDistanceSettingMode.Metric), options.settingManager, options.useRangeUserSettingByDefault)
                .with(GarminMapBuilder.orientation, options.targetOffsets, options.rangeEndpoints, options.useOrientationUserSettings ? options.settingManager : undefined)
                .with(GarminMapBuilder.declutter, options.useDeclutterUserSetting ? options.settingManager : undefined)
                .withBing(options.bingId, options.bingDelay)
                .with(GarminMapBuilder.terrainColors, {
                [exports.MapTerrainMode.None]: MapUtils.noTerrainEarthColors(),
                [exports.MapTerrainMode.Absolute]: MapUtils.absoluteTerrainEarthColors(),
                [exports.MapTerrainMode.Relative]: MapUtils.relativeTerrainEarthColors(),
                [exports.MapTerrainMode.Ground]: MapUtils.groundTerrainEarthColors()
            }, options.useTerrainUserSettings ? options.settingManager : undefined, options.allowRelativeTerrainMode, options.groundRelativeTerrainBlendDuration)
                .with(GarminMapBuilder.nexrad, options.nexradMinRangeIndex, options.useNexradUserSettings ? options.settingManager : undefined, exports.MapDeclutterMode.Level2, MapUtils.connextPrecipRadarColors());
            if (options.includeAirspaces) {
                mapBuilder.with(GarminMapBuilder.airspaces, options.useAirspaceVisUserSettings ? options.settingManager : undefined);
            }
            mapBuilder.with(NextGenGarminMapBuilder.waypoints, (builder) => {
                builder.withNormalStyles(options.waypointIconImageCache, NextGenMapWaypointStyles.normalIconStyles(1, options.waypointStyleScale), NextGenMapWaypointStyles.normalLabelStyles(1, options.waypointStyleFontType, options.waypointStyleScale), NextGenMapWaypointStyles.runwayOutlineIconStyles(1), options.runwayDesignationImageCache);
            }, options.includeRunwayOutlines, options.useWaypointVisUserSettings ? options.settingManager : undefined);
            if (options.flightPlanner) {
                mapBuilder.with(GarminMapBuilder.activeFlightPlan, options.flightPlanner, new DefaultFlightPathPlanRenderer(), false, (builder) => {
                    builder
                        .withFlightPlanInactiveStyles(options.waypointIconImageCache, NextGenMapWaypointStyles.flightPlanIconStyles(false, 2, options.waypointStyleScale), NextGenMapWaypointStyles.flightPlanLabelStyles(false, 2, options.waypointStyleFontType, options.waypointStyleScale))
                        .withFlightPlanActiveStyles(options.waypointIconImageCache, NextGenMapWaypointStyles.flightPlanIconStyles(true, 3, options.waypointStyleScale), NextGenMapWaypointStyles.flightPlanLabelStyles(true, 3, options.waypointStyleFontType, options.waypointStyleScale))
                        .withVNavStyles(options.waypointIconImageCache, NextGenMapWaypointStyles.vnavIconStyles(4, options.waypointStyleScale), NextGenMapWaypointStyles.vnavLabelStyles(4, options.waypointStyleFontType, options.waypointStyleScale));
                }, {
                    lnavIndex: options.lnavIndex,
                    vnavIndex: options.vnavIndex,
                    supportFocus: false
                });
            }
            if (typeof options.defaultNoTargetRangeIndex === 'number') {
                options.defaultNoTargetRangeIndex = msfsSdk.Subject.create(options.defaultNoTargetRangeIndex);
            }
            mapBuilder
                .with(GarminMapBuilder.waypointHighlight, true, (builder) => {
                builder.withHighlightStyles(options.waypointIconImageCache, NextGenMapWaypointStyles.highlightIconStyles(5, options.waypointStyleScale), NextGenMapWaypointStyles.highlightLabelStyles(5, options.waypointStyleFontType, options.waypointStyleScale));
            }, options.lineOptions)
                .withController(GarminMapKeys.Nearest, context => {
                return new NearestMapRTRController(context, options.defaultNoTargetRangeIndex);
            });
            mapBuilder
                .with(GarminMapBuilder.rangeRing, options.rangeRingOptions)
                .with(GarminMapBuilder.rangeCompass, options.rangeCompassOptions);
            if (options.includeTrackVector) {
                mapBuilder.with(GarminMapBuilder.trackVector, {
                    arcTurnRateThreshold: 0.25,
                    arcMaxLookaheadTime: msfsSdk.UnitType.SECOND.createNumber(60)
                }, options.useTrackVectorUserSettings ? options.settingManager : undefined);
            }
            if (options.includeAltitudeArc) {
                mapBuilder.with(GarminMapBuilder.altitudeArc, {
                    renderMethod: 'svg',
                    verticalSpeedPrecision: msfsSdk.UnitType.FPM.createNumber(50),
                    verticalSpeedThreshold: msfsSdk.UnitType.FPM.createNumber(150),
                    altitudeDeviationThreshold: msfsSdk.UnitType.FOOT.createNumber(150)
                }, options.useAltitudeArcUserSettings ? options.settingManager : undefined);
            }
            mapBuilder.with(GarminMapBuilder.crosshair);
            const supportTraffic = options.trafficSystem !== undefined
                && options.trafficIconOptions !== undefined;
            let offScale;
            if (supportTraffic && options.includeTrafficOffScaleIndicator) {
                offScale = msfsSdk.Subject.create(exports.MapTrafficOffScaleStatus.None);
            }
            if (supportTraffic) {
                const iconOptions = Object.assign({}, options.trafficIconOptions);
                iconOptions.drawOffScale = false;
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                (_17 = iconOptions.supportAdsbVector) !== null && _17 !== void 0 ? _17 : (iconOptions.supportAdsbVector = options.trafficSystem.adsb !== null);
                mapBuilder.with(GarminMapBuilder.traffic, 
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                options.trafficSystem, iconOptions, false, offScale, undefined, undefined, options.trafficSettingManager, options.settingManager);
            }
            let airplaneIconSrc = options.airplaneIconSrc;
            let airplaneIconAnchor = options.airplaneIconAnchor;
            if (options.supportDataIntegrity && options.noHeadingAirplaneIconSrc !== undefined && options.noHeadingAirplaneIconAnchor !== undefined) {
                airplaneIconSrc = msfsSdk.Subject.create('');
                airplaneIconAnchor = msfsSdk.Vec2Subject.create(msfsSdk.Vec2Math.create());
            }
            const airplanePropBindings = [
                'position', 'hdgTrue', 'magVar', 'isOnGround',
                ...(options.includeTrackVector || options.includeAltitudeArc
                    ? ['groundSpeed', 'trackTrue']
                    : []),
                ...(options.includeTrackVector
                    ? ['turnRate']
                    : []),
                ...(options.includeAltitudeArc
                    ? ['altitude', 'verticalSpeed']
                    : [])
            ];
            mapBuilder
                .withOwnAirplaneIcon(options.airplaneIconSize, airplaneIconSrc, airplaneIconAnchor)
                .withOwnAirplaneIconOrientation(options.airplaneIconOrientation)
                .withOwnAirplanePropBindings(airplanePropBindings, options.dataUpdateFreq)
                .withFollowAirplane();
            const autopilotPropBindings = [
                'selectedHeading', 'manualHeadingSelect',
                ...(options.includeAltitudeArc
                    ? ['selectedAltitude']
                    : [])
            ];
            mapBuilder.with(GarminMapBuilder.autopilotProps, autopilotPropBindings, options.dataUpdateFreq);
            if (options.supportDataIntegrity) {
                mapBuilder.withLayer(GarminMapKeys.DeadReckoning, context => {
                    return (msfsSdk.FSComponent.buildComponent(MapDeadReckoningLayer, { model: context.model, mapProjection: context.projection, airplaneIconSize: options.airplaneIconSize, airplaneIconAnchor: airplaneIconAnchor }));
                });
            }
            if (options.miniCompassImgSrc !== undefined) {
                mapBuilder.with(GarminMapBuilder.miniCompass, options.miniCompassImgSrc);
            }
            if (options.includeWindVector && options.windDataProvider) {
                mapBuilder.with(GarminMapBuilder.windVector, options.windDataProvider, options.useWindVectorUserSettings ? options.settingManager : undefined);
            }
            // Top-left indicators
            if (options.includeOrientationIndicator || options.includeRangeIndicator) {
                const orientationRef = msfsSdk.FSComponent.createRef();
                const rangeRef = msfsSdk.FSComponent.createRef();
                const factories = [];
                if (options.includeOrientationIndicator) {
                    factories.push((context) => {
                        var _a;
                        const orientationModule = context.model.getModule(GarminMapKeys.Orientation);
                        const pointerModule = context.model.getModule(GarminMapKeys.Pointer);
                        return (msfsSdk.FSComponent.buildComponent(MapOrientationIndicator, { ref: orientationRef, orientation: orientationModule.orientation, text: {
                                [exports.MapOrientation.NorthUp]: 'NORTH UP',
                                [exports.MapOrientation.HeadingUp]: 'HDG UP',
                                [exports.MapOrientation.TrackUp]: 'TRK UP',
                            }, isVisible: (_a = pointerModule === null || pointerModule === void 0 ? void 0 : pointerModule.isActive.map(isActive => !isActive)) !== null && _a !== void 0 ? _a : msfsSdk.Subject.create(true) }));
                    });
                }
                if (options.includeRangeIndicator) {
                    factories.push((context) => {
                        const rangeModule = context.model.getModule(GarminMapKeys.Range);
                        const unitsModule = context.model.getModule(GarminMapKeys.Units);
                        return (msfsSdk.FSComponent.buildComponent(MapRangeDisplay, { ref: rangeRef, range: rangeModule.nominalRange, displayUnit: unitsModule.distanceLarge }));
                    });
                }
                mapBuilder.with(GarminMapBuilder.indicatorGroup, GarminMapKeys.TopLeftIndicators, factories, {
                    onDetached: () => {
                        var _a, _b;
                        (_a = orientationRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
                        (_b = rangeRef.getOrDefault()) === null || _b === void 0 ? void 0 : _b.destroy();
                    }
                }, 'map-indicator-group-top-left');
            }
            // Bottom-left indicators
            if (options.includeDetailIndicator || (supportTraffic && (options.includeTrafficOffScaleIndicator || options.includeTrafficFailedIndicator))) {
                const detailRef = msfsSdk.FSComponent.createRef();
                const offScaleRef = msfsSdk.FSComponent.createRef();
                const failedRef = msfsSdk.FSComponent.createRef();
                const factories = [];
                if (options.includeDetailIndicator) {
                    factories.push((context) => {
                        const declutterModule = context.model.getModule(GarminMapKeys.Declutter);
                        return (msfsSdk.FSComponent.buildComponent(MapDetailIndicator, { ref: detailRef, declutterMode: declutterModule.mode, showTitle: options.showDetailIndicatorTitle }));
                    });
                }
                if (supportTraffic) {
                    if (options.includeTrafficOffScaleIndicator) {
                        factories.push(() => {
                            return (msfsSdk.FSComponent.buildComponent(MapTrafficOffScaleIndicator, { ref: offScaleRef, status: offScale }));
                        });
                    }
                    if (options.includeTrafficFailedIndicator) {
                        factories.push((context) => {
                            const trafficModule = context.model.getModule(GarminMapKeys.Traffic);
                            return (msfsSdk.FSComponent.buildComponent(MapTrafficFailedIndicator, { ref: failedRef, show: trafficModule.show, operatingMode: trafficModule.operatingMode }));
                        });
                    }
                }
                mapBuilder.with(GarminMapBuilder.indicatorGroup, GarminMapKeys.BottomLeftIndicators, factories, {
                    onDetached: () => {
                        var _a, _b;
                        (_a = detailRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
                        (_b = offScaleRef.getOrDefault()) === null || _b === void 0 ? void 0 : _b.destroy();
                    }
                }, 'map-indicator-group-bottom-left');
            }
            // Bottom-right indicators
            if (options.includeTerrainScale || options.relativeTerrainStatusIndicatorIconPath || (supportTraffic && options.includeTrafficStatusIndicator)) {
                const scaleRef = msfsSdk.FSComponent.createRef();
                const trafficRef = msfsSdk.FSComponent.createRef();
                const relTerrainRef = msfsSdk.FSComponent.createRef();
                let showRelTerrain = undefined;
                let trafficRelTerrainContainerStyle = undefined;
                const factories = [];
                if (supportTraffic && options.includeTrafficStatusIndicator) {
                    factories.push((context) => {
                        var _a;
                        const trafficModule = supportTraffic && options.includeTrafficStatusIndicator
                            ? context.model.getModule(GarminMapKeys.Traffic)
                            : undefined;
                        const terrainModule = context.model.getModule(GarminMapKeys.Terrain);
                        if (options.relativeTerrainStatusIndicatorIconPath) {
                            showRelTerrain = msfsSdk.MappedSubject.create(([mode, isRelFailed]) => mode === exports.MapTerrainMode.Relative || mode === exports.MapTerrainMode.Ground || isRelFailed, terrainModule.terrainMode, terrainModule.isRelativeModeFailed);
                        }
                        trafficRelTerrainContainerStyle = msfsSdk.MappedSubject.create(([showTraffic, showTerrain]) => showTraffic || showTerrain ? '' : 'display: none;', (_a = trafficModule === null || trafficModule === void 0 ? void 0 : trafficModule.show) !== null && _a !== void 0 ? _a : msfsSdk.Subject.create(false), showRelTerrain !== null && showRelTerrain !== void 0 ? showRelTerrain : msfsSdk.Subject.create(false));
                        return (msfsSdk.FSComponent.buildComponent("div", { class: 'map-traffic-rel-terrain-indicator-container', style: trafficRelTerrainContainerStyle },
                            trafficModule !== undefined && (msfsSdk.FSComponent.buildComponent(MapTrafficStatusIndicator, { ref: trafficRef, show: trafficModule.show, operatingMode: trafficModule.operatingMode, altitudeRestrictionMode: options.showTrafficAltRestriction ? trafficModule.altitudeRestrictionMode : undefined })),
                            showRelTerrain !== undefined && (msfsSdk.FSComponent.buildComponent(MapRelativeTerrainStatusIndicator, { ref: relTerrainRef, iconFilePath: options.relativeTerrainStatusIndicatorIconPath, show: showRelTerrain, isFailed: terrainModule.isRelativeModeFailed }))));
                    });
                }
                if (options.includeTerrainScale) {
                    factories.push((context) => {
                        const terrainModule = context.model.getModule(GarminMapKeys.Terrain);
                        const unitsModule = context.model.getModule(GarminMapKeys.Units);
                        return (msfsSdk.FSComponent.buildComponent(MapTerrainScaleIndicator, { ref: scaleRef, show: terrainModule.showScale, terrainMode: terrainModule.terrainMode, altitudeUnitsMode: unitsModule.altitudeMode }));
                    });
                }
                mapBuilder.with(GarminMapBuilder.indicatorGroup, GarminMapKeys.BottomRightIndicators, factories, {
                    onDetached: () => {
                        var _a, _b, _c;
                        (_a = scaleRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
                        (_b = trafficRef.getOrDefault()) === null || _b === void 0 ? void 0 : _b.destroy();
                        (_c = relTerrainRef.getOrDefault()) === null || _c === void 0 ? void 0 : _c.destroy();
                        showRelTerrain === null || showRelTerrain === void 0 ? void 0 : showRelTerrain.destroy();
                        trafficRelTerrainContainerStyle === null || trafficRelTerrainContainerStyle === void 0 ? void 0 : trafficRelTerrainContainerStyle.destroy();
                    }
                }, 'map-indicator-group-bottom-right');
            }
            // Center indicators
            if (options.supportDataIntegrity) {
                const noGpsRef = msfsSdk.FSComponent.createRef();
                const factories = [];
                if (options.supportDataIntegrity) {
                    factories.push((context) => {
                        var _a;
                        const dataIntegrityModule = context.model.getModule(msfsSdk.MapSystemKeys.DataIntegrity);
                        return (msfsSdk.FSComponent.buildComponent(MapBannerIndicator, { ref: noGpsRef, show: dataIntegrityModule.gpsSignalValid.map(isValid => !isValid), class: 'map-banner-no-gps' }, (_a = options.noGpsBannerText) !== null && _a !== void 0 ? _a : 'NO GPS POSITION'));
                    });
                }
                mapBuilder.with(GarminMapBuilder.indicatorGroup, GarminMapKeys.CenterIndicators, factories, {
                    onDetached: () => {
                        var _a;
                        (_a = noGpsRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
                    }
                }, 'map-indicator-group-center');
            }
            if (options.pointerBoundsOffset !== undefined) {
                mapBuilder.with(GarminMapBuilder.pointer, options.pointerBoundsOffset);
                if (options.pointerInfoSize !== undefined) {
                    mapBuilder.with(GarminMapBuilder.pointerInfo, options.pointerInfoSize);
                }
            }
            if (options.supportDataIntegrity) {
                let airplaneIconSrcToUse;
                let airplaneIconAnchorToUse;
                if (options.noHeadingAirplaneIconSrc !== undefined && options.noHeadingAirplaneIconAnchor !== undefined) {
                    airplaneIconSrcToUse = airplaneIconSrc;
                    airplaneIconAnchorToUse = airplaneIconAnchor;
                }
                mapBuilder.with(GarminMapBuilder.dataIntegrity, airplaneIconSrcToUse, airplaneIconAnchorToUse, options.airplaneIconSrc, options.airplaneIconAnchor, options.noHeadingAirplaneIconSrc, options.noHeadingAirplaneIconAnchor);
            }
            return mapBuilder;
        }
    }

    /* eslint-disable jsdoc/require-jsdoc */
    /**
     * Builds next-generation (NXi, G3000, etc) Garmin procedure maps.
     */
    class NextGenProcMapBuilder {
        /**
         * Configures a map builder to generate a next-generation (NXi, G3000, etc) Garmin procedure map. The map displays a
         * flight plan procedure (departure, arrival, approach) and its transitions, and is always focused on the displayed
         * procedure. The map is also locked to a North Up orientation.
         *
         * The module `[GarminMapKeys.ProcedurePreview]: MapProcedurePreviewModule` is added to the map model and can be
         * used to control the displayed procedure.
         *
         * The module `[GarminMapKeys.FlightPlanFocus]: MapFlightPlanFocusModule` is added to the map model and can be used
         * to control the procedure focus.
         *
         * The controller `[GarminMapKeys.Range]: MapRangeController` is added to the map context and can be used to control
         * the range of the map.
         *
         * If the map pointer is supported, the controller `[GarminMapKeys.Pointer]: MapPointerController` is added to the
         * map context and can be used to control the pointer.
         *
         * The map builder will **not** be configured to apply a custom projected size, dead zone, or to automatically update
         * the map.
         * @param mapBuilder The map builder to configure.
         * @param options Options for configuring the map.
         * @returns The builder, after it has been configured.
         */
        static build(mapBuilder, options) {
            var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
            var _m;
            options = Object.assign({}, options); // so we don't mutate the object that was passed in.
            (_a = options.rangeEndpoints) !== null && _a !== void 0 ? _a : (options.rangeEndpoints = msfsSdk.VecNMath.create(4, 0.5, 0.5, 0.5, 0.25));
            (_b = options.defaultFocusRangeIndex) !== null && _b !== void 0 ? _b : (options.defaultFocusRangeIndex = 17);
            (_c = options.supportDataIntegrity) !== null && _c !== void 0 ? _c : (options.supportDataIntegrity = true);
            (_d = options.useDeclutterUserSetting) !== null && _d !== void 0 ? _d : (options.useDeclutterUserSetting = true);
            (_e = options.useTerrainUserSettings) !== null && _e !== void 0 ? _e : (options.useTerrainUserSettings = false);
            (_f = options.allowRelativeTerrainMode) !== null && _f !== void 0 ? _f : (options.allowRelativeTerrainMode = false);
            (_g = options.airplaneIconOrientation) !== null && _g !== void 0 ? _g : (options.airplaneIconOrientation = msfsSdk.MapOwnAirplaneIconOrientation.HeadingUp);
            (_h = (_m = options.rangeRingOptions).labelRadial) !== null && _h !== void 0 ? _h : (_m.labelRadial = 225);
            (_j = options.includeOrientationIndicator) !== null && _j !== void 0 ? _j : (options.includeOrientationIndicator = false);
            mapBuilder
                .withModule(GarminMapKeys.Units, () => new MapUnitsModule(options.unitsSettingManager))
                .with(GarminMapBuilder.range, (_k = options.nauticalRangeArray) !== null && _k !== void 0 ? _k : MapUtils.nextGenMapRanges(exports.UnitsDistanceSettingMode.Nautical), (_l = options.metricRangeArray) !== null && _l !== void 0 ? _l : MapUtils.nextGenMapRanges(exports.UnitsDistanceSettingMode.Metric), options.settingManager, false)
                .with(GarminMapBuilder.orientation, { [exports.MapOrientation.NorthUp]: options.targetOffset }, { [exports.MapOrientation.NorthUp]: options.rangeEndpoints })
                .withController('procMapOrientation', context => {
                const orientationModule = context.model.getModule(GarminMapKeys.Orientation);
                const orientationControlConsumer = {
                    priority: Number.MAX_SAFE_INTEGER,
                    onAcquired: () => {
                        orientationModule.orientation.set(exports.MapOrientation.NorthUp);
                    },
                    onCeded: () => { }
                };
                let controller;
                return controller = new msfsSdk.MapSystemGenericController(context, {
                    onAfterMapRender: (contextArg) => {
                        contextArg[GarminMapKeys.OrientationControl].claim(orientationControlConsumer);
                    },
                    onMapDestroyed: () => {
                        controller.destroy();
                    },
                    onDestroyed: (contextArg) => {
                        contextArg[GarminMapKeys.OrientationControl].forfeit(orientationControlConsumer);
                    }
                });
            })
                .with(GarminMapBuilder.declutter, options.useDeclutterUserSetting ? options.settingManager : undefined)
                .withBing(options.bingId, options.bingDelay)
                .with(GarminMapBuilder.terrainColors, {
                [exports.MapTerrainMode.None]: MapUtils.noTerrainEarthColors(),
                [exports.MapTerrainMode.Absolute]: MapUtils.absoluteTerrainEarthColors(),
                [exports.MapTerrainMode.Relative]: MapUtils.relativeTerrainEarthColors()
            }, options.useTerrainUserSettings ? options.settingManager : undefined, options.allowRelativeTerrainMode);
            mapBuilder
                .with(GarminMapBuilder.procPreview, new ProcMapFlightPathPlanRenderer(), (builder) => {
                builder
                    .withProcTransitionPreviewStyles(options.waypointIconImageCache, NextGenMapWaypointStyles.procTransitionPreviewIconStyles(1, options.waypointStyleScale), NextGenMapWaypointStyles.procTransitionPreviewLabelStyles(1, options.waypointStyleFontType, options.waypointStyleScale))
                    .withProcPreviewStyles(options.waypointIconImageCache, NextGenMapWaypointStyles.procPreviewIconStyles(2, options.waypointStyleScale), NextGenMapWaypointStyles.procPreviewLabelStyles(2, options.waypointStyleFontType, options.waypointStyleScale));
            })
                .with(GarminMapBuilder.flightPlanFocus, options.nominalFocusMargins, options.defaultFocusRangeIndex)
                .with(GarminMapBuilder.rangeRing, options.rangeRingOptions)
                .with(GarminMapBuilder.crosshair);
            let airplaneIconSrc = options.airplaneIconSrc;
            let airplaneIconAnchor = options.airplaneIconAnchor;
            if (options.supportDataIntegrity && options.noHeadingAirplaneIconSrc !== undefined && options.noHeadingAirplaneIconAnchor !== undefined) {
                airplaneIconSrc = msfsSdk.Subject.create('');
                airplaneIconAnchor = msfsSdk.Vec2Subject.create(msfsSdk.Vec2Math.create());
            }
            mapBuilder
                .withOwnAirplaneIcon(options.airplaneIconSize, airplaneIconSrc, airplaneIconAnchor)
                .withOwnAirplaneIconOrientation(options.airplaneIconOrientation)
                .withOwnAirplanePropBindings([
                'position',
                'hdgTrue',
                'isOnGround'
            ], options.dataUpdateFreq)
                .withFollowAirplane();
            if (options.supportDataIntegrity) {
                mapBuilder.withLayer(GarminMapKeys.DeadReckoning, context => {
                    return (msfsSdk.FSComponent.buildComponent(MapDeadReckoningLayer, { model: context.model, mapProjection: context.projection, airplaneIconSize: options.airplaneIconSize, airplaneIconAnchor: airplaneIconAnchor }));
                });
            }
            if (options.miniCompassImgSrc !== undefined) {
                mapBuilder.with(GarminMapBuilder.miniCompass, options.miniCompassImgSrc);
            }
            if (options.includeOrientationIndicator) {
                const ref = msfsSdk.FSComponent.createRef();
                mapBuilder.with(GarminMapBuilder.indicatorGroup, GarminMapKeys.TopLeftIndicators, [(context) => {
                        var _a;
                        const orientationModule = context.model.getModule(GarminMapKeys.Orientation);
                        const pointerModule = context.model.getModule(GarminMapKeys.Pointer);
                        return (msfsSdk.FSComponent.buildComponent(MapOrientationIndicator, { ref: ref, orientation: orientationModule.orientation, text: {
                                [exports.MapOrientation.NorthUp]: 'NORTH UP'
                            }, isVisible: (_a = pointerModule === null || pointerModule === void 0 ? void 0 : pointerModule.isActive.map(isActive => !isActive)) !== null && _a !== void 0 ? _a : msfsSdk.Subject.create(true) }));
                    }], { onDetached: () => { ref.instance.destroy(); } }, 'map-indicator-group-top-left');
            }
            // Center indicators
            if (options.supportDataIntegrity) {
                const noGpsRef = msfsSdk.FSComponent.createRef();
                const factories = [];
                if (options.supportDataIntegrity) {
                    factories.push((context) => {
                        var _a;
                        const dataIntegrityModule = context.model.getModule(msfsSdk.MapSystemKeys.DataIntegrity);
                        return (msfsSdk.FSComponent.buildComponent(MapBannerIndicator, { ref: noGpsRef, show: dataIntegrityModule.gpsSignalValid.map(isValid => !isValid), class: 'map-banner-no-gps' }, (_a = options.noGpsBannerText) !== null && _a !== void 0 ? _a : 'NO GPS POSITION'));
                    });
                }
                mapBuilder.with(GarminMapBuilder.indicatorGroup, GarminMapKeys.CenterIndicators, factories, {
                    onDetached: () => {
                        var _a;
                        (_a = noGpsRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
                    }
                }, 'map-indicator-group-center');
            }
            if (options.pointerBoundsOffset !== undefined) {
                mapBuilder.with(GarminMapBuilder.pointer, options.pointerBoundsOffset);
                if (options.pointerInfoSize !== undefined) {
                    mapBuilder.with(GarminMapBuilder.pointerInfo, options.pointerInfoSize);
                }
            }
            if (options.supportDataIntegrity) {
                let airplaneIconSrcToUse;
                let airplaneIconAnchorToUse;
                if (options.noHeadingAirplaneIconSrc !== undefined && options.noHeadingAirplaneIconAnchor !== undefined) {
                    airplaneIconSrcToUse = airplaneIconSrc;
                    airplaneIconAnchorToUse = airplaneIconAnchor;
                }
                mapBuilder.with(GarminMapBuilder.dataIntegrity, airplaneIconSrcToUse, airplaneIconAnchorToUse, options.airplaneIconSrc, options.airplaneIconAnchor, options.noHeadingAirplaneIconSrc, options.noHeadingAirplaneIconAnchor);
            }
            return mapBuilder.withInit('procMapInit', context => {
                context.model.getModule(GarminMapKeys.FlightPlanFocus).planHasFocus.set(true);
                if (!options.useTerrainUserSettings) {
                    context.model.getModule(GarminMapKeys.Terrain).terrainMode.set(exports.MapTerrainMode.None);
                }
            });
        }
    }

    /* eslint-disable jsdoc/require-jsdoc */
    /**
     * Builds next-generation (NXi, G3000, etc) Garmin waypoint maps.
     */
    class NextGenWaypointMapBuilder {
        /**
         * Configures a map builder to generate a next-generation (NXi, G3000, etc) Garmin waypoint map. The map is locked to
         * a North Up orientation, targets a highlighted waypoint, and follows the player airplane when there is no
         * highlighted waypoint.
         *
         * The module `[GarminMapKeys.WaypointHighlight]: MapWaypointHighlightModule` is added to the map model and can be
         * used to control the highlighted waypoint.
         *
         * The controller `[GarminMapKeys.Range]: MapRangeController` is added to the map context and can be used to control
         * the range of the waypoint map.
         *
         * If the map pointer is supported, the controller `[GarminMapKeys.Pointer]: MapPointerController` is added to the
         * map context and can be used to control the pointer.
         *
         * The map builder will **not** be configured to apply a custom projected size, dead zone, or to automatically update
         * the map.
         * @param mapBuilder The map builder to configure.
         * @param options Options for configuring the map.
         * @returns The builder, after it has been configured.
         */
        static build(mapBuilder, options) {
            var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w;
            var _x;
            options = Object.assign({}, options); // so we don't mutate the object that was passed in.
            (_a = options.includeLine) !== null && _a !== void 0 ? _a : (options.includeLine = false);
            (_b = options.supportAirportAutoRange) !== null && _b !== void 0 ? _b : (options.supportAirportAutoRange = false);
            (_c = options.rangeEndpoints) !== null && _c !== void 0 ? _c : (options.rangeEndpoints = msfsSdk.VecNMath.create(4, 0.5, 0.5, 0.5, 0.25));
            (_d = options.supportDataIntegrity) !== null && _d !== void 0 ? _d : (options.supportDataIntegrity = true);
            (_e = options.useDeclutterUserSetting) !== null && _e !== void 0 ? _e : (options.useDeclutterUserSetting = true);
            (_f = options.useTerrainUserSettings) !== null && _f !== void 0 ? _f : (options.useTerrainUserSettings = true);
            (_g = options.allowRelativeTerrainMode) !== null && _g !== void 0 ? _g : (options.allowRelativeTerrainMode = false);
            (_h = options.groundRelativeTerrainBlendDuration) !== null && _h !== void 0 ? _h : (options.groundRelativeTerrainBlendDuration = 2000);
            (_j = options.nexradMinRangeIndex) !== null && _j !== void 0 ? _j : (options.nexradMinRangeIndex = 13);
            (_k = options.useNexradUserSettings) !== null && _k !== void 0 ? _k : (options.useNexradUserSettings = false);
            (_l = options.airplaneIconOrientation) !== null && _l !== void 0 ? _l : (options.airplaneIconOrientation = msfsSdk.MapOwnAirplaneIconOrientation.HeadingUp);
            (_m = options.includeAirspaces) !== null && _m !== void 0 ? _m : (options.includeAirspaces = false);
            (_o = options.useAirspaceVisUserSettings) !== null && _o !== void 0 ? _o : (options.useAirspaceVisUserSettings = true);
            (_p = options.includeRunwayOutlines) !== null && _p !== void 0 ? _p : (options.includeRunwayOutlines = false);
            (_q = options.useWaypointVisUserSettings) !== null && _q !== void 0 ? _q : (options.useWaypointVisUserSettings = true);
            (_r = (_x = options.rangeRingOptions).labelRadial) !== null && _r !== void 0 ? _r : (_x.labelRadial = 225);
            (_s = options.includeOrientationIndicator) !== null && _s !== void 0 ? _s : (options.includeOrientationIndicator = true);
            (_t = options.includeDetailIndicator) !== null && _t !== void 0 ? _t : (options.includeDetailIndicator = false);
            (_u = options.showDetailIndicatorTitle) !== null && _u !== void 0 ? _u : (options.showDetailIndicatorTitle = false);
            mapBuilder
                .withModule(GarminMapKeys.WaypointSelection, () => new WaypointMapSelectionModule())
                .withModule(GarminMapKeys.Units, () => new MapUnitsModule(options.unitsSettingManager))
                .with(GarminMapBuilder.range, (_v = options.nauticalRangeArray) !== null && _v !== void 0 ? _v : MapUtils.nextGenMapRanges(exports.UnitsDistanceSettingMode.Nautical), (_w = options.metricRangeArray) !== null && _w !== void 0 ? _w : MapUtils.nextGenMapRanges(exports.UnitsDistanceSettingMode.Metric), options.settingManager, false)
                .with(GarminMapBuilder.orientation, { [exports.MapOrientation.NorthUp]: options.targetOffset }, { [exports.MapOrientation.NorthUp]: options.rangeEndpoints })
                .withController('waypointMapOrientation', context => {
                const orientationModule = context.model.getModule(GarminMapKeys.Orientation);
                const orientationControlConsumer = {
                    priority: Number.MAX_SAFE_INTEGER,
                    onAcquired: () => {
                        orientationModule.orientation.set(exports.MapOrientation.NorthUp);
                    },
                    onCeded: () => { }
                };
                let controller;
                return controller = new msfsSdk.MapSystemGenericController(context, {
                    onAfterMapRender: (contextArg) => {
                        contextArg[GarminMapKeys.OrientationControl].claim(orientationControlConsumer);
                    },
                    onMapDestroyed: () => {
                        controller.destroy();
                    },
                    onDestroyed: (contextArg) => {
                        contextArg[GarminMapKeys.OrientationControl].forfeit(orientationControlConsumer);
                    }
                });
            })
                .with(GarminMapBuilder.declutter, options.useDeclutterUserSetting ? options.settingManager : undefined)
                .withBing(options.bingId, options.bingDelay)
                .with(GarminMapBuilder.terrainColors, {
                [exports.MapTerrainMode.None]: MapUtils.noTerrainEarthColors(),
                [exports.MapTerrainMode.Absolute]: MapUtils.absoluteTerrainEarthColors(),
                [exports.MapTerrainMode.Relative]: MapUtils.relativeTerrainEarthColors(),
                [exports.MapTerrainMode.Ground]: MapUtils.groundTerrainEarthColors()
            }, options.useTerrainUserSettings ? options.settingManager : undefined, options.allowRelativeTerrainMode, options.groundRelativeTerrainBlendDuration)
                .with(GarminMapBuilder.nexrad, options.nexradMinRangeIndex, options.useNexradUserSettings ? options.settingManager : undefined, exports.MapDeclutterMode.Level2, MapUtils.connextPrecipRadarColors());
            if (options.includeAirspaces) {
                mapBuilder.with(GarminMapBuilder.airspaces, options.useAirspaceVisUserSettings ? options.settingManager : undefined);
            }
            mapBuilder.with(NextGenGarminMapBuilder.waypoints, (builder) => {
                builder.withNormalStyles(options.waypointIconImageCache, NextGenMapWaypointStyles.normalIconStyles(1, options.waypointStyleScale), NextGenMapWaypointStyles.normalLabelStyles(1, options.waypointStyleFontType, options.waypointStyleScale), NextGenMapWaypointStyles.runwayOutlineIconStyles(1), options.runwayDesignationImageCache);
            }, options.includeRunwayOutlines, options.useWaypointVisUserSettings ? options.settingManager : undefined);
            if (options.flightPlanner) {
                mapBuilder.with(GarminMapBuilder.activeFlightPlan, options.flightPlanner, new DefaultFlightPathPlanRenderer(), false, (builder) => {
                    builder
                        .withFlightPlanInactiveStyles(options.waypointIconImageCache, NextGenMapWaypointStyles.flightPlanIconStyles(false, 2, options.waypointStyleScale), NextGenMapWaypointStyles.flightPlanLabelStyles(false, 2, options.waypointStyleFontType, options.waypointStyleScale))
                        .withFlightPlanActiveStyles(options.waypointIconImageCache, NextGenMapWaypointStyles.flightPlanIconStyles(true, 3, options.waypointStyleScale), NextGenMapWaypointStyles.flightPlanLabelStyles(true, 3, options.waypointStyleFontType, options.waypointStyleScale));
                }, {
                    lnavIndex: options.lnavIndex,
                    vnavIndex: options.vnavIndex,
                    supportFocus: false
                });
            }
            mapBuilder
                .with(GarminMapBuilder.waypointHighlight, options.includeLine, (builder) => {
                builder.withHighlightStyles(options.waypointIconImageCache, NextGenMapWaypointStyles.highlightIconStyles(4, options.waypointStyleScale), NextGenMapWaypointStyles.highlightLabelStyles(4, options.waypointStyleFontType, options.waypointStyleScale));
            }, options.lineOptions)
                .withController(GarminMapKeys.WaypointHighlight, context => {
                return new WaypointMapHighlightController(context);
            })
                .withController(GarminMapKeys.WaypointRTR, context => {
                let defaultAirportRangeIndex = undefined;
                let margins = undefined;
                if (options.supportAirportAutoRange && options.defaultAirportRangeIndex !== undefined) {
                    defaultAirportRangeIndex = msfsSdk.SubscribableUtils.toSubscribable(options.defaultAirportRangeIndex, true);
                }
                if (options.supportAirportAutoRange && options.airportAutoRangeMargins !== undefined) {
                    margins = msfsSdk.SubscribableUtils.toSubscribable(options.airportAutoRangeMargins, true);
                }
                return new WaypointMapRTRController(context, options.supportAirportAutoRange, defaultAirportRangeIndex, margins);
            });
            let airplaneIconSrc = options.airplaneIconSrc;
            let airplaneIconAnchor = options.airplaneIconAnchor;
            if (options.supportDataIntegrity && options.noHeadingAirplaneIconSrc !== undefined && options.noHeadingAirplaneIconAnchor !== undefined) {
                airplaneIconSrc = msfsSdk.Subject.create('');
                airplaneIconAnchor = msfsSdk.Vec2Subject.create(msfsSdk.Vec2Math.create());
            }
            mapBuilder
                .with(GarminMapBuilder.rangeRing, options.rangeRingOptions)
                .with(GarminMapBuilder.crosshair)
                .withOwnAirplaneIcon(options.airplaneIconSize, airplaneIconSrc, airplaneIconAnchor)
                .withOwnAirplaneIconOrientation(options.airplaneIconOrientation)
                .withOwnAirplanePropBindings([
                'position',
                'hdgTrue',
                'isOnGround'
            ], options.dataUpdateFreq)
                .withFollowAirplane();
            if (options.supportDataIntegrity) {
                mapBuilder.withLayer(GarminMapKeys.DeadReckoning, context => {
                    return (msfsSdk.FSComponent.buildComponent(MapDeadReckoningLayer, { model: context.model, mapProjection: context.projection, airplaneIconSize: options.airplaneIconSize, airplaneIconAnchor: airplaneIconAnchor }));
                });
            }
            if (options.miniCompassImgSrc !== undefined) {
                mapBuilder.with(GarminMapBuilder.miniCompass, options.miniCompassImgSrc);
            }
            // Top-left indicators
            if (options.includeOrientationIndicator) {
                const ref = msfsSdk.FSComponent.createRef();
                mapBuilder.with(GarminMapBuilder.indicatorGroup, GarminMapKeys.TopLeftIndicators, [(context) => {
                        var _a;
                        const orientationModule = context.model.getModule(GarminMapKeys.Orientation);
                        const pointerModule = context.model.getModule(GarminMapKeys.Pointer);
                        return (msfsSdk.FSComponent.buildComponent(MapOrientationIndicator, { ref: ref, orientation: orientationModule.orientation, text: {
                                [exports.MapOrientation.NorthUp]: 'NORTH UP'
                            }, isVisible: (_a = pointerModule === null || pointerModule === void 0 ? void 0 : pointerModule.isActive.map(isActive => !isActive)) !== null && _a !== void 0 ? _a : msfsSdk.Subject.create(true) }));
                    }], { onDetached: () => { ref.instance.destroy(); } }, 'map-indicator-group-top-left');
            }
            // Bottom-left indicators
            if (options.includeDetailIndicator) {
                const detailRef = msfsSdk.FSComponent.createRef();
                const factories = [];
                if (options.includeDetailIndicator) {
                    factories.push((context) => {
                        const declutterModule = context.model.getModule(GarminMapKeys.Declutter);
                        return (msfsSdk.FSComponent.buildComponent(MapDetailIndicator, { ref: detailRef, declutterMode: declutterModule.mode, showTitle: options.showDetailIndicatorTitle }));
                    });
                }
                mapBuilder.with(GarminMapBuilder.indicatorGroup, GarminMapKeys.BottomLeftIndicators, factories, {
                    onDetached: () => {
                        var _a;
                        (_a = detailRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
                    }
                }, 'map-indicator-group-bottom-left');
            }
            // Center indicators
            if (options.supportDataIntegrity) {
                const noGpsRef = msfsSdk.FSComponent.createRef();
                const factories = [];
                if (options.supportDataIntegrity) {
                    factories.push((context) => {
                        var _a;
                        const dataIntegrityModule = context.model.getModule(msfsSdk.MapSystemKeys.DataIntegrity);
                        return (msfsSdk.FSComponent.buildComponent(MapBannerIndicator, { ref: noGpsRef, show: dataIntegrityModule.gpsSignalValid.map(isValid => !isValid), class: 'map-banner-no-gps' }, (_a = options.noGpsBannerText) !== null && _a !== void 0 ? _a : 'NO GPS POSITION'));
                    });
                }
                mapBuilder.with(GarminMapBuilder.indicatorGroup, GarminMapKeys.CenterIndicators, factories, {
                    onDetached: () => {
                        var _a;
                        (_a = noGpsRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
                    }
                }, 'map-indicator-group-center');
            }
            if (options.pointerBoundsOffset !== undefined) {
                mapBuilder.with(GarminMapBuilder.pointer, options.pointerBoundsOffset);
                if (options.pointerInfoSize !== undefined) {
                    mapBuilder.with(GarminMapBuilder.pointerInfo, options.pointerInfoSize);
                }
            }
            if (options.supportDataIntegrity) {
                let airplaneIconSrcToUse;
                let airplaneIconAnchorToUse;
                if (options.noHeadingAirplaneIconSrc !== undefined && options.noHeadingAirplaneIconAnchor !== undefined) {
                    airplaneIconSrcToUse = airplaneIconSrc;
                    airplaneIconAnchorToUse = airplaneIconAnchor;
                }
                mapBuilder.with(GarminMapBuilder.dataIntegrity, airplaneIconSrcToUse, airplaneIconAnchorToUse, options.airplaneIconSrc, options.airplaneIconAnchor, options.noHeadingAirplaneIconSrc, options.noHeadingAirplaneIconAnchor);
            }
            return mapBuilder;
        }
    }

    /* eslint-disable jsdoc/require-jsdoc */
    /**
     * Builds Garmin traffic maps.
     */
    class TrafficMapBuilder {
        /**
         * Configures a map builder to generate a Garmin traffic map. The map consists of an optional active flight plan
         * layer, an optional traffic range ring layer, a traffic intruder layer, an airplane icon layer, and an optional
         * mini-compass layer. The map is centered on the player airplane and is locked to a Heading Up orientation.
         *
         * The controller `[GarminMapKeys.TrafficRange]: TrafficMapRangeController` is added to the map context and can be
         * used to control the range of the traffic map.
         *
         * The map builder will **not** be configured to apply a custom projected size, dead zone, or to automatically update
         * the map.
         * @param mapBuilder The map builder to configure.
         * @param options Options for configuring the map.
         * @returns The builder, after it has been configured.
         */
        static build(mapBuilder, options) {
            var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
            options = Object.assign({}, options); // so we don't mutate the object that was passed in.
            (_a = options.useRangeSettingByDefault) !== null && _a !== void 0 ? _a : (options.useRangeSettingByDefault = true);
            (_b = options.supportDataIntegrity) !== null && _b !== void 0 ? _b : (options.supportDataIntegrity = true);
            (_c = options.includeOrientationIndicator) !== null && _c !== void 0 ? _c : (options.includeOrientationIndicator = true);
            (_d = options.includeOperatingModeIndicator) !== null && _d !== void 0 ? _d : (options.includeOperatingModeIndicator = true);
            (_e = options.includeAdsbModeIndicator) !== null && _e !== void 0 ? _e : (options.includeAdsbModeIndicator = true);
            (_f = options.includeAltitudeModeIndicator) !== null && _f !== void 0 ? _f : (options.includeAltitudeModeIndicator = true);
            (_g = options.includeStandbyBanner) !== null && _g !== void 0 ? _g : (options.includeStandbyBanner = true);
            (_h = options.includeAdsbOffBanner) !== null && _h !== void 0 ? _h : (options.includeAdsbOffBanner = true);
            (_j = options.includeFailedBanner) !== null && _j !== void 0 ? _j : (options.includeFailedBanner = true);
            mapBuilder
                .withModule(GarminMapKeys.Units, () => new MapUnitsModule(options.unitsSettingManager))
                .with(GarminMapBuilder.orientation, { [exports.MapOrientation.HeadingUp]: options.targetOffset }, { [exports.MapOrientation.HeadingUp]: options.rangeEndpoints })
                .withController('trafficMapOrientation', context => {
                const orientation = msfsSdk.SubscribableUtils.toSubscribable(options.orientation, true);
                const orientationModule = context.model.getModule(GarminMapKeys.Orientation);
                const orientationPipe = orientation.pipe(orientationModule.orientation, true);
                const orientationControlConsumer = {
                    priority: Number.MAX_SAFE_INTEGER,
                    onAcquired: () => {
                        orientationPipe.resume(true);
                    },
                    onCeded: () => {
                        orientationPipe.pause();
                    }
                };
                let controller;
                return controller = new msfsSdk.MapSystemGenericController(context, {
                    onAfterMapRender: (contextArg) => {
                        contextArg[GarminMapKeys.OrientationControl].claim(orientationControlConsumer);
                    },
                    onMapDestroyed: () => {
                        controller.destroy();
                    },
                    onDestroyed: (contextArg) => {
                        contextArg[GarminMapKeys.OrientationControl].forfeit(orientationControlConsumer);
                        orientationPipe.destroy();
                    }
                });
            });
            if (options.flightPlanner !== undefined && options.flightPathRenderer !== undefined && options.configureFlightPlan !== undefined) {
                mapBuilder.with(GarminMapBuilder.activeFlightPlan, options.flightPlanner, options.flightPathRenderer, false, options.configureFlightPlan, {
                    lnavIndex: options.lnavIndex,
                    vnavIndex: options.vnavIndex,
                    supportFocus: false
                });
            }
            mapBuilder.with(GarminMapBuilder.trafficRange, options.nauticalRangeArray, options.metricRangeArray, options.mapRangeSettingManager, options.useRangeSettingByDefault);
            if (options.includeRangeRings) {
                const rangeRingOptions = Object.assign({}, options.rangeRingOptions);
                (_k = rangeRingOptions.outerLabelRadial) !== null && _k !== void 0 ? _k : (rangeRingOptions.outerLabelRadial = 135);
                (_l = rangeRingOptions.innerLabelRadial) !== null && _l !== void 0 ? _l : (rangeRingOptions.innerLabelRadial = 135);
                mapBuilder.with(GarminMapBuilder.trafficRangeRings, rangeRingOptions);
            }
            mapBuilder
                .with(GarminMapBuilder.traffic, options.trafficSystem, options.trafficIconOptions, true, options.offScaleStatus, options.iconFactory, options.initCanvasStyles, options.trafficSettingManager)
                .withOwnAirplaneIcon(options.airplaneIconSize, options.airplaneIconSrc, options.airplaneIconAnchor)
                .withOwnAirplanePropBindings([
                'position',
                'hdgTrue',
                'isOnGround'
            ], options.dataUpdateFreq)
                .withFollowAirplane()
                .withInit(msfsSdk.MapSystemKeys.OwnAirplaneIconOrientation, context => {
                context.model.getModule(msfsSdk.MapSystemKeys.OwnAirplaneIcon).orientation.set(msfsSdk.MapOwnAirplaneIconOrientation.MapUp);
            });
            if (options.miniCompassImgSrc !== undefined) {
                mapBuilder.with(GarminMapBuilder.miniCompass, options.miniCompassImgSrc);
            }
            // Top-left indicators
            if (options.includeOrientationIndicator) {
                const ref = msfsSdk.FSComponent.createRef();
                mapBuilder.with(GarminMapBuilder.indicatorGroup, GarminMapKeys.TopLeftIndicators, [(context) => {
                        var _a, _b;
                        const orientationModule = context.model.getModule(GarminMapKeys.Orientation);
                        const dataIntegrityModule = context.model.getModule(msfsSdk.MapSystemKeys.DataIntegrity);
                        return (msfsSdk.FSComponent.buildComponent(MapOrientationIndicator, { ref: ref, orientation: orientationModule.orientation, text: (_a = options.orientationText) !== null && _a !== void 0 ? _a : {}, isVisible: (_b = dataIntegrityModule === null || dataIntegrityModule === void 0 ? void 0 : dataIntegrityModule.headingSignalValid) !== null && _b !== void 0 ? _b : msfsSdk.Subject.create(true) }));
                    }], { onDetached: () => { var _a; (_a = ref.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy(); } }, 'map-indicator-group-top-left');
            }
            // Top-right indicators
            if (options.includeOperatingModeIndicator || (options.trafficSystem.adsb !== null && options.includeAdsbModeIndicator) || options.includeAltitudeModeIndicator) {
                const operatingModeRef = msfsSdk.FSComponent.createRef();
                const adsbModeRef = msfsSdk.FSComponent.createRef();
                const altitudeModeRef = msfsSdk.FSComponent.createRef();
                const factories = [];
                if (options.includeOperatingModeIndicator) {
                    factories.push((context) => {
                        var _a;
                        const trafficModule = context.model.getModule(GarminMapKeys.Traffic);
                        return (msfsSdk.FSComponent.buildComponent(TrafficMapOperatingModeIndicator, { ref: operatingModeRef, operatingMode: trafficModule.operatingMode, text: (_a = options.operatingModeText) !== null && _a !== void 0 ? _a : {} }));
                    });
                }
                if (options.trafficSystem.adsb !== null && options.includeAdsbModeIndicator) {
                    factories.push((context) => {
                        var _a;
                        const trafficModule = context.model.getModule(GarminMapKeys.Traffic);
                        return (msfsSdk.FSComponent.buildComponent(TrafficMapAdsbModeIndicator, { ref: adsbModeRef, operatingMode: trafficModule.adsbOperatingMode, text: (_a = options.adsbModeText) !== null && _a !== void 0 ? _a : {} }));
                    });
                }
                if (options.includeAltitudeModeIndicator) {
                    factories.push((context) => {
                        var _a;
                        const trafficModule = context.model.getModule(GarminMapKeys.Traffic);
                        return (msfsSdk.FSComponent.buildComponent(TrafficMapAltitudeModeIndicator, { ref: altitudeModeRef, altitudeRestrictionMode: trafficModule.altitudeRestrictionMode, text: (_a = options.altitudeModeText) !== null && _a !== void 0 ? _a : {} }));
                    });
                }
                mapBuilder.with(GarminMapBuilder.indicatorGroup, GarminMapKeys.TopRightIndicators, factories, {
                    onDetached: () => {
                        var _a, _b, _c;
                        (_a = operatingModeRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
                        (_b = adsbModeRef.getOrDefault()) === null || _b === void 0 ? void 0 : _b.destroy();
                        (_c = altitudeModeRef.getOrDefault()) === null || _c === void 0 ? void 0 : _c.destroy();
                    }
                }, 'map-indicator-group-top-right');
            }
            // Center indicators
            if (options.includeStandbyBanner || (options.trafficSystem.adsb !== null && options.includeAdsbOffBanner) || options.includeFailedBanner) {
                const standbyRef = msfsSdk.FSComponent.createRef();
                const adsbOffRef = msfsSdk.FSComponent.createRef();
                const failedRef = msfsSdk.FSComponent.createRef();
                const factories = [];
                if (options.includeStandbyBanner) {
                    factories.push((context) => {
                        const ownAirplanePropsModule = context.model.getModule(msfsSdk.MapSystemKeys.OwnAirplaneProps);
                        const trafficModule = context.model.getModule(GarminMapKeys.Traffic);
                        return (msfsSdk.FSComponent.buildComponent(TrafficMapStandbyBannerIndicator, { ref: standbyRef, operatingMode: trafficModule.operatingMode, isOnGround: ownAirplanePropsModule.isOnGround }));
                    });
                }
                if (options.trafficSystem.adsb !== null && options.includeAdsbOffBanner) {
                    factories.push((context) => {
                        const trafficModule = context.model.getModule(GarminMapKeys.Traffic);
                        return (msfsSdk.FSComponent.buildComponent(TrafficMapAdsbOffBannerIndicator, { ref: adsbOffRef, adsbOperatingMode: trafficModule.adsbOperatingMode, trafficOperatingMode: trafficModule.operatingMode }));
                    });
                }
                if (options.includeFailedBanner) {
                    factories.push((context) => {
                        const trafficModule = context.model.getModule(GarminMapKeys.Traffic);
                        return (msfsSdk.FSComponent.buildComponent(TrafficMapFailedBannerIndicator, { ref: failedRef, operatingMode: trafficModule.operatingMode }));
                    });
                }
                mapBuilder.with(GarminMapBuilder.indicatorGroup, GarminMapKeys.CenterIndicators, factories, {
                    onDetached: () => {
                        var _a, _b, _c;
                        (_a = standbyRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
                        (_b = adsbOffRef.getOrDefault()) === null || _b === void 0 ? void 0 : _b.destroy();
                        (_c = failedRef.getOrDefault()) === null || _c === void 0 ? void 0 : _c.destroy();
                    }
                }, 'map-indicator-group-center');
            }
            if (options.supportDataIntegrity) {
                mapBuilder
                    .withModule(msfsSdk.MapSystemKeys.DataIntegrity, () => new MapGarminDataIntegrityModule())
                    .withController('TrafficMapDataIntegrity', context => {
                    let controller;
                    let headingSignalSub;
                    // TODO: Hide flight plan
                    const miniCompassLayer = context.getLayer(GarminMapKeys.MiniCompass);
                    return controller = new msfsSdk.MapSystemGenericController(context, {
                        onAfterMapRender: (contextArg) => {
                            headingSignalSub = contextArg.model.getModule(msfsSdk.MapSystemKeys.DataIntegrity).headingSignalValid.sub(isValid => {
                                if (isValid) {
                                    miniCompassLayer === null || miniCompassLayer === void 0 ? void 0 : miniCompassLayer.setVisible(true);
                                }
                                else {
                                    miniCompassLayer === null || miniCompassLayer === void 0 ? void 0 : miniCompassLayer.setVisible(false);
                                }
                            }, true);
                        },
                        onMapDestroyed: () => {
                            controller.destroy();
                        },
                        onDestroyed: () => {
                            headingSignalSub === null || headingSignalSub === void 0 ? void 0 : headingSignalSub.destroy();
                        }
                    });
                });
            }
            return mapBuilder.withInit('trafficMapInit', context => {
                const trafficModule = context.model.getModule(GarminMapKeys.Traffic);
                trafficModule.show.set(true);
                trafficModule.showIntruderLabel.set(true);
                trafficModule.alertLevelMode.set(exports.MapTrafficAlertLevelMode.All);
            });
        }
        /**
         * Configures a map builder to generate a next-generation (NXi, G3000, etc) Garmin traffic map. The map consists of
         * an optional active flight plan layer, an optional traffic range ring layer, a traffic intruder layer, an airplane
         * icon layer, and an optional mini-compass layer. The map is centered on the player airplane and is locked in
         * Heading Up orientation.
         *
         * The controller `[GarminMapKeys.TrafficRange]: TrafficMapRangeController` is added to the map context and can be
         * used to control the range of the traffic map.
         *
         * The map builder will **not** be configured to apply a custom projected size, dead zone, or to automatically update
         * the map.
         * @param mapBuilder The map builder to configure.
         * @param options Options for configuring the map.
         * @returns The builder, after it has been configured.
         */
        static buildNextGen(mapBuilder, options) {
            var _a, _b, _c, _d, _e;
            var _f, _g;
            const optionsToUse = Object.assign({
                orientation: exports.MapOrientation.HeadingUp
            }, options);
            (_a = optionsToUse.nauticalRangeArray) !== null && _a !== void 0 ? _a : (optionsToUse.nauticalRangeArray = MapUtils.nextGenTrafficMapRanges());
            (_b = optionsToUse.metricRangeArray) !== null && _b !== void 0 ? _b : (optionsToUse.metricRangeArray = MapUtils.nextGenTrafficMapRanges());
            optionsToUse.trafficIconOptions = Object.assign({}, optionsToUse.trafficIconOptions);
            (_c = (_f = optionsToUse.trafficIconOptions).drawOffScale) !== null && _c !== void 0 ? _c : (_f.drawOffScale = true);
            (_d = (_g = optionsToUse.trafficIconOptions).supportAdsbVector) !== null && _d !== void 0 ? _d : (_g.supportAdsbVector = options.trafficSystem.adsb !== null);
            (_e = optionsToUse.includeRangeRings) !== null && _e !== void 0 ? _e : (optionsToUse.includeRangeRings = true);
            optionsToUse.orientationText = {
                [exports.MapOrientation.HeadingUp]: 'HDG UP'
            };
            let operatingModePrefix = '';
            if (options.trafficSystem.adsb !== null) {
                switch (options.trafficSystem.type) {
                    case exports.TrafficSystemType.Tis:
                        operatingModePrefix = 'TIS: ';
                        break;
                    case exports.TrafficSystemType.Tas:
                        operatingModePrefix = 'TAS: ';
                        break;
                    case exports.TrafficSystemType.TcasII:
                        operatingModePrefix = 'TCAS: ';
                        break;
                }
            }
            if (options.trafficSystem.type === exports.TrafficSystemType.TcasII) {
                optionsToUse.operatingModeText = {
                    [msfsSdk.TcasOperatingMode.Off]: operatingModePrefix + 'FAIL',
                    [msfsSdk.TcasOperatingMode.Failed]: operatingModePrefix + 'FAIL',
                    [msfsSdk.TcasOperatingMode.Standby]: operatingModePrefix + 'STANDBY',
                    [msfsSdk.TcasOperatingMode.TAOnly]: operatingModePrefix + 'TA ONLY',
                    [msfsSdk.TcasOperatingMode.TA_RA]: operatingModePrefix + 'TA/RA',
                    [msfsSdk.TcasOperatingMode.Test]: operatingModePrefix + 'TEST'
                };
            }
            else {
                optionsToUse.operatingModeText = {
                    [msfsSdk.TcasOperatingMode.Off]: operatingModePrefix + 'FAILED',
                    [msfsSdk.TcasOperatingMode.Failed]: operatingModePrefix + 'FAILED',
                    [msfsSdk.TcasOperatingMode.Standby]: operatingModePrefix + 'STANDBY',
                    [msfsSdk.TcasOperatingMode.TAOnly]: operatingModePrefix + 'OPERATING',
                    [msfsSdk.TcasOperatingMode.TA_RA]: operatingModePrefix + 'OPERATING',
                    [msfsSdk.TcasOperatingMode.Test]: operatingModePrefix + 'TEST'
                };
            }
            optionsToUse.adsbModeText = {
                [msfsSdk.AdsbOperatingMode.Standby]: 'ADS-B: OFF',
                [msfsSdk.AdsbOperatingMode.Surface]: 'ADS-B: SURF',
                [msfsSdk.AdsbOperatingMode.Airborne]: 'ADS-B: AIRB',
            };
            optionsToUse.altitudeModeText = {
                [exports.MapTrafficAltitudeRestrictionMode.Unrestricted]: 'UNRESTRICTED',
                [exports.MapTrafficAltitudeRestrictionMode.Above]: 'ABOVE',
                [exports.MapTrafficAltitudeRestrictionMode.Below]: 'BELOW',
                [exports.MapTrafficAltitudeRestrictionMode.Normal]: 'NORMAL',
            };
            if (options.flightPlanner !== undefined) {
                optionsToUse.flightPathRenderer = new DefaultFlightPathPlanRenderer();
                optionsToUse.configureFlightPlan = (builder) => {
                    builder
                        .withFlightPlanInactiveStyles(options.waypointIconImageCache, NextGenMapWaypointStyles.flightPlanIconStyles(false, 2, options.waypointStyleScale), NextGenMapWaypointStyles.flightPlanLabelStyles(false, 2, options.waypointStyleFontType, options.waypointStyleScale))
                        .withFlightPlanActiveStyles(options.waypointIconImageCache, NextGenMapWaypointStyles.flightPlanIconStyles(true, 3, options.waypointStyleScale), NextGenMapWaypointStyles.flightPlanLabelStyles(true, 3, options.waypointStyleFontType, options.waypointStyleScale))
                        .withVNavStyles(options.waypointIconImageCache, NextGenMapWaypointStyles.vnavIconStyles(4, options.waypointStyleScale), NextGenMapWaypointStyles.vnavLabelStyles(4, options.waypointStyleFontType, options.waypointStyleScale));
                };
            }
            return mapBuilder.with(TrafficMapBuilder.build, optionsToUse);
        }
    }

    /**
     * A default implementation of {@link MapRunwayDesignationImageCache}.
     */
    class DefaultMapRunwayDesignationImageCache {
        constructor() {
            this.numberCache = new Map();
            this.designatorCache = new Map();
        }
        /**
         * Registers an image with this cache for a runway number.
         * @param num The runway number for which to register the image.
         * @param src The source URI of the image to register.
         */
        registerNumber(num, src) {
            const img = new Image();
            img.src = src;
            this.numberCache.set(num, img);
        }
        /**
         * Registers an image with this cache for a runway designator.
         * @param designator The runway designator for which to register the image.
         * @param src The source URI of the image to register.
         */
        registerDesignator(designator, src) {
            const img = new Image();
            img.src = src;
            this.designatorCache.set(designator, img);
        }
        /** @inheritdoc */
        getNumber(num) {
            return this.numberCache.get(num);
        }
        /** @inheritdoc */
        getDesignator(designator) {
            return this.designatorCache.get(designator);
        }
    }

    /**
     * Manages settings controlling the display of map relative terrain and weather (datalink and/or weather radar)
     * overlays such that the two are never active at the same time.
     */
    class MapTerrainWxSettingCompatManager {
        /**
         * Constructor.
         * @param settingManager A setting manager containing the user settings controlling the display of relative terrain
         * and weather overlays for this manager to manage.
         */
        constructor(settingManager) {
            this.isInit = false;
            this.isAlive = true;
            this.terrainModeSetting = settingManager === null || settingManager === void 0 ? void 0 : settingManager.tryGetSetting('mapTerrainMode');
            this.nexradShowSetting = settingManager === null || settingManager === void 0 ? void 0 : settingManager.tryGetSetting('mapNexradShow');
        }
        /**
         * Initializes this manager. Once initialized, this manager will automatically control its managed settings such that
         * the relative terrain and weather overlays are never active at the same time.
         * @throws Error if this manager has been destroyed.
         */
        init() {
            if (!this.isAlive) {
                throw new Error('MapTerrainWxSettingCompatManager: cannot initialize a dead manager');
            }
            if (this.isInit) {
                return;
            }
            this.isInit = true;
            if (!this.terrainModeSetting || !this.nexradShowSetting) {
                return;
            }
            this.terrainModeSub = this.terrainModeSetting.sub(mode => {
                if (mode === exports.MapTerrainSettingMode.Relative) {
                    this.nexradShowSetting.value = false;
                }
            }, true);
            this.nexradShowSub = this.nexradShowSetting.sub(show => {
                if (show && this.terrainModeSetting.value === exports.MapTerrainSettingMode.Relative) {
                    this.terrainModeSetting.value = exports.MapTerrainSettingMode.None;
                }
            }, true);
        }
        /**
         * Destroys this manager.
         */
        destroy() {
            var _a, _b;
            this.isAlive = false;
            (_a = this.terrainModeSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.nexradShowSub) === null || _b === void 0 ? void 0 : _b.destroy();
        }
    }

    /**
     * The different types of navigation data fields.
     */
    exports.NavDataFieldType = void 0;
    (function (NavDataFieldType) {
        NavDataFieldType["AboveGroundLevel"] = "AGL";
        NavDataFieldType["BearingToWaypoint"] = "BRG";
        NavDataFieldType["CabinAltitude"] = "CAB";
        NavDataFieldType["ClimbGradient"] = "CLG";
        NavDataFieldType["ClimbGradientPerDistance"] = "CLM";
        NavDataFieldType["CarbonMonoxide"] = "CO";
        NavDataFieldType["DensityAltitude"] = "DA";
        NavDataFieldType["Destination"] = "DEST";
        NavDataFieldType["DistanceToWaypoint"] = "DIS";
        NavDataFieldType["DistanceToDestination"] = "DTG";
        NavDataFieldType["DesiredTrack"] = "DTK";
        NavDataFieldType["FuelEconomy"] = "ECO";
        NavDataFieldType["Endurance"] = "END";
        NavDataFieldType["TimeToDestination"] = "ENR";
        NavDataFieldType["TimeOfWaypointArrival"] = "ETA";
        NavDataFieldType["TimeToWaypoint"] = "ETE";
        NavDataFieldType["EstimatedTimeToVnav"] = "ETV";
        NavDataFieldType["FuelFlow"] = "FF";
        NavDataFieldType["FlightLevel"] = "FL";
        //FlightTimer = 'FLT', <-- Disabling this for now
        NavDataFieldType["FuelOnBoard"] = "FOB";
        NavDataFieldType["FuelOverDestination"] = "FOD";
        NavDataFieldType["GMeter"] = "G";
        NavDataFieldType["GpsAltitude"] = "GPSA";
        NavDataFieldType["GlideRatio"] = "GR";
        NavDataFieldType["GroundSpeed"] = "GS";
        NavDataFieldType["ISA"] = "ISA";
        NavDataFieldType["LocalTime"] = "LCL";
        NavDataFieldType["TimeOfDestinationArrival"] = "LDG";
        NavDataFieldType["MachNumber"] = "MACH";
        NavDataFieldType["OutsideTemperature"] = "OAT";
        NavDataFieldType["RamAirTemperature"] = "RAT";
        NavDataFieldType["TrueAirspeed"] = "TAS";
        NavDataFieldType["TrackAngleError"] = "TKE";
        NavDataFieldType["GroundTrack"] = "TRK";
        NavDataFieldType["FuelUsed"] = "USD";
        NavDataFieldType["UtcTime"] = "UTC";
        NavDataFieldType["VerticalSpeedRequired"] = "VSR";
        NavDataFieldType["Waypoint"] = "WPT";
        NavDataFieldType["CrossTrack"] = "XTK";
    })(exports.NavDataFieldType || (exports.NavDataFieldType = {}));

    /**
     * A generic implementation of a factory for navigation data bar field data models. For each data field type, a
     * single-type model factory can be registered. Once registered, the single-type model factory is used to create data
     * models for its assigned data field type.
     */
    class GenericNavDataBarFieldModelFactory {
        /**
         * Creates an instance of aGenericNavDataBarFieldModelFactory.
         * @param gpsValidity The subscribable that provides the validity of the GPS data for the models.
         */
        constructor(gpsValidity) {
            this.gpsValidity = gpsValidity;
            this.factories = new Map();
        }
        /**
         * Registers a single-type model factory with this factory.
         * @param type The data field type of the single-type model factory to register.
         * @param factory The single-type model factory to register.
         */
        register(type, factory) {
            this.factories.set(type, factory);
        }
        /**
         * Deregisters a single-type model factory from this factory.
         * @param type The data field type of the single-type model factory to deregister.
         * @returns Whether a single-type model factory was deregistered.
         */
        deregister(type) {
            return this.factories.delete(type);
        }
        /**
         * Creates a navigation data bar field data model for a given type of field.
         * @param type A data bar field type.
         * @returns A navigation data bar field data model for the given field type.
         * @throws Error if an unsupported field type is specified.
         */
        create(type) {
            var _a;
            const model = (_a = this.factories.get(type)) === null || _a === void 0 ? void 0 : _a.create(this.gpsValidity);
            if (!model) {
                throw new Error(`GenericNavDataBarFieldModelFactory: no single-type model factory of data field type [${type}] is registered`);
            }
            return model;
        }
    }

    /**
     * An enum describing the validity of the GPS data being provided to nav data field models.
     */
    exports.NavDataFieldGpsValidity = void 0;
    (function (NavDataFieldGpsValidity) {
        NavDataFieldGpsValidity[NavDataFieldGpsValidity["Invalid"] = 0] = "Invalid";
        NavDataFieldGpsValidity[NavDataFieldGpsValidity["DeadReckoning"] = 1] = "DeadReckoning";
        NavDataFieldGpsValidity[NavDataFieldGpsValidity["Valid"] = 2] = "Valid";
    })(exports.NavDataFieldGpsValidity || (exports.NavDataFieldGpsValidity = {}));

    /**
     * An abstract implementation of {@link NavDataBarFieldTypeModelFactory} which accesses data from the event bus to use
     * to create its data models.
     */
    class EventBusNavDataBarFieldTypeModelFactory {
        /**
         * Constructor.
         * @param bus The event bus.
         */
        constructor(bus) {
            this.bus = bus;
            this.sub = this.bus.getSubscriber();
        }
    }

    /**
     * A navigation data bar field data model which uses an arbitrary subscribable to provide its value and function to
     * update the value.
     */
    class NavDataBarFieldGenericModel {
        /**
         * Constructor.
         * @param sub The subscribable used to provide this model's value.
         * @param gpsValidity The current validity state of the GPS data for this model.
         * @param updateFunc The function used to update this model's value. Can take an arbitrary number of arguments, but
         * the first must be the subscribable used to provide this model's value, and the second the model's GPS validity.
         * @param destroyFunc A function which is executed when this model is destroyed.
         */
        constructor(sub, gpsValidity, updateFunc, destroyFunc) {
            this.gpsValidity = gpsValidity;
            this.updateFunc = updateFunc;
            this.destroyFunc = destroyFunc;
            this.value = sub;
        }
        /** @inheritdoc */
        update() {
            this.updateFunc(this.value, this.gpsValidity);
        }
        /** @inheritdoc */
        destroy() {
            this.destroyFunc && this.destroyFunc();
        }
    }
    /**
     * A navigation data bar field data model which uses an arbitrary subscribable to provide its value and function to
     * update the value using data cached from one or more event bus consumers.
     * @deprecated Please use {@link NavDataBarFieldConsumerValueModel} instead.
     */
    class NavDataBarFieldConsumerModel extends NavDataBarFieldGenericModel {
        /**
         * Constructor.
         * @param sub The subscribable used to provide this model's value.
         * @param gpsValidity The current validity state of the GPS data for this model.
         * @param consumers The event bus consumers used by this model.
         * @param initialValues The initial consumer values with which to initialize this model. These values will be used
         * until they are replaced by consumed values from the event bus.
         * @param updateFunc The function used to update this model's value. The first argument taken by the function is the
         * subscribable used to provide this model's value. The second argument is a tuple of Subscribables providing the
         * cached values from this model's consumers.
         */
        constructor(sub, gpsValidity, consumers, initialValues, updateFunc) {
            super(sub, gpsValidity, updateFunc, () => {
                for (let i = 0; i < this.consumerSubs.length; i++) {
                    this.consumerSubs[i].destroy();
                }
            });
            this.consumerSubs = consumers.map((consumer, index) => msfsSdk.ConsumerSubject.create(consumer, initialValues[index]));
        }
        /** @inheritdoc */
        update() {
            this.updateFunc(this.value, this.gpsValidity, this.consumerSubs);
        }
    }
    /**
     * A navigation data bar field data model which provides a {@link NumberUnitInterface} value that is derived directly
     * from an event bus consumer.
     * @deprecated Please use {@link NavDataBarFieldConsumerValueNumberUnitModel} instead.
     */
    class NavDataBarFieldConsumerNumberUnitModel extends NavDataBarFieldConsumerModel {
        /**
         * Constructor.
         * @param gpsValidity The current validity state of the GPS data for this model.
         * @param consumer The event bus consumer used to derive this model's value.
         * @param initialVal The initial consumer value with which to initialize this model. This value will be used until it
         * is replaced by a consumed value from the event bus.
         * @param consumerUnit The unit type of the values consumed from the event bus.
         */
        constructor(gpsValidity, consumer, initialVal, consumerUnit) {
            super(msfsSdk.NumberUnitSubject.create(consumerUnit.createNumber(initialVal)), gpsValidity, [consumer], [initialVal], (sub, validity, consumerSubs) => { sub.set(consumerSubs[0].get()); });
        }
    }
    /**
     * A navigation data bar field data model which uses an arbitrary subscribable to provide its value and function to
     * update the value using data cached from one or more event bus consumers.
     */
    class NavDataBarFieldConsumerValueModel extends NavDataBarFieldGenericModel {
        /**
         * Creates a new instance of NavDataBarFieldConsumerValueModel.
         * @param sub The subscribable used to provide this model's value.
         * @param gpsValidity The current validity state of the GPS data for this model.
         * @param consumers The event bus consumers used by this model.
         * @param initialValues The initial consumer values with which to initialize this model. These values will be used
         * until they are replaced by consumed values from the event bus.
         * @param updateFunc The function used to update this model's value. The first argument taken by the function is the
         * subscribable used to provide this model's value. The second argument is a tuple of {@link ReadonlyConsumerValue}
         * objects providing the cached values from this model's consumers.
         * @param onDestroy A function which will be called when the model is destroyed.
         */
        constructor(sub, gpsValidity, consumers, initialValues, updateFunc, onDestroy) {
            super(sub, gpsValidity, updateFunc, () => {
                onDestroy && onDestroy();
                for (const subs of this.consumerSubs) {
                    subs.destroy();
                }
                for (const values of this.consumerValues) {
                    values.destroy();
                }
            });
            this.consumerSubs = [];
            this.consumerValues = consumers.map((consumer, index) => {
                if (msfsSdk.SubscribableUtils.isSubscribable(consumer)) {
                    const value = msfsSdk.ConsumerValue.create(consumer.get(), initialValues[index]);
                    this.consumerSubs.push(consumer.sub(c => {
                        value.setConsumer(c);
                    }));
                    return value;
                }
                else {
                    return msfsSdk.ConsumerValue.create(consumer, initialValues[index]);
                }
            });
        }
        /** @inheritdoc */
        update() {
            this.updateFunc(this.value, this.gpsValidity, this.consumerValues);
        }
    }
    /**
     * A navigation data bar field data model which provides a {@link NumberUnitInterface} value that is derived directly
     * from an event bus consumer.
     */
    class NavDataBarFieldConsumerValueNumberUnitModel extends NavDataBarFieldConsumerValueModel {
        /**
         * Creates a new instance of NavDataBarFieldConsumerValueNumberUnitModel.
         * @param gpsValidity The current validity state of the GPS data for this model.
         * @param consumer The event bus consumer used to derive this model's value.
         * @param initialVal The initial consumer value with which to initialize this model. This value will be used until it
         * is replaced by a consumed value from the event bus.
         * @param consumerUnit The unit type of the values consumed from the event bus.
         * @param onDestroy A function which will be called when the model is destroyed.
         */
        constructor(gpsValidity, consumer, initialVal, consumerUnit, onDestroy) {
            super(msfsSdk.NumberUnitSubject.create(consumerUnit.createNumber(initialVal)), gpsValidity, [consumer], [initialVal], (sub, validity, consumerValues) => { sub.set(consumerValues[0].get()); }, onDestroy);
        }
    }

    /**
     * Checks whether an LNAV index is valid.
     * @param lnavIndex The LNAV index to check.
     * @returns Whether the specified LNAV index is valid.
     */
    function isLNavIndexValid(lnavIndex) {
        if (typeof lnavIndex === 'object') {
            lnavIndex = lnavIndex.get();
        }
        return msfsSdk.LNavUtils.isValidLNavIndex(lnavIndex);
    }
    /**
     * Resolves a base LNAV event bus topic for an LNAV index value. If the LNAV index is a static number, then the base
     * topic will be resolved to a consumer of the resolved topic corresponding to the value of the index. If the the LNAV
     * index is a subscribable, then the base topic will be resolved to a mapped subscribable of consumers of the resolved
     * topic corresopnding to the value of the index subscribable.
     * @param lnavIndex The LNAV index for which to resolve the topic.
     * @param subscriber The subscriber to use to subscribe to event bus topics.
     * @param topic The base LNAV event bus topic to resolve.
     * @returns A consumer for the resolved LNAV event bus topic if `lnavIndex` is a static number, or a mapped
     * subscribable of consumers of the resolved event bus topic if `lnavIndex` is a subscribable.
     */
    function resolveLNavConsumer(lnavIndex, subscriber, topic) {
        return msfsSdk.SubscribableUtils.isSubscribable(lnavIndex)
            ? lnavIndex.map(index => {
                return msfsSdk.LNavUtils.isValidLNavIndex(index)
                    ? subscriber.on(`${topic}${msfsSdk.LNavUtils.getEventBusTopicSuffix(index)}`)
                    : null;
            })
            : subscriber.on(`${topic}${msfsSdk.LNavUtils.getEventBusTopicSuffix(lnavIndex)}`);
    }
    /**
     * Checks whether a VNAV index is valid.
     * @param vnavIndex The VNAV index to check.
     * @returns Whether the specified VNAV index is valid.
     */
    function isVNavIndexValid(vnavIndex) {
        if (typeof vnavIndex === 'object') {
            vnavIndex = vnavIndex.get();
        }
        return msfsSdk.VNavUtils.isValidVNavIndex(vnavIndex);
    }
    /**
     * Resolves a base VNAV event bus topic for a VNAV index value. If the VNAV index is a static number, then the base
     * topic will be resolved to a consumer of the resolved topic corresponding to the value of the index. If the the VNAV
     * index is a subscribable, then the base topic will be resolved to a mapped subscribable of consumers of the resolved
     * topic corresopnding to the value of the index subscribable.
     * @param vnavIndex The VNAV index for which to resolve the topic.
     * @param subscriber The subscriber to use to subscribe to event bus topics.
     * @param topic The base VNAV event bus topic to resolve.
     * @returns A consumer for the resolved VNAV event bus topic if `vnavIndex` is a static number, or a mapped
     * subscribable of consumers of the resolved event bus topic if `vnavIndex` is a subscribable.
     */
    function resolveVNavConsumer(vnavIndex, subscriber, topic) {
        return msfsSdk.SubscribableUtils.isSubscribable(vnavIndex)
            ? vnavIndex.map(index => {
                return msfsSdk.VNavUtils.isValidVNavIndex(index)
                    ? subscriber.on(`${topic}${msfsSdk.VNavUtils.getEventBusTopicSuffix(index)}`)
                    : null;
            })
            : subscriber.on(`${topic}${msfsSdk.VNavUtils.getEventBusTopicSuffix(vnavIndex)}`);
    }
    /**
     * Creates data models for Above Ground Level navigation data bar fields.
     */
    class NavDataBarFieldAglModelFactory extends EventBusNavDataBarFieldTypeModelFactory {
        /** @inheritdoc */
        create(gpsValidity) {
            return new NavDataBarFieldConsumerValueModel(msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.FOOT.createNumber(NaN)), gpsValidity, [
                this.sub.on('above_ground_height'),
            ], [0], (sub, validity, [alt]) => {
                const gpsValid = validity.get() === exports.NavDataFieldGpsValidity.DeadReckoning || validity.get() === exports.NavDataFieldGpsValidity.Valid;
                sub.set(gpsValid ? alt.get() : NaN);
            });
        }
    }
    /**
     * Creates data models for Bearing to Waypoint navigation data bar fields.
     */
    class NavDataBarFieldBrgModelFactory extends EventBusNavDataBarFieldTypeModelFactory {
        /**
         * Creates a new instance of NavDataBarFieldBrgModelFactory.
         * @param bus The event bus.
         * @param lnavIndex The index of the LNAV from which to source data. Defaults to `0`.
         */
        constructor(bus, lnavIndex = 0) {
            super(bus);
            this.lnavIndex = lnavIndex;
        }
        /** @inheritDoc */
        create(gpsValidity) {
            const consumers = [
                resolveLNavConsumer(this.lnavIndex, this.sub, 'lnav_is_tracking'),
                resolveLNavConsumer(this.lnavIndex, this.sub, 'lnavdata_waypoint_bearing_mag'),
                this.sub.on('magvar')
            ];
            return new NavDataBarFieldConsumerValueModel(msfsSdk.BasicNavAngleSubject.create(msfsSdk.BasicNavAngleUnit.create(true).createNumber(0)), gpsValidity, consumers, [false, 0, 0], (sub, validity, [isTracking, bearing, magVar]) => {
                const gpsValid = validity.get() === exports.NavDataFieldGpsValidity.DeadReckoning || validity.get() === exports.NavDataFieldGpsValidity.Valid;
                sub.set((isLNavIndexValid(this.lnavIndex) && isTracking.get() && gpsValid) ? bearing.get() : NaN, magVar.get());
            }, () => {
                for (const consumer of consumers) {
                    if (msfsSdk.SubscribableUtils.isSubscribable(consumer)) {
                        consumer.destroy();
                    }
                }
            });
        }
    }
    /**
     * Creates data models for Cabin Altitude navigation data bar fields.
     */
    class NavDataBarFieldCabinAltitudeModelFactory extends EventBusNavDataBarFieldTypeModelFactory {
        /** @inheritdoc */
        create(gpsValidity) {
            return new NavDataBarFieldConsumerValueModel(msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.FOOT.createNumber(NaN)), gpsValidity, [
                this.sub.on('cabin_altitude'),
            ], [0], (sub, validity, [alt]) => {
                sub.set(alt.get());
            });
        }
    }
    /**
     * Creates data models for Climb Gradient navigation data bar fields.
     */
    class NavDataBarFieldClgModelFactory extends EventBusNavDataBarFieldTypeModelFactory {
        /** @inheritdoc */
        create(gpsValidity) {
            return new NavDataBarFieldConsumerValueModel(msfsSdk.Subject.create(NaN), gpsValidity, [
                this.sub.on('vertical_speed'),
                this.sub.on('ground_speed'),
            ], [0, 0], (sub, validity, [vs, gs]) => {
                const gpsValid = validity.get() === exports.NavDataFieldGpsValidity.DeadReckoning || validity.get() === exports.NavDataFieldGpsValidity.Valid;
                const gsValue = gs.get();
                if (gpsValid && gsValue >= 30) {
                    sub.set(vs.get() / msfsSdk.UnitType.KNOT.convertTo(gsValue, msfsSdk.UnitType.FPM) * 100);
                }
                else {
                    sub.set(NaN);
                }
            });
        }
    }
    /**
     * Creates data models for Climb Gradient (height per distance) navigation data bar fields.
     */
    class NavDataBarFieldClmModelFactory extends EventBusNavDataBarFieldTypeModelFactory {
        /** @inheritdoc */
        create(gpsValidity) {
            return new NavDataBarFieldConsumerValueModel(msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.FOOT_PER_NMILE.createNumber(NaN)), gpsValidity, [
                this.sub.on('vertical_speed'),
                this.sub.on('ground_speed'),
            ], [0, 0], (sub, validity, [vs, gs]) => {
                const gpsValid = validity.get() === exports.NavDataFieldGpsValidity.DeadReckoning || validity.get() === exports.NavDataFieldGpsValidity.Valid;
                const gsValue = gs.get();
                if (gpsValid && gsValue >= 30) {
                    sub.set(vs.get() / gsValue * 60);
                }
                else {
                    sub.set(NaN);
                }
            });
        }
    }
    /**
     * Creates data models for Density Altitude navigation data bar fields.
     */
    class NavDataBarFieldDensityAltitudeModelFactory extends EventBusNavDataBarFieldTypeModelFactory {
        /** @inheritdoc */
        create(gpsValidity) {
            return new NavDataBarFieldConsumerValueModel(msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.FOOT.createNumber(NaN)), gpsValidity, [
                this.sub.on('pressure_alt'),
            ], [0], (sub, validity, [alt]) => {
                sub.set(alt.get());
            });
        }
    }
    /**
     * Creates data models for Destination navigation data bar fields.
     */
    class NavDataBarFieldDestModelFactory extends EventBusNavDataBarFieldTypeModelFactory {
        /**
         * Creates a new instance of NavDataBarFieldDestModelFactory.
         * @param bus The event bus.
         * @param lnavIndex The index of the LNAV from which to source data. Defaults to `0`.
         */
        constructor(bus, lnavIndex = 0) {
            super(bus);
            this.lnavIndex = lnavIndex;
        }
        /** @inheritDoc */
        create(gpsValidity) {
            const consumers = [
                resolveLNavConsumer(this.lnavIndex, this.sub, 'lnavdata_destination_ident')
            ];
            return new NavDataBarFieldConsumerValueModel(msfsSdk.Subject.create('____'), gpsValidity, consumers, [''], (sub, validity, [ident]) => {
                sub.set(ident.get() === '' ? '____' : ident.get());
            }, () => {
                for (const consumer of consumers) {
                    if (msfsSdk.SubscribableUtils.isSubscribable(consumer)) {
                        consumer.destroy();
                    }
                }
            });
        }
    }
    /**
     * Creates data models for Distance to Waypoint navigation data bar fields.
     */
    class NavDataBarFieldDisModelFactory extends EventBusNavDataBarFieldTypeModelFactory {
        /**
         * Creates a new instance of NavDataBarFieldDisModelFactory.
         * @param bus The event bus.
         * @param lnavIndex The index of the LNAV from which to source data. Defaults to `0`.
         */
        constructor(bus, lnavIndex = 0) {
            super(bus);
            this.lnavIndex = lnavIndex;
        }
        /** @inheritDoc */
        create(gpsValidity) {
            const consumers = [
                resolveLNavConsumer(this.lnavIndex, this.sub, 'lnav_is_tracking'),
                resolveLNavConsumer(this.lnavIndex, this.sub, 'lnavdata_waypoint_distance')
            ];
            return new NavDataBarFieldConsumerValueModel(msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.NMILE.createNumber(NaN)), gpsValidity, consumers, [false, 0], (sub, validity, [isTracking, distance]) => {
                const gpsValid = validity.get() === exports.NavDataFieldGpsValidity.DeadReckoning || validity.get() === exports.NavDataFieldGpsValidity.Valid;
                sub.set((isLNavIndexValid(this.lnavIndex) && isTracking.get() && gpsValid) ? distance.get() : NaN);
            }, () => {
                for (const consumer of consumers) {
                    if (msfsSdk.SubscribableUtils.isSubscribable(consumer)) {
                        consumer.destroy();
                    }
                }
            });
        }
    }
    /**
     * Creates data models for Distance to Destination navigation data bar fields.
     */
    class NavDataBarFieldDtgModelFactory extends EventBusNavDataBarFieldTypeModelFactory {
        /**
         * Creates a new instance of NavDataBarFieldDtgModelFactory.
         * @param bus The event bus.
         * @param lnavIndex The index of the LNAV from which to source data. Defaults to `0`.
         */
        constructor(bus, lnavIndex = 0) {
            super(bus);
            this.lnavIndex = lnavIndex;
        }
        /** @inheritDoc */
        create(gpsValidity) {
            const consumers = [
                resolveLNavConsumer(this.lnavIndex, this.sub, 'lnav_is_tracking'),
                resolveLNavConsumer(this.lnavIndex, this.sub, 'lnavdata_destination_distance')
            ];
            return new NavDataBarFieldConsumerValueModel(msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.NMILE.createNumber(NaN)), gpsValidity, consumers, [false, 0], (sub, validity, [isTracking, distance]) => {
                const gpsValidityValue = validity.get();
                const isGpsValid = gpsValidityValue === exports.NavDataFieldGpsValidity.DeadReckoning || gpsValidityValue === exports.NavDataFieldGpsValidity.Valid;
                const distanceValue = distance.get();
                sub.set((isGpsValid && isLNavIndexValid(this.lnavIndex) && isTracking.get() && distanceValue >= 0) ? distanceValue : NaN);
            }, () => {
                for (const consumer of consumers) {
                    if (msfsSdk.SubscribableUtils.isSubscribable(consumer)) {
                        consumer.destroy();
                    }
                }
            });
        }
    }
    /**
     * Creates data models for Desired Track navigation data bar fields.
     */
    class NavDataBarFieldDtkModelFactory extends EventBusNavDataBarFieldTypeModelFactory {
        /**
         * Creates a new instance of NavDataBarFieldDtkModelFactory.
         * @param bus The event bus.
         * @param lnavIndex The index of the LNAV from which to source data. Defaults to `0`.
         */
        constructor(bus, lnavIndex = 0) {
            super(bus);
            this.lnavIndex = lnavIndex;
        }
        /** @inheritDoc */
        create(gpsValidity) {
            const consumers = [
                resolveLNavConsumer(this.lnavIndex, this.sub, 'lnav_is_tracking'),
                resolveLNavConsumer(this.lnavIndex, this.sub, 'lnavdata_dtk_mag'),
                this.sub.on('magvar')
            ];
            return new NavDataBarFieldConsumerValueModel(msfsSdk.BasicNavAngleSubject.create(msfsSdk.BasicNavAngleUnit.create(true).createNumber(0)), gpsValidity, consumers, [false, 0, 0], (sub, validity, [isTracking, track, magVar]) => {
                const gpsValid = validity.get() === exports.NavDataFieldGpsValidity.DeadReckoning || validity.get() === exports.NavDataFieldGpsValidity.Valid;
                sub.set((isLNavIndexValid(this.lnavIndex) && isTracking.get() && gpsValid) ? track.get() : NaN, magVar.get());
            }, () => {
                for (const consumer of consumers) {
                    if (msfsSdk.SubscribableUtils.isSubscribable(consumer)) {
                        consumer.destroy();
                    }
                }
            });
        }
    }
    /**
     * Creates data models for Fuel Economy navigation data bar fields.
     */
    class NavDataBarFieldEcoModelFactory extends EventBusNavDataBarFieldTypeModelFactory {
        /** @inheritdoc */
        create(gpsValidity) {
            return new NavDataBarFieldConsumerValueModel(msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.NMILE_PER_GALLON_FUEL.createNumber(NaN)), gpsValidity, [
                this.sub.on('fuel_flow_total'),
                this.sub.on('ground_speed'),
            ], [0, 0], (sub, validity, [ff, gs]) => {
                const gpsValid = validity.get() === exports.NavDataFieldGpsValidity.DeadReckoning || validity.get() === exports.NavDataFieldGpsValidity.Valid;
                const ffValue = ff.get();
                const gsValue = gs.get();
                sub.set(gpsValid && ffValue > 0 && gsValue >= 30 ? gsValue / ffValue : NaN);
            });
        }
    }
    /**
     * Creates data models for Endurance navigation data bar fields.
     */
    class NavDataBarFieldEndModelFactory extends EventBusNavDataBarFieldTypeModelFactory {
        /** @inheritdoc */
        create(gpsValidity) {
            return new NavDataBarFieldConsumerValueModel(msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.HOUR.createNumber(NaN)), gpsValidity, [
                this.sub.on('fuel_usable_total'),
                this.sub.on('fuel_flow_total')
            ], [0, 0], (sub, validity, [fuelRemaining, fuelFlow]) => {
                let endurance = NaN;
                const fuelFlowGph = fuelFlow.get();
                if (fuelFlowGph > 0) {
                    const fuelGal = fuelRemaining.get();
                    endurance = fuelGal / fuelFlowGph;
                }
                sub.set(endurance);
            });
        }
    }
    /**
     * Creates data models for Time To Destination navigation data bar fields.
     */
    class NavDataBarFieldEnrModelFactory extends EventBusNavDataBarFieldTypeModelFactory {
        /**
         * Creates a new instance of NavDataBarFieldEnrModelFactory.
         * @param bus The event bus.
         * @param lnavIndex The index of the LNAV from which to source data. Defaults to `0`.
         */
        constructor(bus, lnavIndex = 0) {
            super(bus);
            this.lnavIndex = lnavIndex;
        }
        /** @inheritDoc */
        create(gpsValidity) {
            const consumers = [
                resolveLNavConsumer(this.lnavIndex, this.sub, 'lnav_is_tracking'),
                resolveLNavConsumer(this.lnavIndex, this.sub, 'lnavdata_destination_distance'),
                this.sub.on('ground_speed')
            ];
            return new NavDataBarFieldConsumerValueModel(msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.HOUR.createNumber(NaN)), gpsValidity, consumers, [false, 0, 0], (sub, validity, [isTracking, distance, gs]) => {
                let time = NaN;
                const gpsValidityValue = validity.get();
                const gpsValid = gpsValidityValue === exports.NavDataFieldGpsValidity.DeadReckoning || gpsValidityValue === exports.NavDataFieldGpsValidity.Valid;
                if (gpsValid && isLNavIndexValid(this.lnavIndex) && isTracking.get()) {
                    const distanceNM = distance.get();
                    const gsKnots = gs.get();
                    if (distanceNM >= 0 && gsKnots > 30) {
                        time = distanceNM / gsKnots;
                    }
                }
                sub.set(time);
            }, () => {
                for (const consumer of consumers) {
                    if (msfsSdk.SubscribableUtils.isSubscribable(consumer)) {
                        consumer.destroy();
                    }
                }
            });
        }
    }
    /**
     * Creates data models for Estimated Time of Arrival navigation data bar fields.
     */
    class NavDataBarFieldEtaModelFactory extends EventBusNavDataBarFieldTypeModelFactory {
        /**
         * Creates a new instance of NavDataBarFieldEtaModelFactory.
         * @param bus The event bus.
         * @param lnavIndex The index of the LNAV from which to source data. Defaults to `0`.
         */
        constructor(bus, lnavIndex = 0) {
            super(bus);
            this.lnavIndex = lnavIndex;
        }
        /** @inheritDoc */
        create(gpsValidity) {
            const consumers = [
                resolveLNavConsumer(this.lnavIndex, this.sub, 'lnav_is_tracking'),
                resolveLNavConsumer(this.lnavIndex, this.sub, 'lnavdata_waypoint_distance'),
                this.sub.on('ground_speed'),
                this.sub.on('simTime')
            ];
            return new NavDataBarFieldConsumerValueModel(msfsSdk.Subject.create(NaN), gpsValidity, consumers, [false, 0, 0, NaN], (sub, validity, [isTracking, distance, gs, time]) => {
                let eta = NaN;
                const gpsValid = validity.get() === exports.NavDataFieldGpsValidity.DeadReckoning || validity.get() === exports.NavDataFieldGpsValidity.Valid;
                if (isLNavIndexValid(this.lnavIndex) && isTracking.get() && gpsValid) {
                    const gsKnots = gs.get();
                    if (gsKnots > 30) {
                        const distanceNM = distance.get();
                        eta = msfsSdk.UnitType.HOUR.convertTo(distanceNM / gsKnots, msfsSdk.UnitType.MILLISECOND) + time.get();
                    }
                }
                sub.set(eta);
            }, () => {
                for (const consumer of consumers) {
                    if (msfsSdk.SubscribableUtils.isSubscribable(consumer)) {
                        consumer.destroy();
                    }
                }
            });
        }
    }
    /**
     * Creates data models for Time To Waypoint navigation data bar fields.
     */
    class NavDataBarFieldEteModelFactory extends EventBusNavDataBarFieldTypeModelFactory {
        /**
         * Creates a new instance of NavDataBarFieldEteModelFactory.
         * @param bus The event bus.
         * @param lnavIndex The index of the LNAV from which to source data. Defaults to `0`.
         */
        constructor(bus, lnavIndex = 0) {
            super(bus);
            this.lnavIndex = lnavIndex;
        }
        /** @inheritDoc */
        create(gpsValidity) {
            const consumers = [
                resolveLNavConsumer(this.lnavIndex, this.sub, 'lnav_is_tracking'),
                resolveLNavConsumer(this.lnavIndex, this.sub, 'lnavdata_waypoint_distance'),
                this.sub.on('ground_speed')
            ];
            return new NavDataBarFieldConsumerValueModel(msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.HOUR.createNumber(NaN)), gpsValidity, consumers, [false, 0, 0], (sub, validity, [isTracking, distance, gs]) => {
                let time = NaN;
                const gpsValid = validity.get() === exports.NavDataFieldGpsValidity.DeadReckoning || validity.get() === exports.NavDataFieldGpsValidity.Valid;
                if (isLNavIndexValid(this.lnavIndex) && isTracking.get() && gpsValid) {
                    const gsKnots = gs.get();
                    if (gsKnots > 30) {
                        const distanceNM = distance.get();
                        time = distanceNM / gsKnots;
                    }
                }
                sub.set(time);
            }, () => {
                for (const consumer of consumers) {
                    if (msfsSdk.SubscribableUtils.isSubscribable(consumer)) {
                        consumer.destroy();
                    }
                }
            });
        }
    }
    /**
     * Creates data models for Fuel Flow navigation data bar fields.
     */
    class NavDataBarFieldFuelFlowModelFactory extends EventBusNavDataBarFieldTypeModelFactory {
        /** @inheritdoc */
        create(gpsValidity) {
            return new NavDataBarFieldConsumerValueModel(msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.GPH_FUEL.createNumber(NaN)), gpsValidity, [
                this.sub.on('fuel_flow_total'),
            ], [0], (sub, validity, [fuelFlow]) => {
                sub.set(fuelFlow.get());
            });
        }
    }
    /**
     * Creates data models for Flight Level navigation data bar fields.
     */
    class NavDataBarFieldFlightLevelModelFactory extends EventBusNavDataBarFieldTypeModelFactory {
        /** @inheritdoc */
        create(gpsValidity) {
            return new NavDataBarFieldConsumerValueModel(msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.FOOT.createNumber(NaN)), gpsValidity, [
                this.sub.on('gps-position'),
            ], [{ lat: 0, long: 0, alt: 0 }], (sub, validity, [latLongAlt]) => {
                const gpsValid = validity.get() === exports.NavDataFieldGpsValidity.DeadReckoning || validity.get() === exports.NavDataFieldGpsValidity.Valid;
                sub.set(gpsValid ? latLongAlt.get().alt : NaN);
            });
        }
    }
    /**
     * Creates data models for Fuel on Board navigation data bar fields.
     */
    class NavDataBarFieldFobModelFactory extends EventBusNavDataBarFieldTypeModelFactory {
        /** @inheritdoc */
        create(gpsValidity) {
            return new NavDataBarFieldConsumerValueNumberUnitModel(gpsValidity, this.sub.on('fuel_usable_total'), 0, msfsSdk.UnitType.GALLON_FUEL);
        }
    }
    /**
     * Creates data models for Fuel Over Destination navigation data bar fields.
     */
    class NavDataBarFieldFodModelFactory extends EventBusNavDataBarFieldTypeModelFactory {
        /**
         * Creates a new instance of NavDataBarFieldFodModelFactory.
         * @param bus The event bus.
         * @param lnavIndex The index of the LNAV from which to source data. Defaults to `0`.
         */
        constructor(bus, lnavIndex = 0) {
            super(bus);
            this.lnavIndex = lnavIndex;
        }
        /** @inheritDoc */
        create(gpsValidity) {
            const consumers = [
                resolveLNavConsumer(this.lnavIndex, this.sub, 'lnav_is_tracking'),
                resolveLNavConsumer(this.lnavIndex, this.sub, 'lnavdata_destination_distance'),
                this.sub.on('ground_speed'),
                this.sub.on('fuel_usable_total'),
                this.sub.on('fuel_flow_total')
            ];
            return new NavDataBarFieldConsumerValueModel(msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.GALLON_FUEL.createNumber(NaN)), gpsValidity, consumers, [false, 0, 0, 0, 0], (sub, validity, [isTracking, distance, gs, fuelRemaining, fuelFlow]) => {
                let fod = NaN;
                const gpsValidityValue = validity.get();
                const gpsValid = gpsValidityValue === exports.NavDataFieldGpsValidity.DeadReckoning || gpsValidityValue === exports.NavDataFieldGpsValidity.Valid;
                if (gpsValid && isLNavIndexValid(this.lnavIndex) && isTracking.get()) {
                    const distanceNM = distance.get();
                    const gsKnots = gs.get();
                    const fuelFlowGph = fuelFlow.get();
                    if (distanceNM >= 0 && gsKnots > 30 && fuelFlowGph > 0) {
                        const fuelGal = fuelRemaining.get();
                        fod = fuelGal - distanceNM / gsKnots * fuelFlowGph;
                    }
                }
                sub.set(fod);
            }, () => {
                for (const consumer of consumers) {
                    if (msfsSdk.SubscribableUtils.isSubscribable(consumer)) {
                        consumer.destroy();
                    }
                }
            });
        }
    }
    /**
     * Creates data models for G-Meter navigation data bar fields.
     */
    class NavDataBarFieldGMeterModelFactory extends EventBusNavDataBarFieldTypeModelFactory {
        /** @inheritdoc */
        create(gpsValidity) {
            return new NavDataBarFieldConsumerValueModel(msfsSdk.Subject.create(NaN), gpsValidity, [
                this.sub.on('g_force'),
            ], [0], (sub, validity, [g]) => {
                sub.set(g.get());
            });
        }
    }
    /**
     * Creates data models for GPS Altitude navigation data bar fields.
     */
    class NavDataBarFieldGpsAltitudeModelFactory extends EventBusNavDataBarFieldTypeModelFactory {
        /** @inheritdoc */
        create(gpsValidity) {
            return new NavDataBarFieldConsumerValueModel(msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.FOOT.createNumber(NaN)), gpsValidity, [
                this.sub.on('gps-position'),
            ], [{ lat: 0, long: 0, alt: 0 }], (sub, validity, [latLongAlt]) => {
                const gpsValid = validity.get() === exports.NavDataFieldGpsValidity.DeadReckoning || validity.get() === exports.NavDataFieldGpsValidity.Valid;
                sub.set(gpsValid ? latLongAlt.get().alt : NaN);
            });
        }
    }
    /**
     * Creates data models for Ground Speed navigation data bar fields.
     */
    class NavDataBarFieldGsModelFactory extends EventBusNavDataBarFieldTypeModelFactory {
        /** @inheritdoc */
        create(gpsValidity) {
            return new NavDataBarFieldConsumerValueModel(msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.KNOT.createNumber(NaN)), gpsValidity, [
                this.sub.on('ground_speed')
            ], [0], (sub, validity, [gs]) => {
                const gpsValid = validity.get() === exports.NavDataFieldGpsValidity.DeadReckoning || validity.get() === exports.NavDataFieldGpsValidity.Valid;
                sub.set(gpsValid ? gs.get() : NaN);
            });
        }
    }
    /**
     * Creates data models for ISA navigation data bar fields.
     */
    class NavDataBarFieldIsaModelFactory extends EventBusNavDataBarFieldTypeModelFactory {
        /** @inheritdoc */
        create(gpsValidity) {
            return new NavDataBarFieldConsumerValueModel(msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.DELTA_CELSIUS.createNumber(NaN)), gpsValidity, [
                this.sub.on('ambient_temp_c'),
                this.sub.on('isa_temp_c')
            ], [0], (sub, validity, [sat, isa]) => {
                sub.set(sat.get() - isa.get());
            });
        }
    }
    /**
     * Creates data models for Estimated Time of Arrival at Destination navigation data bar fields.
     */
    class NavDataBarFieldLdgModelFactory extends EventBusNavDataBarFieldTypeModelFactory {
        /**
         * Creates a new instance of NavDataBarFieldLdgModelFactory.
         * @param bus The event bus.
         * @param lnavIndex The index of the LNAV from which to source data. Defaults to `0`.
         */
        constructor(bus, lnavIndex = 0) {
            super(bus);
            this.lnavIndex = lnavIndex;
        }
        /** @inheritDoc */
        create(gpsValidity) {
            const consumers = [
                resolveLNavConsumer(this.lnavIndex, this.sub, 'lnav_is_tracking'),
                resolveLNavConsumer(this.lnavIndex, this.sub, 'lnavdata_destination_distance'),
                this.sub.on('ground_speed'),
                this.sub.on('simTime')
            ];
            return new NavDataBarFieldConsumerValueModel(msfsSdk.Subject.create(NaN), gpsValidity, consumers, [false, 0, 0, NaN], (sub, validity, [isTracking, distance, gs, time]) => {
                let eta = NaN;
                const gpsValidityValue = validity.get();
                const gpsValid = gpsValidityValue === exports.NavDataFieldGpsValidity.DeadReckoning || gpsValidityValue === exports.NavDataFieldGpsValidity.Valid;
                if (gpsValid && isLNavIndexValid(this.lnavIndex) && isTracking.get()) {
                    const distanceNM = distance.get();
                    const gsKnots = gs.get();
                    if (distanceNM >= 0 && gsKnots > 30) {
                        eta = msfsSdk.UnitType.HOUR.convertTo(distanceNM / gsKnots, msfsSdk.UnitType.MILLISECOND) + time.get();
                    }
                }
                sub.set(eta);
            }, () => {
                for (const consumer of consumers) {
                    if (msfsSdk.SubscribableUtils.isSubscribable(consumer)) {
                        consumer.destroy();
                    }
                }
            });
        }
    }
    /**
     * Creates data models for Mach Number navigation data bar fields.
     */
    class NavDataBarFieldMachModelFactory extends EventBusNavDataBarFieldTypeModelFactory {
        /** @inheritdoc */
        create(gpsValidity) {
            return new NavDataBarFieldConsumerValueModel(msfsSdk.Subject.create(NaN), gpsValidity, [
                this.sub.on('mach_number'),
            ], [0], (sub, validity, [mach]) => {
                sub.set(mach.get());
            });
        }
    }
    /**
     * Creates data models for Outside Air Temperature navigation data bar fields.
     */
    class NavDataBarFieldOatModelFactory extends EventBusNavDataBarFieldTypeModelFactory {
        /** @inheritdoc */
        create(gpsValidity) {
            return new NavDataBarFieldConsumerValueModel(msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.CELSIUS.createNumber(NaN)), gpsValidity, [
                this.sub.on('ambient_temp_c'),
            ], [0], (sub, validity, [temp]) => {
                sub.set(temp.get());
            });
        }
    }
    /**
     * Creates data models for Ram Air Temperature navigation data bar fields.
     */
    class NavDataBarFieldRatModelFactory extends EventBusNavDataBarFieldTypeModelFactory {
        /** @inheritdoc */
        create(gpsValidity) {
            return new NavDataBarFieldConsumerValueModel(msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.CELSIUS.createNumber(NaN)), gpsValidity, [
                this.sub.on('ram_air_temp_c'),
            ], [0], (sub, validity, [temp]) => {
                sub.set(temp.get());
            });
        }
    }
    /**
     * Creates data models for True Airspeed navigation data bar fields.
     */
    class NavDataBarFieldTasModelFactory extends EventBusNavDataBarFieldTypeModelFactory {
        /** @inheritdoc */
        create(gpsValidity) {
            return new NavDataBarFieldConsumerValueNumberUnitModel(gpsValidity, this.sub.on('tas'), 0, msfsSdk.UnitType.KNOT);
        }
    }
    /**
     * Creates data models for Track Angle Error navigation data bar fields.
     */
    class NavDataBarFieldTkeModelFactory extends EventBusNavDataBarFieldTypeModelFactory {
        /**
         * Creates a new instance of NavDataBarFieldTkeModelFactory.
         * @param bus The event bus.
         * @param lnavIndex The index of the LNAV from which to source data. Defaults to `0`.
         */
        constructor(bus, lnavIndex = 0) {
            super(bus);
            this.lnavIndex = lnavIndex;
        }
        /** @inheritDoc */
        create(gpsValidity) {
            const consumers = [
                resolveLNavConsumer(this.lnavIndex, this.sub, 'lnav_is_tracking'),
                resolveLNavConsumer(this.lnavIndex, this.sub, 'lnavdata_dtk_true'),
                this.sub.on('track_deg_true')
            ];
            return new NavDataBarFieldConsumerValueModel(msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.DEGREE.createNumber(NaN)), gpsValidity, consumers, [false, 0, 0], (sub, validity, [isTracking, dtk, track]) => {
                const gpsValid = validity.get() === exports.NavDataFieldGpsValidity.DeadReckoning || validity.get() === exports.NavDataFieldGpsValidity.Valid;
                sub.set((isLNavIndexValid(this.lnavIndex) && isTracking.get() && gpsValid) ? msfsSdk.NavMath.diffAngle(dtk.get(), track.get()) : NaN);
            }, () => {
                for (const consumer of consumers) {
                    if (msfsSdk.SubscribableUtils.isSubscribable(consumer)) {
                        consumer.destroy();
                    }
                }
            });
        }
    }
    /**
     * Creates data models for Ground Track navigation data bar fields.
     */
    class NavDataBarFieldTrkModelFactory extends EventBusNavDataBarFieldTypeModelFactory {
        /** @inheritdoc */
        create(gpsValidity) {
            return new NavDataBarFieldConsumerValueModel(msfsSdk.BasicNavAngleSubject.create(msfsSdk.BasicNavAngleUnit.create(true).createNumber(0)), gpsValidity, [
                this.sub.on('hdg_deg_true'),
                this.sub.on('ground_speed'),
                this.sub.on('track_deg_magnetic'),
                this.sub.on('magvar')
            ], [0, 0, 0, 0], (sub, validity, [hdg, gs, track, magVar]) => {
                const gpsValid = validity.get() === exports.NavDataFieldGpsValidity.DeadReckoning || validity.get() === exports.NavDataFieldGpsValidity.Valid;
                if (gs.get() < 5) {
                    sub.set(gpsValid ? hdg.get() : NaN, magVar.get());
                }
                else {
                    sub.set(gpsValid ? track.get() : NaN, magVar.get());
                }
            });
        }
    }
    /**
     * Creates data models for UTC Time navigation data bar fields.
     */
    class NavDataBarFieldUtcModelFactory extends EventBusNavDataBarFieldTypeModelFactory {
        /** @inheritdoc */
        create(gpsValidity) {
            return new NavDataBarFieldConsumerValueModel(msfsSdk.Subject.create(NaN), gpsValidity, [
                this.sub.on('simTime')
            ], [NaN], (sub, validity, [time]) => {
                sub.set(time.get());
            });
        }
    }
    /**
     * Creates data models for Vertical Speed Required navigation data bar fields.
     */
    class NavDataBarFieldVsrModelFactory extends EventBusNavDataBarFieldTypeModelFactory {
        /**
         * Creates a new instance of NavDataBarFieldVsrModelFactory.
         * @param bus The event bus.
         * @param vnavIndex The index of the VNAV from which to source data. Defaults to `0`.
         */
        constructor(bus, vnavIndex = 0) {
            super(bus);
            this.vnavIndex = vnavIndex;
        }
        /** @inheritDoc */
        create(gpsValidity) {
            const consumers = [
                resolveVNavConsumer(this.vnavIndex, this.sub, 'vnav_required_vs'),
            ];
            return new NavDataBarFieldConsumerValueModel(msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.FPM.createNumber(NaN)), gpsValidity, consumers, [0], (sub, validity, [vsr]) => {
                if (isVNavIndexValid(this.vnavIndex)) {
                    const gpsValid = validity.get() === exports.NavDataFieldGpsValidity.DeadReckoning || validity.get() === exports.NavDataFieldGpsValidity.Valid;
                    const vsrValue = vsr.get();
                    sub.set((gpsValid && vsrValue !== 0) ? vsrValue : NaN);
                }
                else {
                    sub.set(NaN);
                }
            }, () => {
                for (const consumer of consumers) {
                    if (msfsSdk.SubscribableUtils.isSubscribable(consumer)) {
                        consumer.destroy();
                    }
                }
            });
        }
    }
    /**
     * Creates data models for Active Waypoint navigation data bar fields.
     */
    class NavDataBarFieldWptModelFactory extends EventBusNavDataBarFieldTypeModelFactory {
        /**
         * Creates a new instance of NavDataBarFieldWptModelFactory.
         * @param bus The event bus.
         * @param lnavIndex The index of the LNAV from which to source data. Defaults to `0`.
         */
        constructor(bus, lnavIndex = 0) {
            super(bus);
            this.lnavIndex = lnavIndex;
        }
        /** @inheritDoc */
        create(gpsValidity) {
            const consumers = [
                resolveLNavConsumer(this.lnavIndex, this.sub, 'lnavdata_waypoint_ident')
            ];
            return new NavDataBarFieldConsumerValueModel(msfsSdk.Subject.create('_____'), gpsValidity, consumers, [''], (sub, validity, [identVal]) => {
                const ident = identVal.get();
                sub.set(!isLNavIndexValid(this.lnavIndex) || ident === '' ? '_____' : ident);
            }, () => {
                for (const consumer of consumers) {
                    if (msfsSdk.SubscribableUtils.isSubscribable(consumer)) {
                        consumer.destroy();
                    }
                }
            });
        }
    }
    /**
     * Creates data models for Cross Track navigation data bar fields.
     */
    class NavDataBarFieldXtkModelFactory extends EventBusNavDataBarFieldTypeModelFactory {
        /**
         * Creates a new instance of NavDataBarFieldXtkModelFactory.
         * @param bus The event bus.
         * @param lnavIndex The index of the LNAV from which to source data. Defaults to `0`.
         */
        constructor(bus, lnavIndex = 0) {
            super(bus);
            this.lnavIndex = lnavIndex;
        }
        /** @inheritDoc */
        create(gpsValidity) {
            const consumers = [
                resolveLNavConsumer(this.lnavIndex, this.sub, 'lnav_is_tracking'),
                resolveLNavConsumer(this.lnavIndex, this.sub, 'lnavdata_xtk')
            ];
            return new NavDataBarFieldConsumerValueModel(msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.NMILE.createNumber(NaN)), gpsValidity, consumers, [false, 0], (sub, validity, [isTracking, xtk]) => {
                const gpsValid = validity.get() === exports.NavDataFieldGpsValidity.DeadReckoning || validity.get() === exports.NavDataFieldGpsValidity.Valid;
                sub.set((isLNavIndexValid(this.lnavIndex) && isTracking.get() && gpsValid) ? xtk.get() : NaN);
            }, () => {
                for (const consumer of consumers) {
                    if (msfsSdk.SubscribableUtils.isSubscribable(consumer)) {
                        consumer.destroy();
                    }
                }
            });
        }
    }

    /**
     * A default implementation of NavDataBarFieldModelFactory.
     */
    class DefaultNavDataBarFieldModelFactory {
        // eslint-disable-next-line jsdoc/require-jsdoc
        constructor(bus, arg2, arg3, arg4) {
            var _a, _b;
            let gpsValidity;
            let options;
            if (arg2 instanceof Fms) {
                gpsValidity = arg3;
                options = arg4;
            }
            else {
                gpsValidity = arg2;
                options = arg3;
            }
            this.factory = new GenericNavDataBarFieldModelFactory(gpsValidity);
            const lnavIndex = (_a = options === null || options === void 0 ? void 0 : options.lnavIndex) !== null && _a !== void 0 ? _a : 0;
            const vnavIndex = (_b = options === null || options === void 0 ? void 0 : options.vnavIndex) !== null && _b !== void 0 ? _b : 0;
            this.factory.register(exports.NavDataFieldType.BearingToWaypoint, new NavDataBarFieldBrgModelFactory(bus, lnavIndex));
            this.factory.register(exports.NavDataFieldType.Destination, new NavDataBarFieldDestModelFactory(bus, lnavIndex));
            this.factory.register(exports.NavDataFieldType.DistanceToWaypoint, new NavDataBarFieldDisModelFactory(bus, lnavIndex));
            this.factory.register(exports.NavDataFieldType.DistanceToDestination, new NavDataBarFieldDtgModelFactory(bus, lnavIndex));
            this.factory.register(exports.NavDataFieldType.DesiredTrack, new NavDataBarFieldDtkModelFactory(bus, lnavIndex));
            this.factory.register(exports.NavDataFieldType.Endurance, new NavDataBarFieldEndModelFactory(bus));
            this.factory.register(exports.NavDataFieldType.TimeToDestination, new NavDataBarFieldEnrModelFactory(bus, lnavIndex));
            this.factory.register(exports.NavDataFieldType.TimeOfWaypointArrival, new NavDataBarFieldEtaModelFactory(bus, lnavIndex));
            this.factory.register(exports.NavDataFieldType.TimeToWaypoint, new NavDataBarFieldEteModelFactory(bus, lnavIndex));
            this.factory.register(exports.NavDataFieldType.FuelOnBoard, new NavDataBarFieldFobModelFactory(bus));
            this.factory.register(exports.NavDataFieldType.FuelOverDestination, new NavDataBarFieldFodModelFactory(bus, lnavIndex));
            this.factory.register(exports.NavDataFieldType.GroundSpeed, new NavDataBarFieldGsModelFactory(bus));
            this.factory.register(exports.NavDataFieldType.ISA, new NavDataBarFieldIsaModelFactory(bus));
            this.factory.register(exports.NavDataFieldType.TimeOfDestinationArrival, new NavDataBarFieldLdgModelFactory(bus, lnavIndex));
            this.factory.register(exports.NavDataFieldType.TrueAirspeed, new NavDataBarFieldTasModelFactory(bus));
            this.factory.register(exports.NavDataFieldType.TrackAngleError, new NavDataBarFieldTkeModelFactory(bus, lnavIndex));
            this.factory.register(exports.NavDataFieldType.GroundTrack, new NavDataBarFieldTrkModelFactory(bus));
            this.factory.register(exports.NavDataFieldType.VerticalSpeedRequired, new NavDataBarFieldVsrModelFactory(bus, vnavIndex));
            this.factory.register(exports.NavDataFieldType.Waypoint, new NavDataBarFieldWptModelFactory(bus, lnavIndex));
            this.factory.register(exports.NavDataFieldType.CrossTrack, new NavDataBarFieldXtkModelFactory(bus, lnavIndex));
        }
        /**
         * Creates a navigation data bar field data model for a given type of field.
         * @param type A data bar field type.
         * @returns A navigation data bar field data model for the given field type.
         * @throws Error if an unsupported field type is specified.
         */
        create(type) {
            return this.factory.create(type);
        }
    }

    /**
     * A navigation data bar. Displays zero or more navigation data fields.
     *
     * The root element of the status bar contains the `nav-data-bar` CSS class by default.
     */
    class NavDataBar extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.fieldCount = Math.max(0, this.props.fieldCount);
            this.fieldSlots = Array.from({ length: this.fieldCount }, () => msfsSdk.FSComponent.buildComponent("div", { class: 'nav-data-bar-field-slot' }));
            this.fields = [];
            this.models = [];
            this.settingSubs = [];
        }
        /** @inheritdoc */
        onAfterRender() {
            for (let i = 0; i < this.fieldCount; i++) {
                this.settingSubs[i] = this.props.dataBarSettingManager.whenSettingChanged(`navDataBarField${i}`).handle(this.onFieldSettingChanged.bind(this, i));
            }
            this.clockSub = this.props.bus.getSubscriber().on('realTime').whenChangedBy(1000 / this.props.updateFreq).handle(this.onUpdated.bind(this));
        }
        /**
         * Responds to changes in field settings.
         * @param index The index of the field whose setting changed.
         * @param type The new setting.
         */
        onFieldSettingChanged(index, type) {
            var _a, _b;
            const slot = this.fieldSlots[index].instance;
            slot.innerHTML = '';
            (_a = this.fields[index]) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.models[index]) === null || _b === void 0 ? void 0 : _b.destroy();
            const model = this.props.modelFactory.create(type);
            model.update();
            const field = this.props.fieldRenderer.render(type, model);
            this.models[index] = model;
            msfsSdk.FSComponent.render(field, slot);
            this.fields[index] = field.instance;
        }
        /**
         * Responds to update events.
         */
        onUpdated() {
            for (let i = 0; i < this.fieldCount; i++) {
                this.models[i].update();
            }
        }
        /** @inheritdoc */
        render() {
            let cssClass;
            if (typeof this.props.class === 'object') {
                cssClass = msfsSdk.SetSubject.create(['nav-data-bar']);
                this.cssClassSub = msfsSdk.FSComponent.bindCssClassSet(cssClass, this.props.class, NavDataBar.RESERVED_CSS_CLASSES);
            }
            else {
                cssClass = 'nav-data-bar';
                if (this.props.class !== undefined && this.props.class.length > 0) {
                    cssClass += ` ${msfsSdk.FSComponent.parseCssClassesFromString(this.props.class, classToAdd => !NavDataBar.RESERVED_CSS_CLASSES.includes(classToAdd)).join(' ')}`;
                }
            }
            return (msfsSdk.FSComponent.buildComponent("div", { class: cssClass }, this.fieldSlots));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c, _d;
            (_a = this.clockSub) === null || _a === void 0 ? void 0 : _a.destroy();
            for (let i = 0; i < this.fieldCount; i++) {
                this.settingSubs[i].destroy();
                (_b = this.fields[i]) === null || _b === void 0 ? void 0 : _b.destroy();
                (_c = this.models[i]) === null || _c === void 0 ? void 0 : _c.destroy();
            }
            (_d = this.cssClassSub) === null || _d === void 0 ? void 0 : _d.destroy();
            super.destroy();
        }
    }
    NavDataBar.RESERVED_CSS_CLASSES = ['nav-data-bar'];

    /**
     * A generic implementation of a navigation data field renderer. For each data field type, a single-type renderer
     * can be registered. Once registered, the single-type renderer is used to create render data fields for its assigned
     * data field type.
     */
    class GenericNavDataFieldRenderer {
        constructor() {
            this.renderers = new Map();
        }
        /**
         * Registers a single-type renderer.
         * @param type The data field type of the single-type renderer to register.
         * @param renderer The single-type renderer to register.
         */
        register(type, renderer) {
            this.renderers.set(type, renderer);
        }
        /**
         * Deregisters a single-type renderer.
         * @param type The data field type of the single-type renderer to deregister.
         * @returns Whether a single-type renderer was deregistered.
         */
        deregister(type) {
            return this.renderers.delete(type);
        }
        /** @inheritdoc */
        render(type, model) {
            var _a;
            const rendered = (_a = this.renderers.get(type)) === null || _a === void 0 ? void 0 : _a.render(model);
            if (!rendered) {
                throw new Error(`GenericNavDataFieldRenderer: no single-type renderer of data field type [${type}] is registered`);
            }
            return rendered;
        }
    }

    /**
     * Setting modes for date/time format.
     */
    exports.DateTimeFormatSettingMode = void 0;
    (function (DateTimeFormatSettingMode) {
        DateTimeFormatSettingMode["UTC"] = "UTC";
        DateTimeFormatSettingMode["Local24"] = "Local24";
        DateTimeFormatSettingMode["Local12"] = "Local12";
    })(exports.DateTimeFormatSettingMode || (exports.DateTimeFormatSettingMode = {}));
    /**
     * Utility class for retrieving date/time user setting managers.
     */
    class DateTimeUserSettings {
        /**
         * Retrieves a manager for date/time user settings.
         * @param bus The event bus.
         * @returns A manager for date/time user settings.
         */
        static getManager(bus) {
            var _a;
            return (_a = DateTimeUserSettings.INSTANCE) !== null && _a !== void 0 ? _a : (DateTimeUserSettings.INSTANCE = new msfsSdk.DefaultUserSettingManager(bus, Object.entries(DateTimeUserSettings.getDefaultValues()).map(([name, defaultValue]) => {
                return {
                    name,
                    defaultValue
                };
            })));
        }
        /**
         * Retrieves a manager for instrument-local date/time user settings.
         * @param bus The event bus.
         * @returns A manager for instrument-local date/time user settings.
         */
        static getLocalManager(bus) {
            if (DateTimeUserSettings.LOCAL_INSTANCE) {
                return DateTimeUserSettings.LOCAL_INSTANCE;
            }
            const defaultValues = Object.entries(DateTimeUserSettings.getDefaultValues());
            const defs = defaultValues.map(([name, defaultValue]) => {
                return {
                    name: `${name}_local`,
                    defaultValue
                };
            });
            const map = {};
            for (const [name] of defaultValues) {
                map[name] = `${name}_local`;
            }
            return DateTimeUserSettings.LOCAL_INSTANCE = new msfsSdk.DefaultUserSettingManager(bus, defs, true).mapTo(map);
        }
        /**
         * Gets the default values for a full set of standard display units user settings.
         * @returns The default values for a full set of standard display units user settings.
         */
        static getDefaultValues() {
            return {
                dateTimeFormat: exports.DateTimeFormatSettingMode.UTC,
                dateTimeLocalOffset: 0
            };
        }
    }

    /**
     * A navigation data field, consisting of a title and a value.
     *
     * The root element of the field contains the `nav-data-field` CSS class by default.
     *
     * The root element contains a child title element with the CSS class `nav-data-field-title`.
     */
    class NavDataField extends msfsSdk.DisplayComponent {
        /** @inheritDoc */
        render() {
            var _a;
            return (msfsSdk.FSComponent.buildComponent("div", { class: `nav-data-field ${(_a = this.props.class) !== null && _a !== void 0 ? _a : ''}` },
                msfsSdk.FSComponent.buildComponent("div", { class: 'nav-data-field-title' }, this.props.title),
                this.renderValue()));
        }
    }
    /**
     * A generic navigation data field which renders its children as its value.
     *
     * The root element of the field contains the `nav-data-field` CSS class by default.
     *
     * The root element contains a child title element with the CSS class `nav-data-field-title`.
     */
    class NavDataGenericField extends NavDataField {
        /**
         * Renders this data field's value component.
         * @returns This data field's value component, as a VNode.
         */
        renderValue() {
            var _a;
            return (msfsSdk.FSComponent.buildComponent(msfsSdk.FSComponent.Fragment, null, (_a = this.props.children) !== null && _a !== void 0 ? _a : null));
        }
        /** @inheritDoc */
        destroy() {
            this.props.onDestroy && this.props.onDestroy();
        }
    }
    /**
     * A navigation data field which displays a value consisting of a number with unit type.
     *
     * The root element of the field contains the `nav-data-field` CSS class by default.
     *
     * The root element contains a child title element with the CSS class `nav-data-field-title`.
     */
    class NavDataNumberUnitField extends NavDataField {
        constructor() {
            super(...arguments);
            this.numberUnitRef = msfsSdk.FSComponent.createRef();
        }
        /** @inheritDoc */
        renderValue() {
            return (msfsSdk.FSComponent.buildComponent(NumberUnitDisplay, { ref: this.numberUnitRef, value: this.props.model.value, displayUnit: this.props.displayUnit, formatter: this.props.formatter, unitFormatter: this.props.unitFormatter, hideUnitWhenNaN: this.props.hideUnitWhenNaN }));
        }
        /** @inheritDoc */
        destroy() {
            var _a;
            super.destroy();
            (_a = this.numberUnitRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
        }
    }
    /**
     * A navigation data field which displays a value consisting of a formatted duration.
     *
     * The root element of the field contains the `nav-data-field` CSS class by default.
     *
     * The root element contains a child title element with the CSS class `nav-data-field-title`.
     */
    class NavDataDurationField extends NavDataField {
        constructor() {
            super(...arguments);
            this.durationRef = msfsSdk.FSComponent.createRef();
        }
        /** @inheritDoc */
        renderValue() {
            return (msfsSdk.FSComponent.buildComponent(msfsSdk.DurationDisplay, { ref: this.durationRef, value: this.props.model.value, options: this.props.options }));
        }
        /** @inheritDoc */
        destroy() {
            var _a;
            super.destroy();
            (_a = this.durationRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
        }
    }
    /**
     * A navigation data field which displays a value consisting of a formatted time.
     *
     * The root element of the field contains the `nav-data-field` CSS class by default.
     *
     * The root element contains a child title element with the CSS class `nav-data-field-title`.
     */
    class NavDataTimeField extends NavDataField {
        constructor() {
            super(...arguments);
            this.timeRef = msfsSdk.FSComponent.createRef();
        }
        /** @inheritDoc */
        renderValue() {
            return (msfsSdk.FSComponent.buildComponent(TimeDisplay, { ref: this.timeRef, time: this.props.model.value, format: this.props.format, localOffset: this.props.localOffset, padHour: this.props.padHour, suffixFormatter: this.props.suffixFormatter, hideSuffixWhenNaN: this.props.hideSuffixWhenNaN }));
        }
        /** @inheritDoc */
        destroy() {
            var _a;
            super.destroy();
            (_a = this.timeRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
        }
    }
    /**
     * A navigation data field which displays a bearing value.
     *
     * The root element of the field contains the `nav-data-field` CSS class by default.
     *
     * The root element contains a child title element with the CSS class `nav-data-field-title`.
     */
    class NavDataBearingField extends NavDataField {
        constructor() {
            super(...arguments);
            this.bearingRef = msfsSdk.FSComponent.createRef();
        }
        /** @inheritDoc */
        renderValue() {
            return (msfsSdk.FSComponent.buildComponent(BearingDisplay, { ref: this.bearingRef, value: this.props.model.value, displayUnit: this.props.displayUnit, formatter: this.props.formatter, unitFormatter: this.props.unitFormatter, use360: this.props.use360 }));
        }
        /** @inheritDoc */
        destroy() {
            var _a;
            super.destroy();
            (_a = this.bearingRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
        }
    }

    /**
     * Renders Distance to Waypoint navigation data fields.
     */
    class NavDataFieldNumberUnitRenderer {
        /**
         * Creates a new instance of NavDataFieldNumberUnitRenderer.
         * @param options Options with which to configure the renderer.
         */
        constructor(options) {
            this.options = options;
        }
        /** @inheritDoc */
        render(model) {
            return (msfsSdk.FSComponent.buildComponent(NavDataNumberUnitField, { title: this.options.title, model: model, displayUnit: this.options.displayUnit, formatter: this.options.formatter, unitFormatter: this.options.unitFormatter, hideUnitWhenNaN: this.options.hideUnitWhenNaN, class: this.options.class }));
        }
    }
    /**
     * Renders navigation data fields which display a bearing value.
     */
    class NavDataFieldBearingRenderer {
        /**
         * Creates a new instance of NavDataFieldBearingRenderer.
         * @param options Options with which to configure the renderer.
         */
        constructor(options) {
            this.options = options;
        }
        /** @inheritDoc */
        render(model) {
            return (msfsSdk.FSComponent.buildComponent(NavDataBearingField, { title: this.options.title, model: model, displayUnit: this.options.displayUnit, formatter: this.options.formatter, unitFormatter: this.options.unitFormatter, use360: this.options.use360, class: this.options.class }));
        }
    }
    /**
     * Renders navigation data fields which display a duration value.
     */
    class NavDataFieldDurationRenderer {
        /**
         * Creates a new instance of NavDataFieldDurationRenderer.
         * @param options Options with which to configure the renderer.
         */
        constructor(options) {
            this.options = options;
        }
        /** @inheritDoc */
        render(model) {
            return (msfsSdk.FSComponent.buildComponent(NavDataDurationField, { title: this.options.title, model: model, options: this.options.options, class: this.options.class }));
        }
    }
    /**
     * Renders navigation data fields which display a time value.
     */
    class NavDataFieldTimeRenderer {
        /**
         * Creates a new instance of NavDataFieldTimeRenderer.
         * @param options Options with which to configure the renderer.
         */
        constructor(options) {
            this.options = options;
        }
        /** @inheritDoc */
        render(model) {
            return (msfsSdk.FSComponent.buildComponent(NavDataTimeField, { title: this.options.title, model: model, format: this.options.format, localOffset: this.options.localOffset, padHour: this.options.padHour, suffixFormatter: this.options.suffixFormatter, hideSuffixWhenNaN: this.options.hideSuffixWhenNaN, class: this.options.class }));
        }
    }
    /**
     * Renders navigation data fields which display a text value.
     */
    class NavDataFieldTextRenderer {
        /**
         * Creates a new instance of NavDataFieldDestRenderer.
         * @param options Options with which to configure the renderer.
         */
        constructor(options) {
            var _a;
            this.options = options;
            this.formatter = (_a = this.options.formatter) !== null && _a !== void 0 ? _a : ((value) => `${value}`);
        }
        /** @inheritDoc */
        render(model) {
            // This seems silly but we need to create our own subscribable in order to be able unsubscribe from it when the
            // component is destroyed.
            const text = model.value.map(this.formatter);
            return (msfsSdk.FSComponent.buildComponent(NavDataGenericField, { title: this.options.title, model: model, onDestroy: () => {
                    text.destroy();
                }, class: this.options.class },
                msfsSdk.FSComponent.buildComponent("div", null, text)));
        }
    }

    /**
     * An abstract next-generation (NXi, G3000, etc) implementation of {@link NavDataFieldTypeRenderer} which supports
     * display unit settings.
     */
    class NextGenDisplayUnitNavDataFieldTypeRenderer {
        /**
         * Constructor.
         * @param unitsSettingManager A user setting manager for measurement units.
         */
        constructor(unitsSettingManager) {
            this.unitsSettingManager = unitsSettingManager;
        }
    }
    NextGenDisplayUnitNavDataFieldTypeRenderer.ALTITUDE_FORMATTER = msfsSdk.NumberFormatter.create({ precision: 1, nanString: '____' });
    NextGenDisplayUnitNavDataFieldTypeRenderer.ANGLE_FORMATTER = msfsSdk.NumberFormatter.create({ precision: 1, nanString: '___' });
    NextGenDisplayUnitNavDataFieldTypeRenderer.BEARING_FORMATTER = msfsSdk.NumberFormatter.create({ precision: 1, pad: 3, nanString: '___' });
    NextGenDisplayUnitNavDataFieldTypeRenderer.DISTANCE_FORMATTER = msfsSdk.NumberFormatter.create({ precision: 0.1, maxDigits: 3, forceDecimalZeroes: true, nanString: '__._' });
    NextGenDisplayUnitNavDataFieldTypeRenderer.FUEL_FORMATTER = msfsSdk.NumberFormatter.create({ precision: 1, nanString: '___' });
    NextGenDisplayUnitNavDataFieldTypeRenderer.ISA_FORMATTER = msfsSdk.NumberFormatter.create({ precision: 1, forceSign: true, nanString: '___' });
    NextGenDisplayUnitNavDataFieldTypeRenderer.SPEED_FORMATTER = msfsSdk.NumberFormatter.create({ precision: 1, nanString: '___' });
    NextGenDisplayUnitNavDataFieldTypeRenderer.TEMPERATURE_FORMATTER = msfsSdk.NumberFormatter.create({ precision: 1, nanString: '___' });
    NextGenDisplayUnitNavDataFieldTypeRenderer.DURATION_OPTIONS = {
        pad: 0,
        format: msfsSdk.DurationDisplayFormat.hh_mm_or_mm_ss,
        delim: msfsSdk.DurationDisplayDelim.ColonOrCross,
        nanString: '__:__'
    };
    /**
     * An abstract next-generation (NXi, G3000, etc) implementation of {@link NavDataFieldTypeRenderer} which supports
     * date/time settings.
     */
    class NextGenDateTimeNavDataFieldTypeRenderer {
        /**
         * Constructor.
         * @param dateTimeSettingManager A date/time user setting manager.
         */
        constructor(dateTimeSettingManager) {
            this.dateTimeSettingManager = dateTimeSettingManager;
            this.timeFormat = this.dateTimeSettingManager.getSetting('dateTimeFormat');
            this.localOffset = this.dateTimeSettingManager.getSetting('dateTimeLocalOffset');
            this.timeFormatMapped = this.timeFormat.map(format => NextGenDateTimeNavDataFieldTypeRenderer.FORMAT_SETTING_MAP[format]);
        }
    }
    NextGenDateTimeNavDataFieldTypeRenderer.FORMAT_SETTING_MAP = {
        [exports.DateTimeFormatSettingMode.Local12]: exports.TimeDisplayFormat.Local12,
        [exports.DateTimeFormatSettingMode.Local24]: exports.TimeDisplayFormat.Local24,
        [exports.DateTimeFormatSettingMode.UTC]: exports.TimeDisplayFormat.UTC
    };
    /**
     * Renders next-generation (NXi, G3000, etc) Bearing to Waypoint navigation data fields.
     */
    class NextGenNavDataFieldBrgRenderer extends NextGenDisplayUnitNavDataFieldTypeRenderer {
        constructor() {
            super(...arguments);
            this.renderer = new NavDataFieldBearingRenderer({
                title: 'BRG',
                displayUnit: this.unitsSettingManager.navAngleUnits,
                formatter: NextGenDisplayUnitNavDataFieldTypeRenderer.BEARING_FORMATTER,
                class: 'nav-data-field-gps'
            });
        }
        /** @inheritdoc */
        render(model) {
            return this.renderer.render(model);
        }
    }
    /**
     * Renders next-generation (NXi, G3000, etc) Destination navigation data fields.
     */
    class NextGenNavDataFieldDestRenderer extends NextGenDisplayUnitNavDataFieldTypeRenderer {
        constructor() {
            super(...arguments);
            this.renderer = new NavDataFieldTextRenderer({
                title: 'DEST',
                class: 'nav-data-field-white'
            });
        }
        /** @inheritdoc */
        render(model) {
            return this.renderer.render(model);
        }
    }
    /**
     * Renders next-generation (NXi, G3000, etc) Distance to Waypoint navigation data fields.
     */
    class NextGenNavDataFieldDisRenderer extends NextGenDisplayUnitNavDataFieldTypeRenderer {
        constructor() {
            super(...arguments);
            this.renderer = new NavDataFieldNumberUnitRenderer({
                title: 'DIS',
                displayUnit: this.unitsSettingManager.distanceUnitsLarge,
                formatter: NextGenDisplayUnitNavDataFieldTypeRenderer.DISTANCE_FORMATTER,
                class: 'nav-data-field-gps'
            });
        }
        /** @inheritdoc */
        render(model) {
            return this.renderer.render(model);
        }
    }
    /**
     * Renders next-generation (NXi, G3000, etc) Distance to Destination navigation data fields.
     */
    class NextGenNavDataFieldDtgRenderer extends NextGenDisplayUnitNavDataFieldTypeRenderer {
        constructor() {
            super(...arguments);
            this.renderer = new NavDataFieldNumberUnitRenderer({
                title: 'DTG',
                displayUnit: this.unitsSettingManager.distanceUnitsLarge,
                formatter: NextGenDisplayUnitNavDataFieldTypeRenderer.DISTANCE_FORMATTER,
                class: 'nav-data-field-gps'
            });
        }
        /** @inheritdoc */
        render(model) {
            return this.renderer.render(model);
        }
    }
    /**
     * Renders next-generation (NXi, G3000, etc) Desired Track navigation data fields.
     */
    class NextGenNavDataFieldDtkRenderer extends NextGenDisplayUnitNavDataFieldTypeRenderer {
        constructor() {
            super(...arguments);
            this.renderer = new NavDataFieldBearingRenderer({
                title: 'DTK',
                displayUnit: this.unitsSettingManager.navAngleUnits,
                formatter: NextGenDisplayUnitNavDataFieldTypeRenderer.BEARING_FORMATTER,
                class: 'nav-data-field-gps'
            });
        }
        /** @inheritdoc */
        render(model) {
            return this.renderer.render(model);
        }
    }
    /**
     * Renders next-generation (NXi, G3000, etc) Endurance navigation data fields.
     */
    class NextGenNavDataFieldEndRenderer extends NextGenDisplayUnitNavDataFieldTypeRenderer {
        constructor() {
            super(...arguments);
            this.renderer = new NavDataFieldDurationRenderer({
                title: 'END',
                options: NextGenDisplayUnitNavDataFieldTypeRenderer.DURATION_OPTIONS,
                class: 'nav-data-field-magenta'
            });
        }
        /** @inheritdoc */
        render(model) {
            return this.renderer.render(model);
        }
    }
    /**
     * Renders next-generation (NXi, G3000, etc) Time to Destination navigation data fields.
     */
    class NextGenNavDataFieldEnrRenderer extends NextGenDisplayUnitNavDataFieldTypeRenderer {
        constructor() {
            super(...arguments);
            this.renderer = new NavDataFieldDurationRenderer({
                title: 'ENR',
                options: NextGenDisplayUnitNavDataFieldTypeRenderer.DURATION_OPTIONS,
                class: 'nav-data-field-gps'
            });
        }
        /** @inheritdoc */
        render(model) {
            return this.renderer.render(model);
        }
    }
    /**
     * Renders next-generation (NXi, G3000, etc) Estimated Time of Arrival navigation data fields.
     */
    class NextGenNavDataFieldEtaRenderer extends NextGenDateTimeNavDataFieldTypeRenderer {
        constructor() {
            super(...arguments);
            this.renderer = new NavDataFieldTimeRenderer({
                title: 'ETA',
                format: this.timeFormatMapped,
                localOffset: this.localOffset,
                class: 'nav-data-field-gps'
            });
        }
        /** @inheritdoc */
        render(model) {
            return this.renderer.render(model);
        }
    }
    /**
     * Renders next-generation (NXi, G3000, etc) Time to Waypoint navigation data fields.
     */
    class NextGenNavDataFieldEteRenderer extends NextGenDisplayUnitNavDataFieldTypeRenderer {
        constructor() {
            super(...arguments);
            this.renderer = new NavDataFieldDurationRenderer({
                title: 'ETE',
                options: NextGenDisplayUnitNavDataFieldTypeRenderer.DURATION_OPTIONS,
                class: 'nav-data-field-gps'
            });
        }
        /** @inheritdoc */
        render(model) {
            return this.renderer.render(model);
        }
    }
    /**
     * Renders next-generation (NXi, G3000, etc) Fuel on Board navigation data fields.
     */
    class NextGenNavDataFieldFobRenderer extends NextGenDisplayUnitNavDataFieldTypeRenderer {
        constructor() {
            super(...arguments);
            this.renderer = new NavDataFieldNumberUnitRenderer({
                title: 'FOB',
                displayUnit: this.unitsSettingManager.fuelUnits,
                formatter: NextGenDisplayUnitNavDataFieldTypeRenderer.FUEL_FORMATTER,
                class: 'nav-data-field-magenta'
            });
        }
        /** @inheritdoc */
        render(model) {
            return this.renderer.render(model);
        }
    }
    /**
     * Renders next-generation (NXi, G3000, etc) Fuel Over Destination navigation data fields.
     */
    class NextGenNavDataFieldFodRenderer extends NextGenDisplayUnitNavDataFieldTypeRenderer {
        constructor() {
            super(...arguments);
            this.renderer = new NavDataFieldNumberUnitRenderer({
                title: 'FOD',
                displayUnit: this.unitsSettingManager.fuelUnits,
                formatter: NextGenDisplayUnitNavDataFieldTypeRenderer.FUEL_FORMATTER,
                class: 'nav-data-field-gps'
            });
        }
        /** @inheritdoc */
        render(model) {
            return this.renderer.render(model);
        }
    }
    /**
     * Renders next-generation (NXi, G3000, etc) Ground Speed navigation data fields.
     */
    class NextGenNavDataFieldGsRenderer extends NextGenDisplayUnitNavDataFieldTypeRenderer {
        constructor() {
            super(...arguments);
            this.renderer = new NavDataFieldNumberUnitRenderer({
                title: 'GS',
                displayUnit: this.unitsSettingManager.speedUnits,
                formatter: NextGenDisplayUnitNavDataFieldTypeRenderer.SPEED_FORMATTER,
                class: 'nav-data-field-gps'
            });
        }
        /** @inheritdoc */
        render(model) {
            return this.renderer.render(model);
        }
    }
    /**
     * Renders next-generation (NXi, G3000, etc) ISA navigation data fields.
     */
    class NextGenNavDataFieldIsaRenderer extends NextGenDisplayUnitNavDataFieldTypeRenderer {
        constructor() {
            super(...arguments);
            this.renderer = new NavDataFieldNumberUnitRenderer({
                title: 'ISA',
                displayUnit: this.unitsSettingManager.temperatureDeltaUnits,
                formatter: NextGenDisplayUnitNavDataFieldTypeRenderer.ISA_FORMATTER,
                class: 'nav-data-field-white'
            });
        }
        /** @inheritdoc */
        render(model) {
            return this.renderer.render(model);
        }
    }
    /**
     * Renders next-generation (NXi, G3000, etc) Estimated Time of Arrival at Destination navigation data fields.
     */
    class NavDataFieldLdgRenderer extends NextGenDateTimeNavDataFieldTypeRenderer {
        constructor() {
            super(...arguments);
            this.renderer = new NavDataFieldTimeRenderer({
                title: 'LDG',
                format: this.timeFormatMapped,
                localOffset: this.localOffset,
                class: 'nav-data-field-gps'
            });
        }
        /** @inheritdoc */
        render(model) {
            return this.renderer.render(model);
        }
    }
    /**
     * Renders next-generation (NXi, G3000, etc) True Airspeed navigation data fields.
     */
    class NextGenNavDataFieldTasRenderer extends NextGenDisplayUnitNavDataFieldTypeRenderer {
        constructor() {
            super(...arguments);
            this.renderer = new NavDataFieldNumberUnitRenderer({
                title: 'TAS',
                displayUnit: this.unitsSettingManager.speedUnits,
                formatter: NextGenDisplayUnitNavDataFieldTypeRenderer.SPEED_FORMATTER,
                class: 'nav-data-field-magenta'
            });
        }
        /** @inheritdoc */
        render(model) {
            return this.renderer.render(model);
        }
    }
    /**
     * Renders next-generation (NXi, G3000, etc) Track Angle Error navigation data fields.
     */
    class NextGenNavDataFieldTkeRenderer extends NextGenDisplayUnitNavDataFieldTypeRenderer {
        constructor() {
            super(...arguments);
            this.renderer = new NavDataFieldNumberUnitRenderer({
                title: 'TKE',
                displayUnit: msfsSdk.Subject.create(msfsSdk.UnitType.DEGREE),
                formatter: NextGenDisplayUnitNavDataFieldTypeRenderer.ANGLE_FORMATTER,
                class: 'nav-data-field-gps'
            });
        }
        /** @inheritdoc */
        render(model) {
            return this.renderer.render(model);
        }
    }
    /**
     * Renders next-generation (NXi, G3000, etc) Ground Track navigation data fields.
     */
    class NextGenNavDataFieldTrkRenderer extends NextGenDisplayUnitNavDataFieldTypeRenderer {
        constructor() {
            super(...arguments);
            this.renderer = new NavDataFieldBearingRenderer({
                title: 'TRK',
                displayUnit: this.unitsSettingManager.navAngleUnits,
                formatter: NextGenDisplayUnitNavDataFieldTypeRenderer.BEARING_FORMATTER,
                class: 'nav-data-field-gps'
            });
        }
        /** @inheritdoc */
        render(model) {
            return this.renderer.render(model);
        }
    }
    /**
     * Renders next-generation (NXi, G3000, etc) Vertical Speed Required navigation data fields.
     */
    class NextGenNavDataFieldVsrRenderer extends NextGenDisplayUnitNavDataFieldTypeRenderer {
        constructor() {
            super(...arguments);
            this.renderer = new NavDataFieldNumberUnitRenderer({
                title: 'VSR',
                displayUnit: this.unitsSettingManager.verticalSpeedUnits,
                formatter: NextGenDisplayUnitNavDataFieldTypeRenderer.SPEED_FORMATTER,
                class: 'nav-data-field-gps'
            });
        }
        /** @inheritdoc */
        render(model) {
            return this.renderer.render(model);
        }
    }
    /**
     * Renders next-generation (NXi, G3000, etc) Cross Track navigation data fields.
     */
    class NextGenNavDataFieldXtkRenderer extends NextGenDisplayUnitNavDataFieldTypeRenderer {
        constructor() {
            super(...arguments);
            this.renderer = new NavDataFieldNumberUnitRenderer({
                title: 'XTK',
                displayUnit: this.unitsSettingManager.distanceUnitsLarge,
                formatter: NextGenDisplayUnitNavDataFieldTypeRenderer.DISTANCE_FORMATTER,
                class: 'nav-data-field-gps'
            });
        }
        /** @inheritdoc */
        render(model) {
            return this.renderer.render(model);
        }
    }

    /**
     * A next-generation (NXi, G3000, etc) implementation of {@link NavDataFieldRenderer} which supports all navigation
     * data bar field types.
     */
    class NextGenNavDataBarFieldRenderer {
        /**
         * Constructor.
         * @param unitsSettingManager A display units user setting manager.
         * @param dateTimeSettingManager A date/time user setting manager.
         */
        constructor(unitsSettingManager, dateTimeSettingManager) {
            this.renderer = new GenericNavDataFieldRenderer();
            this.renderer.register(exports.NavDataFieldType.BearingToWaypoint, new NextGenNavDataFieldBrgRenderer(unitsSettingManager));
            this.renderer.register(exports.NavDataFieldType.Destination, new NextGenNavDataFieldDestRenderer(unitsSettingManager));
            this.renderer.register(exports.NavDataFieldType.DistanceToWaypoint, new NextGenNavDataFieldDisRenderer(unitsSettingManager));
            this.renderer.register(exports.NavDataFieldType.DistanceToDestination, new NextGenNavDataFieldDtgRenderer(unitsSettingManager));
            this.renderer.register(exports.NavDataFieldType.DesiredTrack, new NextGenNavDataFieldDtkRenderer(unitsSettingManager));
            this.renderer.register(exports.NavDataFieldType.Endurance, new NextGenNavDataFieldEndRenderer(unitsSettingManager));
            this.renderer.register(exports.NavDataFieldType.TimeToDestination, new NextGenNavDataFieldEnrRenderer(unitsSettingManager));
            this.renderer.register(exports.NavDataFieldType.TimeOfWaypointArrival, new NextGenNavDataFieldEtaRenderer(dateTimeSettingManager));
            this.renderer.register(exports.NavDataFieldType.TimeToWaypoint, new NextGenNavDataFieldEteRenderer(unitsSettingManager));
            this.renderer.register(exports.NavDataFieldType.FuelOnBoard, new NextGenNavDataFieldFobRenderer(unitsSettingManager));
            this.renderer.register(exports.NavDataFieldType.FuelOverDestination, new NextGenNavDataFieldFodRenderer(unitsSettingManager));
            this.renderer.register(exports.NavDataFieldType.GroundSpeed, new NextGenNavDataFieldGsRenderer(unitsSettingManager));
            this.renderer.register(exports.NavDataFieldType.ISA, new NextGenNavDataFieldIsaRenderer(unitsSettingManager));
            this.renderer.register(exports.NavDataFieldType.TimeOfDestinationArrival, new NavDataFieldLdgRenderer(dateTimeSettingManager));
            this.renderer.register(exports.NavDataFieldType.TrueAirspeed, new NextGenNavDataFieldTasRenderer(unitsSettingManager));
            this.renderer.register(exports.NavDataFieldType.TrackAngleError, new NextGenNavDataFieldTkeRenderer(unitsSettingManager));
            this.renderer.register(exports.NavDataFieldType.GroundTrack, new NextGenNavDataFieldTrkRenderer(unitsSettingManager));
            this.renderer.register(exports.NavDataFieldType.VerticalSpeedRequired, new NextGenNavDataFieldVsrRenderer(unitsSettingManager));
            this.renderer.register(exports.NavDataFieldType.CrossTrack, new NextGenNavDataFieldXtkRenderer(unitsSettingManager));
        }
        /** @inheritdoc */
        render(type, model) {
            return this.renderer.render(type, model);
        }
    }

    /**
     * A utility class for working with reference V-speed user settings.
     */
    class VSpeedUserSettingUtils {
        // eslint-disable-next-line jsdoc/require-jsdoc
        static activeValue(name, settingManager, useFmsValue, allowZeroValue = false) {
            var _a, _b, _c, _d, _e;
            return useFmsValue
                ? msfsSdk.MappedSubject.create(([defaultVal, userVal, fmsVal]) => {
                    let val = -1;
                    if (userVal >= 0) {
                        if (allowZeroValue || userVal > 0) {
                            val = userVal;
                        }
                    }
                    else if (allowZeroValue ? fmsVal >= 0 : fmsVal > 0) {
                        val = fmsVal;
                    }
                    else if (allowZeroValue ? defaultVal >= 0 : defaultVal > 0) {
                        val = defaultVal;
                    }
                    return val;
                }, (_a = settingManager.tryGetSetting(`vSpeedDefaultValue_${name}`)) !== null && _a !== void 0 ? _a : msfsSdk.Subject.create(-1), (_b = settingManager.tryGetSetting(`vSpeedUserValue_${name}`)) !== null && _b !== void 0 ? _b : msfsSdk.Subject.create(-1), (_c = settingManager.tryGetSetting(`vSpeedFmsValue_${name}`)) !== null && _c !== void 0 ? _c : msfsSdk.Subject.create(-1))
                : msfsSdk.MappedSubject.create(([defaultVal, userVal]) => {
                    let val = -1;
                    if (userVal >= 0) {
                        if (allowZeroValue || userVal > 0) {
                            val = userVal;
                        }
                    }
                    else if (allowZeroValue ? defaultVal >= 0 : defaultVal > 0) {
                        val = defaultVal;
                    }
                    return val;
                }, (_d = settingManager.tryGetSetting(`vSpeedDefaultValue_${name}`)) !== null && _d !== void 0 ? _d : msfsSdk.Subject.create(-1), (_e = settingManager.tryGetSetting(`vSpeedUserValue_${name}`)) !== null && _e !== void 0 ? _e : msfsSdk.Subject.create(-1));
        }
        // eslint-disable-next-line jsdoc/require-jsdoc
        static isUserValueActive(name, settingManager, useFmsValue) {
            var _a, _b, _c, _d, _e;
            return useFmsValue
                ? msfsSdk.MappedSubject.create(([defaultVal, userVal, fmsVal]) => {
                    if (userVal < 0) {
                        return false;
                    }
                    return userVal !== fmsVal && userVal !== defaultVal;
                }, (_a = settingManager.tryGetSetting(`vSpeedDefaultValue_${name}`)) !== null && _a !== void 0 ? _a : msfsSdk.Subject.create(-1), (_b = settingManager.tryGetSetting(`vSpeedUserValue_${name}`)) !== null && _b !== void 0 ? _b : msfsSdk.Subject.create(-1), (_c = settingManager.tryGetSetting(`vSpeedFmsValue_${name}`)) !== null && _c !== void 0 ? _c : msfsSdk.Subject.create(-1))
                : msfsSdk.MappedSubject.create(([defaultVal, userVal]) => {
                    if (userVal < 0) {
                        return false;
                    }
                    return userVal !== defaultVal;
                }, (_d = settingManager.tryGetSetting(`vSpeedDefaultValue_${name}`)) !== null && _d !== void 0 ? _d : msfsSdk.Subject.create(-1), (_e = settingManager.tryGetSetting(`vSpeedUserValue_${name}`)) !== null && _e !== void 0 ? _e : msfsSdk.Subject.create(-1));
        }
        /**
         * Creates a mapped subscribable which provides whether a reference V-speed is using its FMS-defined value as its
         * active value.
         * @param name The name of the reference V-speed.
         * @param settingManager A manager for reference V-speed user settings.
         * @returns A mapped subscribable which provides whether the specified reference V-speed is using its FMS-defined
         * value as its active value.
         */
        static isFmsValueActive(name, settingManager) {
            var _a, _b;
            return msfsSdk.MappedSubject.create(([userVal, fmsVal]) => {
                if (fmsVal < 0) {
                    return false;
                }
                if (userVal >= 0) {
                    return userVal === fmsVal;
                }
                else {
                    return true;
                }
            }, (_a = settingManager.tryGetSetting(`vSpeedUserValue_${name}`)) !== null && _a !== void 0 ? _a : msfsSdk.Subject.create(-1), (_b = settingManager.tryGetSetting(`vSpeedFmsValue_${name}`)) !== null && _b !== void 0 ? _b : msfsSdk.Subject.create(-1));
        }
    }

    /**
     * Width options for airspeed indicator color ranges.
     */
    exports.AirspeedIndicatorColorRangeWidth = void 0;
    (function (AirspeedIndicatorColorRangeWidth) {
        AirspeedIndicatorColorRangeWidth["Full"] = "Full";
        AirspeedIndicatorColorRangeWidth["Half"] = "Half";
    })(exports.AirspeedIndicatorColorRangeWidth || (exports.AirspeedIndicatorColorRangeWidth = {}));
    /**
     * Color options for airspeed indicator color ranges.
     */
    exports.AirspeedIndicatorColorRangeColor = void 0;
    (function (AirspeedIndicatorColorRangeColor) {
        AirspeedIndicatorColorRangeColor["Red"] = "Red";
        AirspeedIndicatorColorRangeColor["Yellow"] = "Yellow";
        AirspeedIndicatorColorRangeColor["White"] = "White";
        AirspeedIndicatorColorRangeColor["Green"] = "Green";
        AirspeedIndicatorColorRangeColor["BarberPole"] = "BarberPole";
    })(exports.AirspeedIndicatorColorRangeColor || (exports.AirspeedIndicatorColorRangeColor = {}));

    /**
     * Types of airspeed alerts.
     */
    exports.AirspeedAlert = void 0;
    (function (AirspeedAlert) {
        AirspeedAlert[AirspeedAlert["None"] = 0] = "None";
        AirspeedAlert[AirspeedAlert["Overspeed"] = 1] = "Overspeed";
        AirspeedAlert[AirspeedAlert["TrendOverspeed"] = 2] = "TrendOverspeed";
        AirspeedAlert[AirspeedAlert["Underspeed"] = 4] = "Underspeed";
        AirspeedAlert[AirspeedAlert["TrendUnderspeed"] = 8] = "TrendUnderspeed";
    })(exports.AirspeedAlert || (exports.AirspeedAlert = {}));
    /**
     * A default implementation of {@link AirspeedIndicatorDataProvider}.
     */
    class DefaultAirspeedIndicatorDataProvider {
        /**
         * Constructor.
         * @param bus The event bus.
         * @param adcIndex The index of the ADC that is the source of this provider's data.
         * @param options Configuration options for this provider.
         * @param aoaDataProvider A provider of angle of attack data.
         * @param trendInputSmoothingTau The smoothing time constant, in milliseconds, to apply to the IAS lookahead trend's
         * input values. Defaults to `2000 / ln(2)`.
         * @param trendTrendSmoothingTau The smoothing time constant, in milliseconds, to apply to the IAS lookahead trend
         * values. Defaults to `1000 / ln(2)`.
         */
        constructor(bus, adcIndex, options, aoaDataProvider, trendInputSmoothingTau = DefaultAirspeedIndicatorDataProvider.DEFAULT_IAS_TREND_INPUT_SMOOTHING_TAU, trendTrendSmoothingTau = DefaultAirspeedIndicatorDataProvider.DEFAULT_IAS_TREND_TREND_SMOOTHING_TAU) {
            var _a, _b;
            this.bus = bus;
            this.aoaDataProvider = aoaDataProvider;
            this._iasKnots = msfsSdk.ConsumerSubject.create(null, 0);
            /** @inheritdoc */
            this.iasKnots = this._iasKnots;
            this._tasKnots = msfsSdk.ConsumerSubject.create(null, 0);
            /** @inheritdoc */
            this.tasKnots = this._tasKnots;
            this._mach = msfsSdk.ConsumerSubject.create(null, 0);
            /** @inheritdoc */
            this.mach = this._mach;
            this._machToKias = msfsSdk.Subject.create(0);
            /** @inheritdoc */
            this.machToKias = this._machToKias;
            this._tasToIas = msfsSdk.Subject.create(0);
            /** @inheritdoc */
            this.tasToIas = this._tasToIas;
            this._pressureAlt = msfsSdk.ConsumerSubject.create(null, 0);
            /** @inheritdoc */
            this.pressureAlt = this._pressureAlt;
            this._iasTrend = msfsSdk.Subject.create(0);
            /** @inheritdoc */
            this.iasTrend = this._iasTrend;
            this._referenceIas = msfsSdk.Subject.create(0);
            /** @inheritdoc */
            this.referenceIas = this._referenceIas;
            this._referenceMach = msfsSdk.Subject.create(0);
            /** @inheritdoc */
            this.referenceMach = this._referenceMach;
            this._referenceIsManual = msfsSdk.ConsumerSubject.create(null, true);
            /** @inheritdoc */
            this.referenceIsManual = this._referenceIsManual;
            this._airspeedAlerts = msfsSdk.Subject.create(0);
            /** @inheritdoc */
            this.airspeedAlerts = this._airspeedAlerts;
            this._isOverspeedProtectionActive = msfsSdk.Subject.create(false);
            /** @inheritdoc */
            this.isOverspeedProtectionActive = this._isOverspeedProtectionActive;
            this._isUnderspeedProtectionActive = msfsSdk.Subject.create(false);
            /** @inheritdoc */
            this.isUnderspeedProtectionActive = this._isUnderspeedProtectionActive;
            /** @inheritdoc */
            this.normAoaIasCoef = (_b = (_a = this.aoaDataProvider) === null || _a === void 0 ? void 0 : _a.normAoaIasCoef) !== null && _b !== void 0 ? _b : msfsSdk.Subject.create(null);
            this.isAirspeedDataValid = msfsSdk.ConsumerSubject.create(null, false);
            /** @inheritdoc */
            this.isDataFailed = this.isAirspeedDataValid.map(msfsSdk.SubscribableMapFunctions.not());
            this.machToKiasSmoother = new msfsSdk.ExpSmoother(DefaultAirspeedIndicatorDataProvider.SPEED_CONVERSION_SMOOTHING_TAU);
            this.lastMachToKiasTime = 0;
            this.tasToIasSmoother = new msfsSdk.ExpSmoother(DefaultAirspeedIndicatorDataProvider.SPEED_CONVERSION_SMOOTHING_TAU);
            this.lastTasToIasTime = 0;
            this.simTime = msfsSdk.ConsumerSubject.create(null, 0);
            this.isOnGround = msfsSdk.ConsumerSubject.create(null, false);
            this.referenceIasSource = msfsSdk.ConsumerSubject.create(null, 0);
            this.referenceMachSource = msfsSdk.ConsumerSubject.create(null, 0);
            this.referenceIsMach = msfsSdk.ConsumerSubject.create(null, false);
            this.referenceIasPipe = this.referenceIasSource.pipe(this._referenceIas, ias => ias > 0 ? ias : null, true);
            this.referenceMachPipe = this.referenceMachSource.pipe(this._referenceMach, mach => mach > 0 ? mach : null, true);
            this.isInit = false;
            this.isAlive = true;
            this.isPaused = false;
            this.adcIndex = msfsSdk.SubscribableUtils.toSubscribable(adcIndex, true);
            this.trendLookahead = msfsSdk.SubscribableUtils.toSubscribable(options.trendLookahead, true);
            this.iasLookahead = new msfsSdk.Lookahead(this.trendLookahead.get() * 1000, trendInputSmoothingTau, trendTrendSmoothingTau);
            this.overspeedThreshold = msfsSdk.SubscribableUtils.toSubscribable(options.overspeedThreshold(this), true);
            this.underspeedThreshold = msfsSdk.SubscribableUtils.toSubscribable(options.underspeedThreshold(this), true);
            this.isAirspeedHoldActive = options.isAirspeedHoldActive
                ? msfsSdk.SubscribableUtils.toSubscribable(options.isAirspeedHoldActive, true)
                : this.isFlcActive = msfsSdk.ConsumerSubject.create(null, false);
            this.isOverspeed = msfsSdk.MappedSubject.create(([iasKnots, threshold]) => iasKnots >= threshold, this._iasKnots, this.overspeedThreshold);
            this.isTrendOverspeed = msfsSdk.MappedSubject.create(([iasKnots, iasTrend, threshold]) => iasKnots + iasTrend >= threshold, this._iasKnots, this._iasTrend, this.overspeedThreshold);
            this.isUnderspeed = msfsSdk.MappedSubject.create(([iasKnots, threshold, isOnGround]) => !isOnGround && iasKnots <= threshold, this._iasKnots, this.underspeedThreshold, this.isOnGround);
            this.isTrendUnderspeed = msfsSdk.MappedSubject.create(([iasKnots, iasTrend, threshold, isOnGround]) => !isOnGround && iasKnots + iasTrend <= threshold, this._iasKnots, this._iasTrend, this.underspeedThreshold, this.isOnGround);
        }
        /**
         * Initializes this data provider. Once initialized
         * @param paused Whether to initialize this data provider as paused. If `true`, this data provider will provide an
         * initial set of data but will not update the provided data until it is resumed. Defaults to `false`.
         * @throws Error if this data provider is dead.
         */
        init(paused = false) {
            var _a;
            if (!this.isAlive) {
                throw new Error('DefaultAirspeedIndicatorDataProvider: cannot initialize a dead provider');
            }
            if (this.isInit) {
                return;
            }
            this.isInit = true;
            this.isPaused = paused;
            const sub = this.bus.getSubscriber();
            this.simTime.setConsumer(sub.on('simTime'));
            this.isOnGround.setConsumer(sub.on('on_ground'));
            this.adcIndexSub = this.adcIndex.sub(index => {
                var _a, _b;
                (_a = this.machToKiasSub) === null || _a === void 0 ? void 0 : _a.destroy();
                this.machToKiasSmoother.reset();
                (_b = this.tasToIasSub) === null || _b === void 0 ? void 0 : _b.destroy();
                this.tasToIasSmoother.reset();
                this._iasKnots.setConsumer(sub.on(`adc_ias_${index}`));
                this._tasKnots.setConsumer(sub.on(`adc_tas_${index}`));
                this._mach.setConsumer(sub.on(`adc_mach_number_${index}`));
                this._pressureAlt.setConsumer(sub.on(`adc_pressure_alt_${index}`));
                this.isAirspeedDataValid.setConsumer(sub.on(`adc_airspeed_data_valid_${index}`));
                this.machToKiasSub = sub.on(`adc_mach_to_kias_factor_${index}`).handle(machToKcas => {
                    const time = Date.now();
                    this._machToKias.set(this.machToKiasSmoother.next(machToKcas, time - this.lastMachToKiasTime));
                    this.lastMachToKiasTime = time;
                });
                this.tasToIasSub = sub.on(`adc_tas_to_ias_factor_${index}`).handle(tasToCas => {
                    const time = Date.now();
                    this._tasToIas.set(this.tasToIasSmoother.next(tasToCas, time - this.lastTasToIasTime));
                    this.lastTasToIasTime = time;
                });
            }, true);
            this.referenceIasSource.setConsumer(sub.on('ap_ias_selected'));
            this.referenceMachSource.setConsumer(sub.on('ap_mach_selected'));
            this.referenceIsMach.setConsumer(sub.on('ap_selected_speed_is_mach'));
            this._referenceIsManual.setConsumer(sub.on('ap_selected_speed_is_manual'));
            (_a = this.isFlcActive) === null || _a === void 0 ? void 0 : _a.setConsumer(sub.on('ap_flc_hold'));
            if (paused) {
                this.pause();
            }
            this.trendLookaheadSub = this.trendLookahead.sub(lookahead => {
                this._iasTrend.set(0);
                this.iasLookahead.lookahead = lookahead * 1000;
                this.iasLookahead.reset();
            });
            this.simTime.sub(simTime => {
                var _a;
                const dt = simTime - ((_a = this.lastTrendTime) !== null && _a !== void 0 ? _a : simTime);
                const iasKnots = this.iasKnots.get();
                this.lastTrendTime = simTime;
                this._iasTrend.set(this.iasLookahead.nextTrend(iasKnots, dt));
            }, true);
            this.referenceIsMach.sub(isReferenceInMach => {
                if (isReferenceInMach) {
                    this.referenceIasPipe.pause();
                    this._referenceIas.set(null);
                    this.referenceMachPipe.resume(true);
                }
                else {
                    this.referenceMachPipe.pause();
                    this._referenceMach.set(null);
                    this.referenceIasPipe.resume(true);
                }
            }, true);
            this.isOverspeed.sub(isOverspeed => {
                const alerts = this._airspeedAlerts.get();
                this._airspeedAlerts.set(msfsSdk.BitFlags.set(alerts, isOverspeed ? exports.AirspeedAlert.Overspeed : exports.AirspeedAlert.None, exports.AirspeedAlert.Overspeed));
            }, true);
            this.isTrendOverspeed.sub(isTrendOverspeed => {
                const alerts = this._airspeedAlerts.get();
                this._airspeedAlerts.set(msfsSdk.BitFlags.set(alerts, isTrendOverspeed ? exports.AirspeedAlert.TrendOverspeed : exports.AirspeedAlert.None, exports.AirspeedAlert.TrendOverspeed));
            }, true);
            this.isUnderspeed.sub(isUnderspeed => {
                const alerts = this._airspeedAlerts.get();
                this._airspeedAlerts.set(msfsSdk.BitFlags.set(alerts, isUnderspeed ? exports.AirspeedAlert.Underspeed : exports.AirspeedAlert.None, exports.AirspeedAlert.Underspeed));
            }, true);
            this.isTrendUnderspeed.sub(isTrendUnderspeed => {
                const alerts = this._airspeedAlerts.get();
                this._airspeedAlerts.set(msfsSdk.BitFlags.set(alerts, isTrendUnderspeed ? exports.AirspeedAlert.TrendUnderspeed : exports.AirspeedAlert.None, exports.AirspeedAlert.TrendUnderspeed));
            }, true);
        }
        /** @inheritdoc */
        estimateIasFromNormAoa(normAoa) {
            var _a, _b;
            return (_b = (_a = this.aoaDataProvider) === null || _a === void 0 ? void 0 : _a.estimateIasFromNormAoa(normAoa)) !== null && _b !== void 0 ? _b : NaN;
        }
        /**
         * Resumes this data provider. Once resumed, this data provider will continuously update its data until paused or
         * destroyed.
         * @throws Error if this data provider is dead.
         */
        resume() {
            var _a, _b, _c;
            if (!this.isAlive) {
                throw new Error('DefaultAirspeedIndicatorDataProvider: cannot resume a dead provider');
            }
            if (!this.isPaused) {
                return;
            }
            this.isPaused = false;
            this.simTime.resume();
            this.isOnGround.resume();
            this._iasKnots.resume();
            this._tasKnots.resume();
            this._mach.resume();
            (_a = this.machToKiasSub) === null || _a === void 0 ? void 0 : _a.resume(true);
            (_b = this.tasToIasSub) === null || _b === void 0 ? void 0 : _b.resume(true);
            this._pressureAlt.resume();
            this.referenceIasSource.resume();
            this.referenceMachSource.resume();
            this.referenceIsMach.resume();
            this._referenceIsManual.resume();
            (_c = this.isFlcActive) === null || _c === void 0 ? void 0 : _c.resume();
            this.isOverspeed.resume();
            this.isTrendOverspeed.resume();
            this.isUnderspeed.resume();
            this.isTrendUnderspeed.resume();
            this.isAirspeedDataValid.resume();
        }
        /**
         * Pauses this data provider. Once paused, this data provider will not update its data until it is resumed.
         * @throws Error if this data provider is dead.
         */
        pause() {
            var _a, _b, _c;
            if (!this.isAlive) {
                throw new Error('DefaultAirspeedIndicatorDataProvider: cannot pause a dead provider');
            }
            if (this.isPaused) {
                return;
            }
            this.simTime.pause();
            this.isOnGround.pause();
            this._iasKnots.pause();
            this._tasKnots.pause();
            this._mach.pause();
            (_a = this.machToKiasSub) === null || _a === void 0 ? void 0 : _a.pause();
            (_b = this.tasToIasSub) === null || _b === void 0 ? void 0 : _b.pause();
            this._pressureAlt.pause();
            this.machToKiasSmoother.reset();
            this.referenceIasSource.pause();
            this.referenceMachSource.pause();
            this.referenceIsMach.pause();
            this._referenceIsManual.pause();
            (_c = this.isFlcActive) === null || _c === void 0 ? void 0 : _c.pause();
            this.isOverspeed.pause();
            this.isTrendOverspeed.pause();
            this.isUnderspeed.pause();
            this.isTrendUnderspeed.pause();
            this.isAirspeedDataValid.pause();
            this.lastTrendTime = undefined;
            this.iasLookahead.reset();
            this.isPaused = true;
        }
        /**
         * Destroys this data provider. Once destroyed, this data provider will no longer update its provided data, and can
         * no longer be paused or resumed.
         */
        destroy() {
            var _a, _b, _c, _d, _e;
            this.isAlive = false;
            this.simTime.destroy();
            this.isOnGround.destroy();
            this._iasKnots.destroy();
            this._tasKnots.destroy();
            this._mach.destroy();
            this._pressureAlt.destroy();
            this.referenceIasSource.destroy();
            this.referenceMachSource.destroy();
            this.referenceIsMach.destroy();
            this._referenceIsManual.destroy();
            (_a = this.isFlcActive) === null || _a === void 0 ? void 0 : _a.destroy();
            this.isOverspeed.destroy();
            this.isTrendOverspeed.destroy();
            this.isUnderspeed.destroy();
            this.isTrendUnderspeed.destroy();
            this.isAirspeedDataValid.destroy();
            (_b = this.adcIndexSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.machToKiasSub) === null || _c === void 0 ? void 0 : _c.destroy();
            (_d = this.tasToIasSub) === null || _d === void 0 ? void 0 : _d.destroy();
            (_e = this.trendLookaheadSub) === null || _e === void 0 ? void 0 : _e.destroy();
        }
    }
    DefaultAirspeedIndicatorDataProvider.SPEED_CONVERSION_SMOOTHING_TAU = 5000 / Math.LN2; // milliseconds
    DefaultAirspeedIndicatorDataProvider.DEFAULT_IAS_TREND_INPUT_SMOOTHING_TAU = 2000 / Math.LN2; // milliseconds
    DefaultAirspeedIndicatorDataProvider.DEFAULT_IAS_TREND_TREND_SMOOTHING_TAU = 1000 / Math.LN2; // milliseconds

    /**
     * V-speed annunciation types.
     */
    exports.VSpeedAnnunciation = void 0;
    (function (VSpeedAnnunciation) {
        VSpeedAnnunciation["None"] = "None";
        VSpeedAnnunciation["Takeoff"] = "Takeoff";
        VSpeedAnnunciation["Landing"] = "Landing";
    })(exports.VSpeedAnnunciation || (exports.VSpeedAnnunciation = {}));
    /**
     * A default implementation of {@link VSpeedAnnunciationDataProvider}.
     */
    class DefaultVSpeedAnnunciationDataProvider {
        /**
         * Constructor.
         * @param bus The event bus.
         * @param vSpeedSettingManager A manager for V-speed user settings.
         * @param takeoffVSpeedNames The names of every takeoff V-speed.
         * @param landingVSpeedNames The names of every landing V-speed.
         * @param options Options with which to configure the data provider.
         */
        constructor(bus, vSpeedSettingManager, takeoffVSpeedNames, landingVSpeedNames, options) {
            var _a;
            this.bus = bus;
            this._annunciation = msfsSdk.Subject.create(exports.VSpeedAnnunciation.None);
            /** @inheritdoc */
            this.annunciation = this._annunciation;
            this.isOnGround = msfsSdk.ConsumerSubject.create(null, false);
            this.flightPhase = msfsSdk.ConsumerSubject.create(null, {
                isApproachActive: false,
                isToFaf: false,
                isPastFaf: false,
                isInMissedApproach: false
            }, FmsUtils.flightPhaseEquals);
            this.isInit = false;
            this.isAlive = true;
            this.isPaused = false;
            this.fmsId = msfsSdk.SubscribableUtils.toSubscribable((_a = options === null || options === void 0 ? void 0 : options.fmsId) !== null && _a !== void 0 ? _a : '', true);
            this.takeoffVSpeedShowSettings = Array.from(takeoffVSpeedNames)
                .map(name => vSpeedSettingManager.tryGetSetting(`vSpeedShow_${name}`))
                .filter(setting => setting !== undefined);
            this.landingVSpeedShowSettings = Array.from(landingVSpeedNames)
                .map(name => vSpeedSettingManager.tryGetSetting(`vSpeedShow_${name}`))
                .filter(setting => setting !== undefined);
            this.takeoffVSpeedShowCount = msfsSdk.MappedSubject.create(settings => settings.reduce(DefaultVSpeedAnnunciationDataProvider.COUNT_TRUE_FUNC, 0), ...this.takeoffVSpeedShowSettings).pause();
            this.landingVSpeedShowCount = msfsSdk.MappedSubject.create(settings => settings.reduce(DefaultVSpeedAnnunciationDataProvider.COUNT_TRUE_FUNC, 0), ...this.landingVSpeedShowSettings).pause();
        }
        /**
         * Initializes this data provider. Once initialized
         * @param paused Whether to initialize this data provider as paused. If `true`, this data provider will provide an
         * initial set of data but will not update the provided data until it is resumed. Defaults to `false`.
         * @throws Error if this data provider is dead.
         */
        init(paused = false) {
            if (!this.isAlive) {
                throw new Error('DefaultVSpeedAnnunciationDataProvider: cannot initialize a dead provider');
            }
            if (this.isInit) {
                return;
            }
            this.isInit = true;
            if (this.takeoffVSpeedShowSettings.length > 0 || this.landingVSpeedShowSettings.length > 0) {
                const sub = this.bus.getSubscriber();
                this.fmsIdSub = this.fmsId.sub(id => {
                    this.flightPhase.setConsumer(FmsUtils.onFmsEvent(id, sub, 'fms_flight_phase'));
                }, true);
                this.isOnGround.setConsumer(sub.on('on_ground'));
                this.takeoffVSpeedShowCount.resume();
                this.landingVSpeedShowCount.resume();
                if (this.takeoffVSpeedShowSettings.length > 0) {
                    this.takeoffAnnuncPipe = msfsSdk.MappedSubject.create(([takeoffCount, landingCount]) => {
                        if (takeoffCount === this.takeoffVSpeedShowSettings.length || landingCount > 0) {
                            return exports.VSpeedAnnunciation.None;
                        }
                        else {
                            return exports.VSpeedAnnunciation.Takeoff;
                        }
                    }, this.takeoffVSpeedShowCount, this.landingVSpeedShowCount).pipe(this._annunciation, true);
                }
                if (this.landingVSpeedShowSettings.length > 0) {
                    this.landingAnnuncPipe = msfsSdk.MappedSubject.create(([takeoffCount, landingCount, flightPhase]) => {
                        if (!flightPhase.isApproachActive || landingCount === this.landingVSpeedShowSettings.length || takeoffCount > 0) {
                            return exports.VSpeedAnnunciation.None;
                        }
                        else {
                            return exports.VSpeedAnnunciation.Landing;
                        }
                    }, this.takeoffVSpeedShowCount, this.landingVSpeedShowCount, this.flightPhase).pipe(this._annunciation, true);
                }
                this.isOnGroundSub = this.isOnGround.sub(isOnGround => {
                    var _a, _b, _c, _d;
                    if (isOnGround) {
                        (_a = this.landingAnnuncPipe) === null || _a === void 0 ? void 0 : _a.pause();
                        (_b = this.takeoffAnnuncPipe) === null || _b === void 0 ? void 0 : _b.resume(true);
                    }
                    else {
                        (_c = this.takeoffAnnuncPipe) === null || _c === void 0 ? void 0 : _c.pause();
                        (_d = this.landingAnnuncPipe) === null || _d === void 0 ? void 0 : _d.resume(true);
                    }
                }, true);
            }
            if (paused) {
                this.pause();
            }
        }
        /**
         * Resumes this data provider. Once resumed, this data provider will continuously update its data until paused or
         * destroyed.
         * @throws Error if this data provider is dead.
         */
        resume() {
            var _a;
            if (!this.isAlive) {
                throw new Error('DefaultVSpeedAnnunciationDataProvider: cannot resume a dead provider');
            }
            if (!this.isInit || !this.isPaused) {
                return;
            }
            this.isPaused = false;
            this.isOnGround.resume();
            this.flightPhase.resume();
            this.takeoffVSpeedShowCount.resume();
            this.landingVSpeedShowCount.resume();
            (_a = this.isOnGroundSub) === null || _a === void 0 ? void 0 : _a.resume(true);
        }
        /**
         * Pauses this data provider. Once paused, this data provider will not update its data until it is resumed.
         * @throws Error if this data provider is dead.
         */
        pause() {
            var _a, _b, _c;
            if (!this.isAlive) {
                throw new Error('DefaultVSpeedAnnunciationDataProvider: cannot pause a dead provider');
            }
            if (!this.isInit || this.isPaused) {
                return;
            }
            this.isPaused = true;
            this.isOnGround.pause();
            this.flightPhase.pause();
            this.takeoffVSpeedShowCount.pause();
            this.landingVSpeedShowCount.pause();
            (_a = this.isOnGroundSub) === null || _a === void 0 ? void 0 : _a.pause();
            (_b = this.takeoffAnnuncPipe) === null || _b === void 0 ? void 0 : _b.pause();
            (_c = this.landingAnnuncPipe) === null || _c === void 0 ? void 0 : _c.pause();
        }
        /**
         * Resets this data provider to provide an annunciation type of `VSpeedAnnunciation.None`.
         */
        reset() {
            this._annunciation.set(exports.VSpeedAnnunciation.None);
        }
        /**
         * Destroys this data provider. Once destroyed, this data provider will no longer update its provided data, and can
         * no longer be paused or resumed.
         */
        destroy() {
            var _a;
            this.isAlive = false;
            this.isOnGround.destroy();
            this.flightPhase.destroy();
            this.takeoffVSpeedShowCount.destroy();
            this.landingVSpeedShowCount.destroy();
            (_a = this.fmsIdSub) === null || _a === void 0 ? void 0 : _a.destroy();
        }
    }
    DefaultVSpeedAnnunciationDataProvider.COUNT_TRUE_FUNC = (sum, v) => sum + (v ? 1 : 0);

    /**
     * Modes for an airspeed indicator's bottom display box.
     */
    exports.AirspeedIndicatorBottomDisplayMode = void 0;
    (function (AirspeedIndicatorBottomDisplayMode) {
        AirspeedIndicatorBottomDisplayMode["TrueAirspeed"] = "Tas";
        AirspeedIndicatorBottomDisplayMode["Mach"] = "Mach";
    })(exports.AirspeedIndicatorBottomDisplayMode || (exports.AirspeedIndicatorBottomDisplayMode = {}));
    /**
     * A next-generation (NXi, G3000, etc) Garmin PFD airspeed indicator.
     */
    class AirspeedIndicator extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.referenceRef = msfsSdk.FSComponent.createRef();
            this.alertRef = msfsSdk.FSComponent.createRef();
            this.tapeRef = msfsSdk.FSComponent.createRef();
            this.rootCssClass = msfsSdk.SetSubject.create(['airspeed']);
            this.airspeedAlertOptions = Object.assign({}, this.props.airspeedAlertOptions);
            this.activeAlert = this.props.dataProvider.airspeedAlerts.map((alerts) => {
                // We should never have an overspeed and underspeed alert at the same time, but just in case, underspeed alerts
                // will take precedence
                if (this.airspeedAlertOptions.supportUnderspeed && msfsSdk.BitFlags.isAny(alerts, exports.AirspeedAlert.Underspeed)) {
                    return exports.AirspeedAlert.Underspeed;
                }
                else if (this.airspeedAlertOptions.supportOverspeed && msfsSdk.BitFlags.isAny(alerts, exports.AirspeedAlert.Overspeed)) {
                    return exports.AirspeedAlert.Overspeed;
                }
                else if (this.airspeedAlertOptions.supportTrendUnderspeed && msfsSdk.BitFlags.isAny(alerts, exports.AirspeedAlert.TrendUnderspeed)) {
                    return exports.AirspeedAlert.TrendUnderspeed;
                }
                else if (this.airspeedAlertOptions.supportTrendOverspeed && msfsSdk.BitFlags.isAny(alerts, exports.AirspeedAlert.TrendOverspeed)) {
                    return exports.AirspeedAlert.TrendOverspeed;
                }
                else {
                    return exports.AirspeedAlert.None;
                }
            }).pause();
            this.showTopDisplay = this.props.declutter.map(msfsSdk.SubscribableMapFunctions.not());
            this.isReferenceDisplayVisible = msfsSdk.Subject.create(false);
            this.isBottomDisplayVisible = msfsSdk.Subject.create(false);
            this.subscriptions = [
                this.activeAlert,
                this.showTopDisplay
            ];
        }
        /** @inheritdoc */
        onAfterRender() {
            this.activeAlert.resume();
            this.isReferenceDisplayVisible.sub(isVisible => {
                if (isVisible) {
                    this.rootCssClass.add('airspeed-reference-visible');
                }
                else {
                    this.rootCssClass.delete('airspeed-reference-visible');
                }
            }, true);
            this.isBottomDisplayVisible.sub(isVisible => {
                if (isVisible) {
                    this.rootCssClass.add('airspeed-bottom-display-visible');
                }
                else {
                    this.rootCssClass.delete('airspeed-bottom-display-visible');
                }
            }, true);
            this.activeAlert.sub(this.updateAlertClass.bind(this), true);
            this.subscriptions.push(this.props.dataProvider.isDataFailed.sub(isDataFailed => {
                if (isDataFailed) {
                    this.rootCssClass.add('data-failed');
                }
                else {
                    this.rootCssClass.delete('data-failed');
                }
            }, true));
        }
        /**
         * Updates this indicator's root CSS class list in response to the current active airspeed alert.
         * @param alert The current active airspeed alert.
         */
        updateAlertClass(alert) {
            this.rootCssClass.delete('airspeed-alert-overspeed');
            this.rootCssClass.delete('airspeed-alert-trend-overspeed');
            this.rootCssClass.delete('airspeed-alert-underspeed');
            this.rootCssClass.delete('airspeed-alert-trend-underspeed');
            switch (alert) {
                case exports.AirspeedAlert.Overspeed:
                    this.rootCssClass.add('airspeed-alert-overspeed');
                    break;
                case exports.AirspeedAlert.TrendOverspeed:
                    this.rootCssClass.add('airspeed-alert-trend-overspeed');
                    break;
                case exports.AirspeedAlert.Underspeed:
                    this.rootCssClass.add('airspeed-alert-underspeed');
                    break;
                case exports.AirspeedAlert.TrendUnderspeed:
                    this.rootCssClass.add('airspeed-alert-trend-underspeed');
                    break;
            }
        }
        /** @inheritdoc */
        render() {
            var _a;
            if (typeof this.props.class === 'object') {
                const sub = msfsSdk.FSComponent.bindCssClassSet(this.rootCssClass, this.props.class, AirspeedIndicator.RESERVED_CLASSES);
                if (Array.isArray(sub)) {
                    this.subscriptions.push(...sub);
                }
                else {
                    this.subscriptions.push(sub);
                }
            }
            else if (this.props.class) {
                for (const classToAdd of msfsSdk.FSComponent.parseCssClassesFromString(this.props.class, classToFilter => !AirspeedIndicator.RESERVED_CLASSES.includes(classToFilter))) {
                    this.rootCssClass.add(classToAdd);
                }
            }
            let isTasDisplayVisible;
            let isMachDisplayVisible;
            let isTakeoffVSpeedAnnuncVisible;
            let isLandingVSpeedAnnuncVisible;
            let showTas;
            let showMach;
            let showTakeoffVSpeedAnnunc;
            let showLandingVSpeedAnnunc;
            if (this.props.bottomDisplayOptions.mode === exports.AirspeedIndicatorBottomDisplayMode.TrueAirspeed) {
                // Bottom displays TAS.
                isTasDisplayVisible = msfsSdk.Subject.create(false);
                if (this.props.vSpeedAnnunciationDataProvider) {
                    isTakeoffVSpeedAnnuncVisible = msfsSdk.Subject.create(false);
                    isLandingVSpeedAnnuncVisible = msfsSdk.Subject.create(false);
                    // TAS display is hidden whenever any V-speed annunciation is active.
                    showTas = msfsSdk.MappedSubject.create(([isTakeoffVSpeedVisible, isLandingVSpeedVisible]) => !(isTakeoffVSpeedVisible || isLandingVSpeedVisible), isTakeoffVSpeedAnnuncVisible, isLandingVSpeedAnnuncVisible);
                    showTakeoffVSpeedAnnunc = msfsSdk.Subject.create(true);
                    showLandingVSpeedAnnunc = msfsSdk.Subject.create(true);
                    msfsSdk.MappedSubject.create(([isTasVisible, isTakeoffVSpeedVisible, isLandingVSpeedVisible]) => isTasVisible || isTakeoffVSpeedVisible || isLandingVSpeedVisible, isTasDisplayVisible, isTakeoffVSpeedAnnuncVisible, isLandingVSpeedAnnuncVisible).pipe(this.isBottomDisplayVisible);
                }
                else {
                    showTas = msfsSdk.Subject.create(true);
                    isTasDisplayVisible.pipe(this.isBottomDisplayVisible);
                }
            }
            else {
                // Bottom displays Mach.
                isMachDisplayVisible = msfsSdk.Subject.create(false);
                if (this.props.vSpeedAnnunciationDataProvider) {
                    isTakeoffVSpeedAnnuncVisible = msfsSdk.Subject.create(false);
                    isLandingVSpeedAnnuncVisible = msfsSdk.Subject.create(false);
                    // Mach display is hidden when the takeoff V-speed annunciation is active.
                    showMach = isTakeoffVSpeedAnnuncVisible.map(msfsSdk.SubscribableMapFunctions.not());
                    showTakeoffVSpeedAnnunc = msfsSdk.Subject.create(true);
                    // Landing V-speed annunciation is hidden when the mach display is visible.
                    showLandingVSpeedAnnunc = isMachDisplayVisible.map(msfsSdk.SubscribableMapFunctions.not());
                    msfsSdk.MappedSubject.create(([isTasVisible, isTakeoffVSpeedVisible, isLandingVSpeedVisible]) => isTasVisible || isTakeoffVSpeedVisible || isLandingVSpeedVisible, isMachDisplayVisible, isTakeoffVSpeedAnnuncVisible, isLandingVSpeedAnnuncVisible).pipe(this.isBottomDisplayVisible);
                }
                else {
                    showMach = msfsSdk.Subject.create(true);
                    isMachDisplayVisible.pipe(this.isBottomDisplayVisible);
                }
            }
            return (msfsSdk.FSComponent.buildComponent("div", { class: this.rootCssClass, "data-checklist": "checklist-airspeed" },
                msfsSdk.FSComponent.buildComponent(AirspeedTape, Object.assign({ ref: this.tapeRef, dataProvider: this.props.dataProvider }, this.props.tapeScaleOptions, { colorRanges: this.props.colorRanges }, this.props.trendVectorOptions, { vSpeedBugOptions: this.props.vSpeedBugOptions })),
                msfsSdk.FSComponent.buildComponent("div", { class: 'airspeed-top-container', "data-checklist": "checklist-airspeed-top" },
                    msfsSdk.FSComponent.buildComponent(AirspeedReferenceDisplay, { ref: this.referenceRef, show: this.showTopDisplay, referenceIas: this.props.dataProvider.referenceIas, referenceMach: this.props.dataProvider.referenceMach, referenceIsManual: this.props.dataProvider.referenceIsManual, isAirspeedHoldActive: this.props.dataProvider.isAirspeedHoldActive, isVisible: this.isReferenceDisplayVisible }),
                    msfsSdk.FSComponent.buildComponent(AirspeedProtectionAnnunciation, { ref: this.alertRef, show: this.showTopDisplay, isOverspeedProtectionActive: this.props.dataProvider.isOverspeedProtectionActive, isUnderspeedProtectionActive: this.props.dataProvider.isUnderspeedProtectionActive })),
                msfsSdk.FSComponent.buildComponent("div", { class: 'airspeed-bottom-container', "data-checklist": "checklist-airspeed-bottom" },
                    this.props.bottomDisplayOptions.mode === exports.AirspeedIndicatorBottomDisplayMode.TrueAirspeed && (msfsSdk.FSComponent.buildComponent(AirspeedTasDisplay, { show: showTas, tasKnots: this.props.dataProvider.tasKnots, isDataFailed: this.props.dataProvider.isDataFailed, isVisible: isTasDisplayVisible })),
                    this.props.vSpeedAnnunciationDataProvider !== undefined && (msfsSdk.FSComponent.buildComponent(AirspeedVSpeedAnnunciation, { annunciationType: exports.VSpeedAnnunciation.Landing, show: showLandingVSpeedAnnunc, activeAnnunciation: this.props.vSpeedAnnunciationDataProvider.annunciation, isDataFailed: this.props.dataProvider.isDataFailed, isVisible: isLandingVSpeedAnnuncVisible })),
                    this.props.bottomDisplayOptions.mode === exports.AirspeedIndicatorBottomDisplayMode.Mach && (msfsSdk.FSComponent.buildComponent(AirspeedMachDisplay, { show: showMach, mach: this.props.dataProvider.mach, threshold: (_a = this.props.bottomDisplayOptions.machThreshold) !== null && _a !== void 0 ? _a : msfsSdk.Subject.create(0), isDataFailed: this.props.dataProvider.isDataFailed, isVisible: isMachDisplayVisible })),
                    this.props.vSpeedAnnunciationDataProvider !== undefined && (msfsSdk.FSComponent.buildComponent(AirspeedVSpeedAnnunciation, { annunciationType: exports.VSpeedAnnunciation.Takeoff, show: showTakeoffVSpeedAnnunc, activeAnnunciation: this.props.vSpeedAnnunciationDataProvider.annunciation, isDataFailed: this.props.dataProvider.isDataFailed, isVisible: isTakeoffVSpeedAnnuncVisible }))),
                msfsSdk.FSComponent.buildComponent("div", { class: 'failed-box' })));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c;
            (_a = this.referenceRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.alertRef.getOrDefault()) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.tapeRef.getOrDefault()) === null || _c === void 0 ? void 0 : _c.destroy();
            for (const sub of this.subscriptions) {
                sub.destroy();
            }
            super.destroy();
        }
    }
    AirspeedIndicator.RESERVED_CLASSES = [
        'airspeed',
        'airspeed-reference-visible',
        'airspeed-bottom-display-visible',
        'airspeed-alert-overspeed',
        'airspeed-alert-trend-overspeed',
        'airspeed-alert-underspeed',
        'airspeed-alert-trend-underspeed',
        'data-failed'
    ];
    /**
     * A next-generation (NXi, G3000, etc) Garmin airspeed tape.
     */
    class AirspeedTape extends msfsSdk.DisplayComponent {
        /** @inheritdoc */
        constructor(props) {
            super(props);
            this.iasBoxRef = msfsSdk.FSComponent.createRef();
            this.minorTickContainerRef = msfsSdk.FSComponent.createRef();
            this.majorTickContainerRef = msfsSdk.FSComponent.createRef();
            this.labelContainerRef = msfsSdk.FSComponent.createRef();
            this.colorRangeRefs = [];
            this.manualRefSpeedBugRef = msfsSdk.FSComponent.createRef();
            this.vSpeedBugRefs = [];
            this.vSpeedOffScaleLabelRefs = [];
            this.vSpeedLegendRefs = [];
            this.rootCssClass = msfsSdk.SetSubject.create(['airspeed-tape-container']);
            this.labelTexts = [];
            this.tapeStyle = msfsSdk.ObjectSubject.create({
                position: 'absolute',
                left: '0%',
                bottom: '50%',
                width: '100%',
                height: '100%',
                transform: 'translate3d(0, 0, 0)'
            });
            this.tapeClipStyle = msfsSdk.ObjectSubject.create({
                position: 'absolute',
                left: '0%',
                bottom: '0%',
                width: '100%',
                height: '100%',
                overflow: 'hidden'
            });
            this.tapeOverflowTopStyle = msfsSdk.ObjectSubject.create({
                position: 'absolute',
                left: '0%',
                bottom: '100%',
                width: '100%',
                height: '50%'
            });
            this.trendVectorStyle = msfsSdk.ObjectSubject.create({
                display: '',
                position: 'absolute',
                bottom: '50%',
                height: '0%',
                transform: 'rotateX(0deg)',
                'transform-origin': '50% 100%'
            });
            this.vSpeedOffScaleContainerStyle = msfsSdk.ObjectSubject.create({
                display: 'flex',
                'flex-flow': 'column-reverse nowrap',
                position: 'absolute',
                bottom: '0%',
                overflow: 'hidden'
            });
            this.vSpeedLegendContainerStyle = msfsSdk.ObjectSubject.create({
                display: 'none',
                'flex-flow': 'column-reverse nowrap',
                position: 'absolute',
                bottom: '0%',
                overflow: 'hidden'
            });
            this.currentLength = msfsSdk.Subject.create(0);
            this.currentMinimum = 0;
            this.currentTranslate = msfsSdk.Subject.create(0);
            this.minimum = msfsSdk.SubscribableUtils.toSubscribable(this.props.minimum, true);
            this.maximum = msfsSdk.SubscribableUtils.toSubscribable(this.props.maximum, true);
            this.window = msfsSdk.SubscribableUtils.toSubscribable(this.props.window, true);
            this.majorTickInterval = msfsSdk.SubscribableUtils.toSubscribable(this.props.majorTickInterval, true);
            this.minorTickFactor = msfsSdk.SubscribableUtils.toSubscribable(this.props.minorTickFactor, true);
            this.options = msfsSdk.MappedSubject.create(this.minimum, this.maximum, this.window, this.majorTickInterval, this.minorTickFactor);
            this.isIasBelowScale = msfsSdk.MappedSubject.create(([iasKnots, minimum]) => {
                return iasKnots < minimum;
            }, this.props.dataProvider.iasKnots, this.minimum);
            this.isIasAboveScale = msfsSdk.MappedSubject.create(([iasKnots, maximum]) => {
                return iasKnots > maximum;
            }, this.props.dataProvider.iasKnots, this.maximum);
            this.isIasOffScale = msfsSdk.MappedSubject.create(([isIasBelowScale, isIasAboveScale]) => {
                return isIasBelowScale || isIasAboveScale;
            }, this.isIasBelowScale, this.isIasAboveScale);
            this.iasTapeValue = msfsSdk.MappedSubject.create(([iasKnots, minimum, maximum, window, isDataFailed]) => {
                return isDataFailed ? minimum + window / 2 : msfsSdk.MathUtils.clamp(iasKnots, minimum, maximum);
            }, this.props.dataProvider.iasKnots, this.minimum, this.maximum, this.window, this.props.dataProvider.isDataFailed);
            this.iasBoxValue = msfsSdk.MappedSubject.create(([iasKnots, isIasOffScale]) => {
                return isIasOffScale ? NaN : iasKnots;
            }, this.props.dataProvider.iasKnots, this.isIasOffScale);
            this.trendThreshold = msfsSdk.SubscribableUtils.toSubscribable(this.props.trendThreshold, true);
            this.showTrendVector = msfsSdk.MappedSubject.create(([iasKnot, minimum, maximum, threshold, iasTrend, isDataFailed]) => {
                return !isDataFailed && iasKnot >= minimum && iasKnot < maximum && Math.abs(iasTrend) >= threshold;
            }, this.props.dataProvider.iasKnots, this.minimum, this.maximum, this.trendThreshold, this.props.dataProvider.iasTrend, this.props.dataProvider.isDataFailed);
            this.iasTrendParams = msfsSdk.MappedSubject.create(this.props.dataProvider.iasTrend, this.window);
            this.trendVectorHeight = msfsSdk.Subject.create(0);
            this.trendVectorScale = msfsSdk.Subject.create(1);
            this.updateTapeEvent = new msfsSdk.SubEvent();
            this.updateTapeWindowEvent = new msfsSdk.SubEvent();
            this.colorRangeSubscribables = [];
            this.vSpeedBugSubscribables = [];
            this.showAirspeedData = this.props.dataProvider.isDataFailed.map(msfsSdk.SubscribableMapFunctions.not());
            this.iasTapeValue.pause();
            this.iasBoxValue.pause();
            this.showTrendVector.pause();
            this.iasTrendParams.pause();
        }
        /** @inheritdoc */
        onAfterRender() {
            this.iasTapeValue.resume();
            this.iasBoxValue.resume();
            this.iasTapeValue.sub(this.updateTape.bind(this));
            this.iasTrendParams.sub(this.updateTrendVector.bind(this));
            this.showTrendVector.resume();
            this.showTrendVector.sub(show => {
                if (show) {
                    this.trendVectorStyle.set('display', '');
                    this.iasTrendParams.resume();
                }
                else {
                    this.iasTrendParams.pause();
                    this.trendVectorStyle.set('display', 'none');
                }
            }, true);
            this.trendVectorHeight.sub(height => { this.trendVectorStyle.set('height', `${height}%`); });
            this.trendVectorScale.sub(scale => { this.trendVectorStyle.set('transform', `scale(${scale}) rotateX(0deg)`); });
            this.currentTranslate.sub(translate => {
                this.tapeStyle.set('transform', `translate3d(0, ${translate * 100}%, 0)`);
            });
            this.options.sub(this.rebuildTape.bind(this), true);
            this.isIasBelowScale.sub(isIasBelowScale => {
                this.vSpeedOffScaleContainerStyle.set('display', isIasBelowScale ? 'flex' : 'none');
            }, true);
        }
        /**
         * Calculates the absolute vertical position on the tape at which a particular airspeed is located, with `0` at the
         * top of the tape and `1` at the bottom.
         * @param iasKnots An airspeed, in knots.
         * @param clamp Whether the airspeed should be clamped to the range defined by this tape's minimum and maximum
         * representable airspeeds. Defaults to `false`.
         * @returns The absolute vertical position on the tape at which the specified airspeed is located, with `0` at the
         * top of the tape and `1` at the bottom.
         */
        calculateAbsoluteTapePosition(iasKnots, clamp = false) {
            if (clamp) {
                iasKnots = msfsSdk.MathUtils.clamp(iasKnots, this.minimum.get(), this.maximum.get());
            }
            return 1 - (iasKnots - this.currentMinimum) / this.currentLength.get();
        }
        /**
         * Calculates the vertical position on the tape window at which a particular airspeed is located, with `0` at the top
         * of the tape window and `1` at the bottom.
         * @param iasKnots An airspeed, in knots.
         * @param clamp Whether the airspeed should be clamped to the range defined by this tape's minimum and maximum
         * representable airspeeds. Defaults to `false`.
         * @returns The vertical position on the tape window at which the specified airspeed is located, with `0` at the top
         * of the tape window and `1` at the bottom.
         */
        calculateWindowTapePosition(iasKnots, clamp = false) {
            return (this.calculateAbsoluteTapePosition(iasKnots, clamp) - 1 + this.currentTranslate.get()) * this.currentLength.get() / this.window.get() + 0.5;
        }
        /**
         * Rebuilds this tape's ticks and labels.
         * @param options Scale options for this tape, as `[minimum, maximum, window, majorTickInterval, minorTickFactor]`.
         */
        rebuildTape(options) {
            const [minimum, maximum, window, majorTickInterval, minorTickFactor] = options;
            this.labelTexts.length = 0;
            this.minorTickContainerRef.instance.innerHTML = '';
            this.majorTickContainerRef.instance.innerHTML = '';
            this.labelContainerRef.instance.innerHTML = '';
            const majorTickCount = Math.ceil(window / majorTickInterval) * 2 + 1;
            const desiredRange = (majorTickCount - 1) * majorTickInterval;
            this.currentLength.set(desiredRange);
            const maxRange = maximum - minimum;
            const trueRange = Math.min(maxRange, desiredRange);
            const heightFactor = trueRange / desiredRange;
            const len = (majorTickCount - 1) * minorTickFactor;
            for (let i = 0; i <= len; i++) {
                const y = 100 - (i / len) * 100 / heightFactor;
                if (i % minorTickFactor === 0) {
                    // major tick
                    msfsSdk.FSComponent.render(msfsSdk.FSComponent.buildComponent("path", { d: `M 0 ${y} L 100 ${y}`, "vector-effect": 'non-scaling-stroke', class: 'airspeed-tape-tick airspeed-tape-tick-major' }, "."), this.majorTickContainerRef.instance);
                    const text = msfsSdk.Subject.create('0');
                    msfsSdk.FSComponent.render(msfsSdk.FSComponent.buildComponent("div", { class: 'airspeed-tape-label', style: `position: absolute; right: 0%; top: ${y}%; transform: translateY(-50%)` }, text), this.labelContainerRef.instance);
                    this.labelTexts.push(text);
                }
                else {
                    // minor tick
                    msfsSdk.FSComponent.render(msfsSdk.FSComponent.buildComponent("path", { d: `M 0 ${y} L 100 ${y}`, "vector-effect": 'non-scaling-stroke', class: 'airspeed-tape-tick airspeed-tape-tick-minor' }, "."), this.minorTickContainerRef.instance);
                }
            }
            this.tapeStyle.set('height', `${100 * desiredRange / window}%`);
            this.tapeClipStyle.set('height', `${100 * heightFactor}%`);
            this.currentMinimum = minimum;
            this.updateTapeEvent.notify(this);
            this.updateTapeLabels();
            this.updateTapeOverflow();
            this.updateTape(this.iasTapeValue.get());
        }
        /**
         * Updates the tape based on the current indicated airspeed.
         * @param iasKnots The current indicated airspeed, in knots.
         */
        updateTape(iasKnots) {
            let tapePos = this.calculateAbsoluteTapePosition(iasKnots);
            if (tapePos <= 0.25 || tapePos >= 0.75) {
                const [minimum, maximum, window, majorTickInterval] = this.options.get();
                const desiredMinimum = Math.floor((iasKnots - window) / majorTickInterval) * majorTickInterval;
                const constrainedMinimum = Math.ceil((maximum - this.currentLength.get()) / majorTickInterval) * majorTickInterval;
                const minimumToSet = Math.max(minimum, Math.min(constrainedMinimum, desiredMinimum));
                if (this.currentMinimum !== minimumToSet) {
                    this.currentMinimum = minimumToSet;
                    this.updateTapeEvent.notify(this);
                    this.updateTapeLabels();
                    this.updateTapeOverflow();
                    tapePos = msfsSdk.MathUtils.clamp(this.calculateAbsoluteTapePosition(iasKnots), 0, 1);
                }
            }
            this.currentTranslate.set(msfsSdk.MathUtils.round(1 - tapePos, 1e-3));
            this.updateTapeWindowEvent.notify(this);
        }
        /**
         * Updates this tape's labels.
         */
        updateTapeLabels() {
            const interval = this.majorTickInterval.get();
            for (let i = 0; i < this.labelTexts.length; i++) {
                this.labelTexts[i].set((this.currentMinimum + interval * i).toFixed(0));
            }
        }
        /**
         * Updates this tape's overflow regions.
         */
        updateTapeOverflow() {
            const maximumPos = this.calculateAbsoluteTapePosition(this.maximum.get());
            this.tapeOverflowTopStyle.set('bottom', `${Math.min(100, 100 - maximumPos * 100)}%`);
        }
        /**
         * Updates this tape's speed trend vector.
         * @param params Parameters for the speed trend vector, as `[iasTrend, window]`.
         */
        updateTrendVector(params) {
            const [iasTrend, window] = params;
            this.trendVectorHeight.set(msfsSdk.MathUtils.clamp(msfsSdk.MathUtils.round(Math.abs(iasTrend) / window * 100, 0.1), 0, 50));
            this.trendVectorScale.set(iasTrend < 0 ? -1 : 1);
        }
        /** @inheritdoc */
        render() {
            const { bugs: vSpeedBugs, offscale: vSpeedOffscaleLabels, legends: vSpeedLegends } = this.renderVSpeedBugs();
            return (msfsSdk.FSComponent.buildComponent("div", { class: this.rootCssClass, "data-checklist": "checklist-airspeed-tape" },
                msfsSdk.FSComponent.buildComponent("div", { class: 'airspeed-tape-border-top' }),
                msfsSdk.FSComponent.buildComponent("div", { class: 'airspeed-tape-border-bottom' }),
                msfsSdk.FSComponent.buildComponent("div", { class: 'airspeed-tape-window', style: 'overflow: hidden;' },
                    msfsSdk.FSComponent.buildComponent("div", { class: 'airspeed-tape', style: this.tapeStyle },
                        msfsSdk.FSComponent.buildComponent("div", { class: 'airspeed-tape-color-range-container', style: 'position: absolute; left: 0%; top: 0%; height: 100%; width: 100%' }, this.renderColorRanges()),
                        msfsSdk.FSComponent.buildComponent("div", { class: 'airspeed-tape-clip', style: this.tapeClipStyle },
                            msfsSdk.FSComponent.buildComponent("svg", { ref: this.minorTickContainerRef, class: 'airspeed-tape-tick-minor-container', viewBox: '0 0 100 100', preserveAspectRatio: 'none', style: 'position: absolute; top: 0; height: 100%;' }),
                            msfsSdk.FSComponent.buildComponent("svg", { ref: this.majorTickContainerRef, class: 'airspeed-tape-tick-major-container', viewBox: '0 0 100 100', preserveAspectRatio: 'none', style: 'position: absolute; top: 0; height: 100%;' }),
                            msfsSdk.FSComponent.buildComponent("div", { ref: this.labelContainerRef, class: 'airspeed-tape-label-container', style: 'position: absolute; top: 0; height: 100%; text-align: right;' })),
                        msfsSdk.FSComponent.buildComponent("div", { class: 'airspeed-tape-overflow', style: this.tapeOverflowTopStyle }),
                        msfsSdk.FSComponent.buildComponent("div", { class: 'airspeed-tape-overflow', style: 'position: absolute; left: 0; top: 100%; width: 100%; height: 50%;' })),
                    msfsSdk.FSComponent.buildComponent("div", { class: 'airspeed-vspeed-legend-container', style: this.vSpeedLegendContainerStyle }, vSpeedLegends)),
                msfsSdk.FSComponent.buildComponent("div", { class: 'airspeed-trend', style: this.trendVectorStyle }),
                msfsSdk.FSComponent.buildComponent("div", { class: 'airspeed-vspeed-offscale-container', style: this.vSpeedOffScaleContainerStyle }, vSpeedOffscaleLabels),
                msfsSdk.FSComponent.buildComponent("div", { class: 'airspeed-vspeed-bug-container', style: 'position: absolute; top: 0; height: 100%; overflow: hidden;' }, vSpeedBugs),
                msfsSdk.FSComponent.buildComponent(AirspeedIasDisplayBox, { ref: this.iasBoxRef, show: this.showAirspeedData, ias: this.iasBoxValue }),
                msfsSdk.FSComponent.buildComponent("div", { class: 'airspeed-refspeed-bug-container', style: 'position: absolute; left: 0; top: 0; width: 100%; height: 100%; overflow: hidden;' },
                    msfsSdk.FSComponent.buildComponent(ReferenceSpeedBug, { ref: this.manualRefSpeedBugRef, show: this.showAirspeedData, referenceIas: this.props.dataProvider.referenceIas, referenceMach: this.props.dataProvider.referenceMach, referenceIsManual: this.props.dataProvider.referenceIsManual, machToKias: this.props.dataProvider.machToKias, isAirspeedHoldActive: this.props.dataProvider.isAirspeedHoldActive, updateEvent: this.updateTapeWindowEvent, getPosition: this.calculateWindowTapePosition.bind(this) }))));
        }
        /**
         * Renders this tape's color ranges.
         * @returns This tape's color ranges, as an array of VNodes.
         */
        renderColorRanges() {
            const getPosition = this.calculateAbsoluteTapePosition.bind(this);
            const ranges = [];
            for (const definition of this.props.colorRanges) {
                const minimum = definition.minimum(this.props.dataProvider);
                const maximum = definition.maximum(this.props.dataProvider);
                const ref = msfsSdk.FSComponent.createRef();
                this.colorRangeRefs.push(ref);
                if (msfsSdk.SubscribableUtils.isSubscribable(minimum)) {
                    this.colorRangeSubscribables.push(minimum);
                }
                if (msfsSdk.SubscribableUtils.isSubscribable(maximum)) {
                    this.colorRangeSubscribables.push(maximum);
                }
                ranges.push(msfsSdk.FSComponent.buildComponent(AirspeedColorRange, { ref: ref, width: definition.width, color: definition.color, show: this.showAirspeedData, minimum: minimum, maximum: maximum, updateEvent: this.updateTapeEvent, getPosition: getPosition }));
            }
            return ranges;
        }
        /**
         * Renders this tape's reference V-speed bugs and off-scale labels.
         * @returns This tape's reference V-speed bugs and off-scale labels, as arrays of VNodes.
         */
        renderVSpeedBugs() {
            var _a, _b;
            const getPosition = this.calculateWindowTapePosition.bind(this);
            const bugs = [];
            const offscale = [];
            const legends = [];
            const legendShow = [];
            const options = this.props.vSpeedBugOptions;
            const allowZeroValue = (_a = options.allowZeroValue) !== null && _a !== void 0 ? _a : false;
            for (const def of options.vSpeedBugDefinitions) {
                const showSetting = options.vSpeedSettingManager.tryGetSetting(`vSpeedShow_${def.name}`);
                if (showSetting !== undefined) {
                    const bugRef = msfsSdk.FSComponent.createRef();
                    this.vSpeedBugRefs.push(bugRef);
                    const activeValue = VSpeedUserSettingUtils.activeValue(def.name, options.vSpeedSettingManager, true, allowZeroValue);
                    const isFmsValueActive = VSpeedUserSettingUtils.isFmsValueActive(def.name, options.vSpeedSettingManager);
                    const isFmsConfigMiscompare = (_b = options.vSpeedSettingManager.tryGetSetting(`vSpeedFmsConfigMiscompare_${def.name}`)) !== null && _b !== void 0 ? _b : msfsSdk.Subject.create(false);
                    const showBug = msfsSdk.MappedSubject.create(([showAirspeedData, isIasOffScale, show, value, isConfigMiscompare]) => {
                        return showAirspeedData && !isIasOffScale && show && (allowZeroValue ? value >= 0 : value > 0) && !isConfigMiscompare;
                    }, this.showAirspeedData, this.isIasOffScale, showSetting, activeValue, isFmsConfigMiscompare);
                    this.vSpeedBugSubscribables.push(activeValue);
                    this.vSpeedBugSubscribables.push(isFmsValueActive);
                    this.vSpeedBugSubscribables.push(showBug);
                    bugs.push(msfsSdk.FSComponent.buildComponent(VSpeedBug, { ref: bugRef, value: activeValue, isFmsValueActive: isFmsValueActive, label: def.label, show: showBug, updateEvent: this.updateTapeWindowEvent, getPosition: getPosition }));
                    if (def.showOffscale) {
                        const labelRef = msfsSdk.FSComponent.createRef();
                        this.vSpeedOffScaleLabelRefs.push(labelRef);
                        const showLabel = msfsSdk.MappedSubject.create(([showAirspeedData, show, value]) => showAirspeedData && show && (allowZeroValue ? value >= 0 : value > 0), this.showAirspeedData, showSetting, activeValue);
                        this.vSpeedBugSubscribables.push(showLabel);
                        offscale.push(msfsSdk.FSComponent.buildComponent(VSpeedOffScaleLabel, { ref: labelRef, value: activeValue, isFmsValueActive: isFmsValueActive, isFmsConfigMiscompare: isFmsConfigMiscompare, label: def.label, show: showLabel }));
                    }
                    if (def.showLegend) {
                        const legendRef = msfsSdk.FSComponent.createRef();
                        this.vSpeedLegendRefs.push(legendRef);
                        legendShow.push(showBug);
                        legends.push(msfsSdk.FSComponent.buildComponent(VSpeedLegend, { ref: legendRef, value: activeValue, isFmsValueActive: isFmsValueActive, isFmsConfigMiscompare: isFmsConfigMiscompare, label: def.label, show: showBug }));
                    }
                }
            }
            if (legendShow.length > 0) {
                // Initialize logic to show/hide the V-speed legend container. The container should be visible if and only if IAS
                // is not below scale and at least one legend is visible.
                msfsSdk.MappedSubject.create(inputs => {
                    if (inputs[0]) { // IAS is below scale
                        return false;
                    }
                    return inputs.includes(true, 1);
                }, this.isIasBelowScale, ...legendShow).sub(show => {
                    this.vSpeedLegendContainerStyle.set('display', show ? 'flex' : 'none');
                }, true);
            }
            return { bugs, offscale, legends };
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c, _d, _e, _f;
            (_a = this.iasBoxRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
            for (const ref of this.colorRangeRefs) {
                (_b = ref.getOrDefault()) === null || _b === void 0 ? void 0 : _b.destroy();
            }
            (_c = this.manualRefSpeedBugRef.getOrDefault()) === null || _c === void 0 ? void 0 : _c.destroy();
            for (const ref of this.vSpeedBugRefs) {
                (_d = ref.getOrDefault()) === null || _d === void 0 ? void 0 : _d.destroy();
            }
            for (const ref of this.vSpeedOffScaleLabelRefs) {
                (_e = ref.getOrDefault()) === null || _e === void 0 ? void 0 : _e.destroy();
            }
            for (const ref of this.vSpeedLegendRefs) {
                (_f = ref.getOrDefault()) === null || _f === void 0 ? void 0 : _f.destroy();
            }
            this.options.destroy();
            this.isIasBelowScale.destroy();
            this.isIasAboveScale.destroy();
            this.isIasOffScale.destroy();
            this.iasTapeValue.destroy();
            this.iasBoxValue.destroy();
            this.iasTrendParams.destroy();
            for (const subscribable of this.colorRangeSubscribables) {
                subscribable.destroy();
            }
            for (const subscribable of this.vSpeedBugSubscribables) {
                subscribable.destroy();
            }
            super.destroy();
        }
    }
    /**
     * An indicated airspeed display box for a next-generation (NXi, G3000, etc) Garmin airspeed tape.
     */
    class AirspeedIasDisplayBox extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.scrollerRefs = [];
            this.rootStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.ias = this.props.ias.map(msfsSdk.SubscribableMapFunctions.identity()).pause();
        }
        /** @inheritdoc */
        onAfterRender() {
            this.showSub = this.props.show.sub(show => {
                if (show) {
                    this.rootStyle.set('display', '');
                    this.ias.resume();
                }
                else {
                    this.rootStyle.set('display', 'none');
                    this.ias.pause();
                }
            }, true);
        }
        /** @inheritdoc */
        render() {
            const onesScrollerRef = msfsSdk.FSComponent.createRef();
            const tensScrollerRef = msfsSdk.FSComponent.createRef();
            const hundredsScrollerRef = msfsSdk.FSComponent.createRef();
            this.scrollerRefs.push(onesScrollerRef, tensScrollerRef, hundredsScrollerRef);
            return (msfsSdk.FSComponent.buildComponent("div", { class: 'airspeed-ias-box', style: this.rootStyle },
                msfsSdk.FSComponent.buildComponent("svg", { viewBox: "0 0 90 70", class: 'airspeed-ias-box-bg', preserveAspectRatio: 'none' },
                    msfsSdk.FSComponent.buildComponent("path", { "vector-effect": 'non-scaling-stroke', d: 'M 86 35 l -11 -6 l 0 -26 c 0 -1 -1 -2 -2 -2 l -19 0 c -1 0 -2 1 -2 2 l 0 11 l -44 0 c -1 0 -2 1 -2 2 l 0 38 c 0 1 1 2 2 2 l 44 0 l 0 11 c 0 1 1 2 2 2 l 19 0 c 1 0 2 -1 2 -2 l 0 -26 l 11 -6 z' })),
                msfsSdk.FSComponent.buildComponent("div", { class: 'airspeed-ias-box-scrollers', style: 'position: absolute; left: 6.7%; top: 1.5%; width: 76.6%; height: 97%;' },
                    msfsSdk.FSComponent.buildComponent("div", { class: 'airspeed-ias-box-digit-container airspeed-ias-box-hundreds' },
                        msfsSdk.FSComponent.buildComponent("div", { class: 'airspeed-ias-box-digit-bg' }),
                        msfsSdk.FSComponent.buildComponent(msfsSdk.DigitScroller, { ref: hundredsScrollerRef, value: this.ias, base: 10, factor: 100, scrollThreshold: 99, renderDigit: (digit) => digit === 0 ? ' ' : (Math.abs(digit) % 10).toString() })),
                    msfsSdk.FSComponent.buildComponent("div", { class: 'airspeed-ias-box-digit-container airspeed-ias-box-tens' },
                        msfsSdk.FSComponent.buildComponent("div", { class: 'airspeed-ias-box-digit-bg' }),
                        msfsSdk.FSComponent.buildComponent(msfsSdk.DigitScroller, { ref: tensScrollerRef, value: this.ias, base: 10, factor: 10, scrollThreshold: 9 })),
                    msfsSdk.FSComponent.buildComponent("div", { class: 'airspeed-ias-box-digit-container airspeed-ias-box-ones' },
                        msfsSdk.FSComponent.buildComponent("div", { class: 'airspeed-ias-box-digit-bg' }),
                        msfsSdk.FSComponent.buildComponent(msfsSdk.DigitScroller, { ref: onesScrollerRef, value: this.ias, base: 10, factor: 1 }),
                        msfsSdk.FSComponent.buildComponent("div", { class: 'airspeed-ias-box-scroller-mask' })))));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b;
            for (const ref of this.scrollerRefs) {
                (_a = ref.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
            }
            this.ias.destroy();
            (_b = this.showSub) === null || _b === void 0 ? void 0 : _b.destroy();
            super.destroy();
        }
    }
    /**
     * A color range for an airspeed indicator tape.
     */
    class AirspeedColorRange extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.rootStyle = msfsSdk.ObjectSubject.create({
                display: '',
                position: 'absolute',
                top: '0%',
                height: this.props.color === exports.AirspeedIndicatorColorRangeColor.BarberPole ? '0%' : '100%',
                transform: `translate3d(0px, 0px, 0px) scaleY(${this.props.color === exports.AirspeedIndicatorColorRangeColor.BarberPole ? 1 : 0})`,
                'transform-origin': '50% 0%'
            });
            this.minimum = msfsSdk.SubscribableUtils.toSubscribable(this.props.minimum, true);
            this.maximum = msfsSdk.SubscribableUtils.toSubscribable(this.props.maximum, true);
            this.minPos = 0;
            this.maxPos = 0;
            this.setStyles = this.props.color === exports.AirspeedIndicatorColorRangeColor.BarberPole
                ? this.setStylesTopHeight.bind(this)
                : this.setStylesTransform.bind(this);
        }
        /** @inheritdoc */
        onAfterRender() {
            const handler = this.updatePosition.bind(this);
            const minSub = this.minimumSub = this.minimum.sub(handler, false, true);
            const maxSub = this.maximumSub = this.maximum.sub(handler, false, true);
            const updateSub = this.updateEventSub = this.props.updateEvent.on(handler, true);
            this.showSub = this.props.show.sub(show => {
                if (show) {
                    minSub.resume();
                    maxSub.resume();
                    updateSub.resume();
                    this.updatePosition();
                }
                else {
                    minSub.pause();
                    maxSub.pause();
                    updateSub.pause();
                    this.rootStyle.set('display', 'none');
                }
            }, true);
            this.updatePosition();
        }
        /**
         * Updates this color range's start and end positions on its parent airspeed tape.
         */
        updatePosition() {
            const minimum = this.minimum.get();
            const maximum = this.maximum.get();
            if (isNaN(maximum - minimum)) {
                this.rootStyle.set('display', 'none');
                return;
            }
            const minPos = msfsSdk.MathUtils.clamp(msfsSdk.MathUtils.round(this.props.getPosition(minimum, true), 0.001), 0, 1);
            const maxPos = msfsSdk.MathUtils.clamp(msfsSdk.MathUtils.round(this.props.getPosition(maximum, true), 0.001), 0, 1);
            if (minPos <= maxPos) {
                this.rootStyle.set('display', 'none');
            }
            else {
                this.rootStyle.set('display', '');
                if (minPos !== this.minPos || maxPos !== this.maxPos) {
                    this.setStyles(minPos, maxPos);
                    this.minPos = minPos;
                    this.maxPos = maxPos;
                }
            }
        }
        /**
         * Positions this color range using the top and height styles.
         * @param minPos The position of this color range's minimum airspeed on its parent tape.
         * @param maxPos The position of this color range's maximum airspeed on its parent tape.
         */
        setStylesTopHeight(minPos, maxPos) {
            this.rootStyle.set('top', `${maxPos * 100}%`);
            this.rootStyle.set('height', `${(minPos - maxPos) * 100}%`);
        }
        /**
         * Positions this color range using the transform style.
         * @param minPos The position of this color range's minimum airspeed on its parent tape.
         * @param maxPos The position of this color range's maximum airspeed on its parent tape.
         */
        setStylesTransform(minPos, maxPos) {
            const translate = maxPos * 100;
            const scale = minPos - maxPos;
            this.rootStyle.set('transform', `translate3d(0px, ${translate}%, 0px) scaleY(${scale})`);
        }
        /** @inheritdoc */
        render() {
            const widthClass = this.props.width === exports.AirspeedIndicatorColorRangeWidth.Full ? 'airspeed-tape-color-range-full' : 'airspeed-tape-color-range-half';
            const colorClass = AirspeedColorRange.COLOR_CLASS[this.props.color];
            return (msfsSdk.FSComponent.buildComponent("div", { class: `airspeed-tape-color-range ${widthClass} ${colorClass}`, style: this.rootStyle }));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c, _d;
            (_a = this.showSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.minimumSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.maximumSub) === null || _c === void 0 ? void 0 : _c.destroy();
            (_d = this.updateEventSub) === null || _d === void 0 ? void 0 : _d.destroy();
            super.destroy();
        }
    }
    AirspeedColorRange.COLOR_CLASS = {
        [exports.AirspeedIndicatorColorRangeColor.Red]: 'airspeed-tape-color-range-red',
        [exports.AirspeedIndicatorColorRangeColor.Yellow]: 'airspeed-tape-color-range-yellow',
        [exports.AirspeedIndicatorColorRangeColor.White]: 'airspeed-tape-color-range-white',
        [exports.AirspeedIndicatorColorRangeColor.Green]: 'airspeed-tape-color-range-green',
        [exports.AirspeedIndicatorColorRangeColor.BarberPole]: 'airspeed-tape-color-range-barber-pole',
    };
    /**
     * A speed bug for a next-generation (NXi, G3000, etc) Garmin airspeed tape.
     */
    class SpeedBug extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.style = msfsSdk.ObjectSubject.create({
                display: '',
                position: 'absolute',
                top: '50%',
                transform: 'translate3d(0, -50%, 0)'
            });
            this.position = msfsSdk.Subject.create(0);
            this.speedKnotsRounded = this.props.speedKnots.map(msfsSdk.SubscribableMapFunctions.withPrecision(1)).pause();
        }
        /** @inheritdoc */
        onAfterRender() {
            const updateHandler = this.updatePosition.bind(this);
            const speedKnotsRoundedSub = this.speedKnotsRounded.sub(updateHandler, false, true);
            const updateSub = this.updateEventSub = this.props.updateEvent.on(updateHandler, true);
            this.position.sub(translate => {
                this.style.set('top', `${translate}%`);
            });
            this.showSub = this.props.show.sub(show => {
                if (show) {
                    this.speedKnotsRounded.resume();
                    speedKnotsRoundedSub.resume();
                    updateSub.resume();
                    this.updatePosition();
                    this.style.set('display', '');
                }
                else {
                    this.speedKnotsRounded.pause();
                    speedKnotsRoundedSub.pause();
                    updateSub.pause();
                    this.style.set('display', 'none');
                }
            }, true);
        }
        /**
         * Updates this speed bug's position on its parent airspeed tape window.
         */
        updatePosition() {
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            const pos = this.props.getPosition(this.speedKnotsRounded.get());
            this.position.set(msfsSdk.MathUtils.round(pos * 100, 0.1));
        }
        /** @inheritdoc */
        render() {
            var _a;
            let cssClass;
            if (typeof this.props.class === 'object') {
                cssClass = msfsSdk.SetSubject.create(['airspeed-speed-bug']);
                this.cssClassSub = msfsSdk.FSComponent.bindCssClassSet(cssClass, this.props.class, ['airspeed-speed-bug']);
            }
            else {
                cssClass = `airspeed-speed-bug ${(_a = this.props.class) !== null && _a !== void 0 ? _a : ''}`;
            }
            return (msfsSdk.FSComponent.buildComponent("div", { class: cssClass, style: this.style }, this.props.children));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c;
            this.speedKnotsRounded.destroy();
            (_a = this.cssClassSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.showSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.updateEventSub) === null || _c === void 0 ? void 0 : _c.destroy();
            super.destroy();
        }
    }
    /**
     * A reference speed bug.
     */
    class ReferenceSpeedBug extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.bugRef = msfsSdk.FSComponent.createRef();
            this.bugCssClass = msfsSdk.SetSubject.create(['airspeed-refspeed-bug']);
            this.manualStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.fmsStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.state = msfsSdk.MappedSubject.create(this.props.referenceIas, this.props.referenceMach, this.props.referenceIsManual, this.props.machToKias, this.props.isAirspeedHoldActive);
            this.show = msfsSdk.Subject.create(false);
            this.speedKnots = msfsSdk.Subject.create(0);
        }
        /** @inheritdoc */
        onAfterRender() {
            const stateSub = this.state.sub(this.update.bind(this), false, true);
            this.showSub = this.props.show.sub(show => {
                if (show) {
                    stateSub.resume(true);
                }
                else {
                    stateSub.pause();
                    this.show.set(false);
                }
            }, true);
        }
        /**
         * Updates this speed bug.
         * @param state The current reference airspeed state.
         */
        update(state) {
            const [ias, mach, referenceIsManual, machToKias, isAirspeedHoldActive] = state;
            if (isAirspeedHoldActive && (ias !== null || mach !== null)) {
                this.bugCssClass.toggle('airspeed-refspeed-bug-manual', referenceIsManual);
                this.bugCssClass.toggle('airspeed-refspeed-bug-fms', !referenceIsManual);
                if (referenceIsManual) {
                    this.fmsStyle.set('display', 'none');
                    this.manualStyle.set('display', '');
                }
                else {
                    this.manualStyle.set('display', 'none');
                    this.fmsStyle.set('display', '');
                }
                if (ias !== null) {
                    this.speedKnots.set(ias);
                }
                else {
                    this.speedKnots.set(mach * machToKias);
                }
                this.show.set(true);
            }
            else {
                this.show.set(false);
            }
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent(SpeedBug, { ref: this.bugRef, speedKnots: this.speedKnots, show: this.show, updateEvent: this.props.updateEvent, getPosition: (iasKnots) => msfsSdk.MathUtils.clamp(this.props.getPosition(iasKnots, true), 0, 1), class: this.bugCssClass },
                msfsSdk.FSComponent.buildComponent("svg", { viewBox: '0 0 100 100', preserveAspectRatio: 'none', class: 'airspeed-refspeed-bug-icon airspeed-refspeed-bug-icon-manual', style: this.manualStyle },
                    msfsSdk.FSComponent.buildComponent("path", { d: "M 5 5 h 90 v 90 h -90 v -30 L 55 50 L 5 30 Z", "vector-effect": 'non-scaling-stroke' })),
                msfsSdk.FSComponent.buildComponent("svg", { viewBox: '0 0 100 100', preserveAspectRatio: 'none', class: 'airspeed-refspeed-bug-icon airspeed-refspeed-bug-icon-fms', style: this.fmsStyle },
                    msfsSdk.FSComponent.buildComponent("path", { d: "M 5 5 v 90 l 90 -45 Z", "vector-effect": 'non-scaling-stroke' }))));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b;
            (_a = this.bugRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
            this.state.destroy();
            (_b = this.showSub) === null || _b === void 0 ? void 0 : _b.destroy();
            super.destroy();
        }
    }
    /**
     * An icon for a reference V-speed bug.
     */
    class VSpeedBugIcon extends msfsSdk.DisplayComponent {
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: 'airspeed-vspeed-bug-icon' },
                msfsSdk.FSComponent.buildComponent("svg", { class: 'airspeed-vspeed-bug-icon-arrow', viewBox: '0 0 100 100', preserveAspectRatio: 'none' },
                    msfsSdk.FSComponent.buildComponent("path", { d: 'M 0 50 L 100 0 L 100 100 Z', "vector-effect": 'non-scaling-stroke' })),
                msfsSdk.FSComponent.buildComponent("div", { class: 'airspeed-vspeed-bug-icon-label' }, this.props.children)));
        }
    }
    /**
     * A reference V-speed bug.
     */
    class VSpeedBug extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.bugRef = msfsSdk.FSComponent.createRef();
            this.cssClass = msfsSdk.SetSubject.create(['airspeed-vspeed-bug']);
        }
        /** @inheritdoc */
        onAfterRender() {
            const fmsSub = this.fmsSub = this.props.isFmsValueActive.sub(isFmsValueActive => {
                this.cssClass.toggle('airspeed-vspeed-fms', isFmsValueActive);
            }, false, true);
            this.showSub = this.props.show.sub(show => {
                if (show) {
                    fmsSub.resume(true);
                }
                else {
                    fmsSub.pause();
                }
            }, true);
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent(SpeedBug, { ref: this.bugRef, speedKnots: this.props.value, show: this.props.show, updateEvent: this.props.updateEvent, getPosition: (iasKnots) => msfsSdk.MathUtils.clamp(this.props.getPosition(iasKnots, false), -0.5, 1.5), class: this.cssClass },
                msfsSdk.FSComponent.buildComponent(VSpeedBugIcon, null, this.props.label)));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c;
            (_a = this.bugRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.showSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.fmsSub) === null || _c === void 0 ? void 0 : _c.destroy();
            super.destroy();
        }
    }
    /**
     * A reference V-speed label displayed when the airspeed tape is off-scale.
     */
    class VSpeedOffScaleLabel extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.rootCssClass = msfsSdk.SetSubject.create(['airspeed-vspeed-offscale-label']);
            this.style = msfsSdk.ObjectSubject.create({
                display: '',
                order: '0'
            });
            this.valueText = msfsSdk.MappedSubject.create(([value, isFmsValueActive, miscompare]) => isFmsValueActive && miscompare ? '–––' : value.toFixed(0), this.props.value, this.props.isFmsValueActive, this.props.isFmsConfigMiscompare).pause();
        }
        /** @inheritdoc */
        onAfterRender() {
            const fmsSub = this.fmsSub = this.props.isFmsValueActive.sub(isFmsValueActive => {
                this.rootCssClass.toggle('airspeed-vspeed-fms', isFmsValueActive);
            }, false, true);
            const configSub = this.configSub = this.props.isFmsConfigMiscompare.sub(miscompare => {
                this.rootCssClass.toggle('airspeed-vspeed-fms-config-miscompare', miscompare);
            }, false, true);
            this.valueSub = this.props.value.sub(value => {
                this.style.set('order', value.toFixed(0));
            }, true);
            this.showSub = this.props.show.sub(show => {
                if (show) {
                    this.valueText.resume();
                    fmsSub.resume(true);
                    configSub.resume(true);
                    this.style.set('display', '');
                }
                else {
                    this.valueText.pause();
                    fmsSub.pause();
                    configSub.pause();
                    this.style.set('display', 'none');
                }
            }, true);
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: this.rootCssClass, style: this.style },
                msfsSdk.FSComponent.buildComponent("div", { class: 'airspeed-vspeed-offscale-label-value' }, this.valueText),
                msfsSdk.FSComponent.buildComponent(VSpeedBugIcon, null, this.props.label)));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c, _d;
            this.valueText.destroy();
            (_a = this.showSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.valueSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.fmsSub) === null || _c === void 0 ? void 0 : _c.destroy();
            (_d = this.configSub) === null || _d === void 0 ? void 0 : _d.destroy();
            super.destroy();
        }
    }
    /**
     * A reference V-speed legend.
     */
    class VSpeedLegend extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.rootCssClass = msfsSdk.SetSubject.create(['airspeed-vspeed-legend']);
            this.style = msfsSdk.ObjectSubject.create({
                display: '',
                order: '0'
            });
            this.valueText = msfsSdk.MappedSubject.create(([value, isFmsValueActive, miscompare]) => isFmsValueActive && miscompare ? '–––' : value.toFixed(0), this.props.value, this.props.isFmsValueActive, this.props.isFmsConfigMiscompare).pause();
        }
        /** @inheritdoc */
        onAfterRender() {
            const fmsSub = this.fmsSub = this.props.isFmsValueActive.sub(isFmsValueActive => {
                this.rootCssClass.toggle('airspeed-vspeed-fms', isFmsValueActive);
            }, false, true);
            const configSub = this.configSub = this.props.isFmsConfigMiscompare.sub(miscompare => {
                this.rootCssClass.toggle('airspeed-vspeed-fms-config-miscompare', miscompare);
            }, false, true);
            this.valueSub = this.props.value.sub(value => {
                this.style.set('order', value.toFixed(0));
            }, true);
            this.showSub = this.props.show.sub(show => {
                if (show) {
                    this.valueText.resume();
                    fmsSub.resume(true);
                    configSub.resume(true);
                    this.style.set('display', '');
                }
                else {
                    this.valueText.pause();
                    fmsSub.pause();
                    configSub.pause();
                    this.style.set('display', 'none');
                }
            }, true);
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: this.rootCssClass, style: this.style },
                msfsSdk.FSComponent.buildComponent("div", { class: 'airspeed-vspeed-legend-name' }, this.props.label),
                msfsSdk.FSComponent.buildComponent("div", { class: 'airspeed-vspeed-legend-value' }, this.valueText)));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c, _d;
            this.valueText.destroy();
            (_a = this.showSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.valueSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.fmsSub) === null || _c === void 0 ? void 0 : _c.destroy();
            (_d = this.configSub) === null || _d === void 0 ? void 0 : _d.destroy();
            super.destroy();
        }
    }
    /**
     * A display for an airspeed reference value.
     */
    class AirspeedReferenceDisplay extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.rootStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.manualStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.fmsStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.iasStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.machStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.rootCssClass = msfsSdk.SetSubject.create(['airspeed-refspeed-container']);
            this.referenceIas = msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.KNOT.createNumber(0));
            this.referenceMach = msfsSdk.Subject.create(0);
            this.state = msfsSdk.MappedSubject.create(this.props.referenceIas, this.props.referenceMach, this.props.referenceIsManual, this.props.isAirspeedHoldActive);
        }
        /** @inheritdoc */
        onAfterRender() {
            const stateSub = this.state.sub(this.update.bind(this), false, true);
            this.showSub = this.props.show.sub(show => {
                if (show) {
                    stateSub.resume(true);
                }
                else {
                    stateSub.pause();
                    this.rootStyle.set('display', 'none');
                    this.props.isVisible.set(false);
                }
            }, true);
        }
        /**
         * Updates this display.
         * @param state The current reference airspeed state.
         */
        update(state) {
            const [ias, mach, referenceIsManual, isAirspeedHoldActive] = state;
            if (isAirspeedHoldActive && (ias !== null || mach !== null)) {
                this.rootCssClass.toggle('airspeed-refspeed-container-manual', referenceIsManual);
                this.rootCssClass.toggle('airspeed-refspeed-container-fms', !referenceIsManual);
                if (referenceIsManual) {
                    this.fmsStyle.set('display', 'none');
                    this.manualStyle.set('display', '');
                }
                else {
                    this.manualStyle.set('display', 'none');
                    this.fmsStyle.set('display', '');
                }
                if (ias !== null) {
                    this.iasStyle.set('display', '');
                    this.machStyle.set('display', 'none');
                    this.referenceIas.set(Math.round(ias));
                }
                else {
                    this.machStyle.set('display', '');
                    this.iasStyle.set('display', 'none');
                    this.referenceMach.set(msfsSdk.MathUtils.round(mach, 0.001));
                }
                this.rootStyle.set('display', '');
                this.props.isVisible.set(true);
            }
            else {
                this.rootStyle.set('display', 'none');
                this.props.isVisible.set(false);
            }
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: this.rootCssClass, style: this.rootStyle },
                msfsSdk.FSComponent.buildComponent("svg", { class: 'airspeed-refspeed-icon airspeed-refspeed-icon-manual', viewBox: '0 0 100 100', preserveAspectRatio: 'none', style: this.manualStyle },
                    msfsSdk.FSComponent.buildComponent("path", { d: 'M 5 5 h 90 v 90 h -90 v -30 L 55 50 L 5 30 Z', "vector-effect": 'non-scaling-stroke' })),
                msfsSdk.FSComponent.buildComponent("svg", { class: 'airspeed-refspeed-icon airspeed-refspeed-icon-fms', viewBox: '0 0 100 100', preserveAspectRatio: 'none', style: this.fmsStyle },
                    msfsSdk.FSComponent.buildComponent("path", { d: 'M 5 5 v 90 l 90 -45 Z', "vector-effect": 'non-scaling-stroke' })),
                msfsSdk.FSComponent.buildComponent("div", { class: 'airspeed-refspeed-text' },
                    msfsSdk.FSComponent.buildComponent("div", { class: 'airspeed-refspeed-ias', style: this.iasStyle },
                        msfsSdk.FSComponent.buildComponent(NumberUnitDisplay, { value: this.referenceIas, displayUnit: null, formatter: msfsSdk.NumberFormatter.create({ precision: 1 }) })),
                    msfsSdk.FSComponent.buildComponent("div", { class: 'airspeed-refspeed-mach', style: this.machStyle },
                        "M ",
                        this.referenceMach.map(msfsSdk.NumberFormatter.create({ pad: 0, precision: 0.001 }))))));
        }
        /** @inheritdoc */
        destroy() {
            var _a;
            super.destroy();
            this.state.destroy();
            (_a = this.showSub) === null || _a === void 0 ? void 0 : _a.destroy();
        }
    }
    /**
     * A next-generation (NXi, G3000, etc) Garmin airspeed alert annunciation.
     */
    class AirspeedProtectionAnnunciation extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.rootStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.text = msfsSdk.Subject.create('');
            this.protectionState = msfsSdk.MappedSubject.create(this.props.isOverspeedProtectionActive, this.props.isUnderspeedProtectionActive);
        }
        /** @inheritdoc */
        onAfterRender() {
            const stateSub = this.protectionState.sub(([isOverspeedActive, isUnderspeedActive]) => {
                if (isUnderspeedActive) {
                    this.text.set('MINSPD');
                    this.rootStyle.set('display', '');
                }
                else if (isOverspeedActive) {
                    this.text.set('MAXSPD');
                    this.rootStyle.set('display', '');
                }
                else {
                    this.rootStyle.set('display', 'none');
                }
            }, false, true);
            this.showSub = this.props.show.sub(show => {
                if (show) {
                    stateSub.resume(true);
                }
                else {
                    stateSub.pause();
                    this.rootStyle.set('display', 'none');
                }
            }, true);
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: 'airspeed-protection-container', style: this.rootStyle },
                msfsSdk.FSComponent.buildComponent("div", { class: 'airspeed-protection-text' }, this.text)));
        }
        /** @inheritdoc */
        destroy() {
            var _a;
            super.destroy();
            this.protectionState.destroy();
            (_a = this.showSub) === null || _a === void 0 ? void 0 : _a.destroy();
        }
    }
    /**
     * A true airspeed display for an airspeed indicator.
     */
    class AirspeedTasDisplay extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.rootStyle = msfsSdk.ObjectSubject.create({
                display: ''
            });
            this.tas = msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.KNOT.createNumber(0));
        }
        /** @inheritdoc */
        onAfterRender() {
            const tasSub = this.tasSub = this.props.tasKnots.sub(this.update.bind(this), false, true);
            const isDataFailedSub = this.isDataFailedSub = this.props.isDataFailed.sub(isFailed => {
                if (isFailed) {
                    tasSub.pause();
                    this.rootStyle.set('display', 'none');
                    this.props.isVisible.set(false);
                }
                else {
                    this.rootStyle.set('display', '');
                    this.props.isVisible.set(true);
                    tasSub.resume(true);
                }
            }, false, true);
            this.showSub = this.props.show.sub(show => {
                if (show) {
                    isDataFailedSub.resume(true);
                }
                else {
                    isDataFailedSub.pause();
                    tasSub.pause();
                    this.rootStyle.set('display', 'none');
                    this.props.isVisible.set(false);
                }
            }, true);
        }
        /**
         * Updates this display.
         * @param tasKnots The current true airspeed, in knots.
         */
        update(tasKnots) {
            this.tas.set(msfsSdk.MathUtils.round(tasKnots, 1));
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: 'airspeed-bottom-display airspeed-tas-display', style: this.rootStyle },
                msfsSdk.FSComponent.buildComponent("div", { class: 'airspeed-tas-display-title' }, "TAS"),
                msfsSdk.FSComponent.buildComponent(NumberUnitDisplay, { value: this.tas, displayUnit: null, formatter: msfsSdk.NumberFormatter.create({ precision: 1 }), class: 'airspeed-tas-display-value' })));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c;
            (_a = this.showSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.tasSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.isDataFailedSub) === null || _c === void 0 ? void 0 : _c.destroy();
            super.destroy();
        }
    }
    /**
     * A mach display for an airspeed indicator.
     */
    class AirspeedMachDisplay extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.rootStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.roundedMach = msfsSdk.Subject.create(0);
            this.state = msfsSdk.MappedSubject.create(this.props.mach, msfsSdk.SubscribableUtils.toSubscribable(this.props.threshold, true));
        }
        /** @inheritdoc */
        onAfterRender() {
            const stateSub = this.state.sub(this.update.bind(this), false, true);
            const isDataFailedSub = this.isDataFailedSub = this.props.isDataFailed.sub(isFailed => {
                if (isFailed) {
                    stateSub.pause();
                    this.rootStyle.set('display', 'none');
                    this.props.isVisible.set(false);
                }
                else {
                    stateSub.resume(true);
                }
            }, false, true);
            this.showSub = this.props.show.sub(show => {
                if (show) {
                    isDataFailedSub.resume(true);
                }
                else {
                    isDataFailedSub.pause();
                    stateSub.pause();
                    this.rootStyle.set('display', 'none');
                    this.props.isVisible.set(false);
                }
            }, true);
        }
        /**
         * Updates this display.
         * @param state The state of this display.
         */
        update(state) {
            const [mach, threshold] = state;
            if (mach < threshold) {
                this.rootStyle.set('display', 'none');
                this.props.isVisible.set(false);
            }
            else {
                this.roundedMach.set(msfsSdk.MathUtils.round(mach, 0.001));
                this.rootStyle.set('display', '');
                this.props.isVisible.set(true);
            }
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: 'airspeed-bottom-display airspeed-mach-display', style: this.rootStyle },
                msfsSdk.FSComponent.buildComponent("div", { class: 'airspeed-mach-display-value' },
                    "M ",
                    this.roundedMach.map(msfsSdk.NumberFormatter.create({ precision: 0.001, pad: 0 })))));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b;
            this.state.destroy();
            (_a = this.showSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.isDataFailedSub) === null || _b === void 0 ? void 0 : _b.destroy();
            super.destroy();
        }
    }
    /**
     * A V-speed annunciation an airspeed indicator.
     */
    class AirspeedVSpeedAnnunciation extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.rootStyle = msfsSdk.ObjectSubject.create({
                display: ''
            });
        }
        /** @inheritdoc */
        onAfterRender() {
            const annunciationSub = this.annunciationSub = this.props.activeAnnunciation.sub(this.update.bind(this), false, true);
            const isDataFailedSub = this.isDataFailed = this.props.isDataFailed.sub(isFailed => {
                if (isFailed) {
                    annunciationSub.pause();
                    this.rootStyle.set('display', 'none');
                    this.props.isVisible.set(false);
                }
                else {
                    annunciationSub.resume(true);
                }
            }, false, true);
            this.showSub = this.props.show.sub(show => {
                if (show) {
                    isDataFailedSub.resume(true);
                }
                else {
                    isDataFailedSub.pause();
                    annunciationSub.pause();
                    this.rootStyle.set('display', 'none');
                    this.props.isVisible.set(false);
                }
            }, true);
        }
        /**
         * Updates this display.
         * @param annunciation The current true airspeed, in knots.
         */
        update(annunciation) {
            if (annunciation === this.props.annunciationType) {
                this.rootStyle.set('display', '');
                this.props.isVisible.set(true);
            }
            else {
                this.rootStyle.set('display', 'none');
                this.props.isVisible.set(false);
            }
        }
        /** @inheritdoc */
        render() {
            let typeClass;
            switch (this.props.annunciationType) {
                case exports.VSpeedAnnunciation.Takeoff:
                    typeClass = 'airspeed-vspeed-annunciation-takeoff';
                    break;
                case exports.VSpeedAnnunciation.Landing:
                    typeClass = 'airspeed-vspeed-annunciation-landing';
                    break;
                default:
                    typeClass = '';
            }
            return (msfsSdk.FSComponent.buildComponent("div", { class: `airspeed-bottom-display airspeed-vspeed-annunciation ${typeClass}`, style: this.rootStyle },
                msfsSdk.FSComponent.buildComponent("div", { class: 'airspeed-vspeed-annunciation-box' }, "VSPEEDS")));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c;
            (_a = this.showSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.isDataFailed) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.annunciationSub) === null || _c === void 0 ? void 0 : _c.destroy();
            super.destroy();
        }
    }

    /**
     * Altitude alert states.
     */
    exports.AltitudeAlertState = void 0;
    (function (AltitudeAlertState) {
        /** Disabled. */
        AltitudeAlertState["Disabled"] = "Disabled";
        /** Inhibited. */
        AltitudeAlertState["Inhibited"] = "Inhibited";
        /** Armed and outside of 1000 feet of selected altitude prior to capture. */
        AltitudeAlertState["Armed"] = "Armed";
        /** Within 1000 feet of selected altitude prior to capture. */
        AltitudeAlertState["Within1000"] = "Within1000";
        /** Within 200 feet of selected altitude prior to capture. */
        AltitudeAlertState["Within200"] = "Within200";
        /** Captured the selected altitude. */
        AltitudeAlertState["Captured"] = "Captured";
        /** Deviation from captured altitude is greater than 200 feet. */
        AltitudeAlertState["Deviation"] = "Deviation";
    })(exports.AltitudeAlertState || (exports.AltitudeAlertState = {}));
    /**
     * Maintains an altitude alert state based on the relationship between indicated altitude and selected altitude.
     */
    class AltitudeAlerter {
        /**
         * Creates an instance of AltitudeAlerter.
         * @param index The index of this alerter.
         * @param bus The event bus.
         * @param adcIndex The index of the ADC from which this alerter sources indicated altitude data.
         */
        constructor(index, bus, adcIndex) {
            this.index = index;
            this.bus = bus;
            this._state = msfsSdk.Subject.create(exports.AltitudeAlertState.Disabled);
            this.state = this._state;
            this.stateChangeQueue = [];
            this.isChangingState = false;
            this.indicatedAlt = msfsSdk.ConsumerSubject.create(null, 0);
            this.selectedAltSource = msfsSdk.ConsumerSubject.create(null, 0);
            this.selectedAltIsInit = msfsSdk.ConsumerSubject.create(null, false);
            this.selectedAlt = msfsSdk.MappedSubject.create(([selectedAlt, isInit]) => {
                return isInit ? selectedAlt : null;
            }, this.selectedAltSource, this.selectedAltIsInit);
            this.isOnGround = msfsSdk.ConsumerSubject.create(null, false);
            this.gpApproachMode = msfsSdk.ConsumerSubject.create(null, msfsSdk.ApproachGuidanceMode.None);
            this.isGsGpActive = msfsSdk.Subject.create(false);
            this.isAlertInhibited = msfsSdk.MappedSubject.create(([isOnGround, isGsGpActive, selectedAltIsInit]) => {
                return isOnGround || isGsGpActive || !selectedAltIsInit;
            }, this.isOnGround, this.isGsGpActive, this.selectedAltIsInit);
            this.targetAltitude = NaN;
            this.inhibitTimer = new msfsSdk.DebounceTimer();
            this.isInit = false;
            this.isAlive = true;
            this.adcIndex = msfsSdk.SubscribableUtils.toSubscribable(adcIndex, true);
        }
        /**
         * Initializes this alerter. Once initialized, this alerter will continuously update its alert state until destroyed.
         * @throws Error if this alerter is dead.
         */
        init() {
            if (!this.isAlive) {
                throw new Error('AltitudeAlerter: cannot initialize a dead alerter');
            }
            if (this.isInit) {
                return;
            }
            const sub = this.bus.getSubscriber();
            this.adcIndexSub = this.adcIndex.sub(index => {
                this.indicatedAlt.setConsumer(sub.on(`adc_indicated_alt_${index}`));
            }, true);
            this.selectedAltSource.setConsumer(sub.on('ap_altitude_selected'));
            this.selectedAltIsInit.setConsumer(sub.on('alt_select_is_initialized'));
            this.isOnGround.setConsumer(sub.on('on_ground'));
            this.gpApproachMode.setConsumer(sub.on('gp_approach_mode'));
            this.gpApproachMode.sub(mode => {
                switch (mode) {
                    case msfsSdk.ApproachGuidanceMode.GSActive:
                    case msfsSdk.ApproachGuidanceMode.GPActive:
                        this.isGsGpActive.set(true);
                        break;
                    default:
                        this.isGsGpActive.set(false);
                }
            }, true);
            this.disableSub = sub.on(`alt_alert_disable_${this.index}`).handle(() => {
                this.changeState(exports.AltitudeAlertState.Disabled);
            });
            this.selectedAltSub = this.selectedAlt.sub(selectedAlt => {
                if (selectedAlt !== null && selectedAlt !== this.targetAltitude) {
                    this.changeState(exports.AltitudeAlertState.Inhibited);
                    this.inhibitTimer.schedule(() => {
                        this.changeState(exports.AltitudeAlertState.Armed);
                    }, AltitudeAlerter.SELECTED_ALT_CHANGE_INHIBIT_DURATION);
                }
            }, false, true);
            this.isAlertInhibited.sub(isInhibited => {
                if (isInhibited) {
                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                    this.selectedAltSub.pause();
                    this.inhibitTimer.clear();
                    this.changeState(exports.AltitudeAlertState.Inhibited);
                }
                else {
                    this.changeState(exports.AltitudeAlertState.Disabled);
                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                    this.selectedAltSub.resume(true);
                }
            }, true);
        }
        /**
         * Requests a change in the state of this alerter. If no state change is currently in progress, the request will be
         * processed immediately. If a state change is currently in progress, the request will be queued and processed after
         * all pending state change requests are processed if no state change other than the one currently in progress is
         * carried out.
         * @param state The alert state to which to change.
         */
        changeState(state) {
            if (this.isChangingState) {
                this.stateChangeQueue.push(state);
            }
            else {
                this.processStateChange(state);
            }
        }
        /**
         * Processes a state change request.
         * @param state The alert state to which to change.
         */
        processStateChange(state) {
            var _a;
            const currentState = this._state.get();
            if (currentState === state) {
                this.dequeueStateChange();
                return;
            }
            if (state !== exports.AltitudeAlertState.Inhibited && this.isAlertInhibited.get()) {
                this.dequeueStateChange();
                return;
            }
            this.isChangingState = true;
            if (state === exports.AltitudeAlertState.Armed) {
                this.targetAltitude = this.selectedAlt.get();
                // If attempting to arm, check if we can directly enter one of the less deviated states
                const delta = Math.abs(this.targetAltitude - this.indicatedAlt.get());
                if (delta < 150) {
                    state = exports.AltitudeAlertState.Captured;
                }
                else if (delta < 200) {
                    state = exports.AltitudeAlertState.Within200;
                }
                else if (delta < 1000) {
                    state = exports.AltitudeAlertState.Within1000;
                }
            }
            (_a = this.indicatedAltSub) === null || _a === void 0 ? void 0 : _a.destroy();
            this.indicatedAltSub = undefined;
            this.stateChangeQueue.length = 0;
            this._state.set(state);
            switch (state) {
                case exports.AltitudeAlertState.Armed:
                    this.indicatedAltSub = this.indicatedAlt.sub(indicatedAlt => {
                        const delta = Math.abs(this.targetAltitude - indicatedAlt);
                        if (delta < 200) {
                            this.changeState(exports.AltitudeAlertState.Within200);
                        }
                        else if (delta < 1000) {
                            this.changeState(exports.AltitudeAlertState.Within1000);
                        }
                    }, false, true);
                    this.indicatedAltSub.resume(true);
                    break;
                case exports.AltitudeAlertState.Within1000:
                    this.indicatedAltSub = this.indicatedAlt.sub(indicatedAlt => {
                        const delta = Math.abs(this.targetAltitude - indicatedAlt);
                        if (delta < 200) {
                            this.changeState(exports.AltitudeAlertState.Within200);
                        }
                    }, false, true);
                    this.indicatedAltSub.resume(true);
                    break;
                case exports.AltitudeAlertState.Within200:
                    this.indicatedAltSub = this.indicatedAlt.sub(indicatedAlt => {
                        const delta = Math.abs(this.targetAltitude - indicatedAlt);
                        if (delta < 150) {
                            this.changeState(exports.AltitudeAlertState.Captured);
                        }
                        else if (delta > 250) {
                            this.changeState(exports.AltitudeAlertState.Deviation);
                        }
                    }, false, true);
                    this.indicatedAltSub.resume(true);
                    break;
                case exports.AltitudeAlertState.Captured:
                    this.indicatedAltSub = this.indicatedAlt.sub(indicatedAlt => {
                        const delta = Math.abs(this.targetAltitude - indicatedAlt);
                        if (delta > 200) {
                            this.changeState(exports.AltitudeAlertState.Deviation);
                        }
                    }, false, true);
                    this.indicatedAltSub.resume(true);
                    break;
                case exports.AltitudeAlertState.Deviation:
                    this.indicatedAltSub = this.indicatedAlt.sub(indicatedAlt => {
                        const delta = Math.abs(this.targetAltitude - indicatedAlt);
                        if (delta < 150) {
                            this.changeState(exports.AltitudeAlertState.Captured);
                        }
                    }, false, true);
                    this.indicatedAltSub.resume(true);
                    break;
            }
            this.isChangingState = false;
            this.dequeueStateChange();
        }
        /**
         * Processes the next state change request in the queue, if one exists.
         */
        dequeueStateChange() {
            const state = this.stateChangeQueue.shift();
            if (state !== undefined) {
                this.processStateChange(state);
            }
        }
        /**
         * Destroys this alerter. Once destroyed, this alerter will no longer update its alert state.
         */
        destroy() {
            var _a, _b, _c;
            this.isAlive = false;
            this.inhibitTimer.clear();
            this.indicatedAlt.destroy();
            this.selectedAltSource.destroy();
            this.selectedAltIsInit.destroy();
            this.isOnGround.destroy();
            this.gpApproachMode.destroy();
            (_a = this.adcIndexSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.apLockSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.disableSub) === null || _c === void 0 ? void 0 : _c.destroy();
        }
    }
    AltitudeAlerter.SELECTED_ALT_CHANGE_INHIBIT_DURATION = 3000; // milliseconds

    /**
     * Minimums alert states.
     */
    exports.MinimumsAlertState = void 0;
    (function (MinimumsAlertState) {
        /** Inhibited. */
        MinimumsAlertState["Inhibited"] = "Inhibited";
        /** Armed. */
        MinimumsAlertState["Armed"] = "Armed";
        /** Greater than 100 feet above minimums. */
        MinimumsAlertState["Above100"] = "Above100";
        /** Less than or equal to 100 feet above minimums. */
        MinimumsAlertState["Within100"] = "Within100";
        /** At or below minimums. */
        MinimumsAlertState["AtOrBelow"] = "AtOrBelow";
    })(exports.MinimumsAlertState || (exports.MinimumsAlertState = {}));
    /**
     * Maintains a minimums alert state based on the relationship between indicated/radar altitude and the appropriate
     * minimums setting.
     */
    class MinimumsAlerter {
        /**
         * Creates an instance of AltitudeAlerter.
         * @param bus The event bus.
         * @param adcIndex The index of the ADC from which this alerter sources indicated altitude data.
         * @param supportRadarMins Whether to support radar minimums.
         */
        constructor(bus, adcIndex, supportRadarMins) {
            this.bus = bus;
            this.supportRadarMins = supportRadarMins;
            this._minimumsMode = msfsSdk.ConsumerSubject.create(null, msfsSdk.MinimumsMode.OFF);
            this.minimumsMode = this._minimumsMode;
            this._state = msfsSdk.Subject.create(exports.MinimumsAlertState.Inhibited);
            this.state = this._state;
            this.stateChangeQueue = [];
            this.isChangingState = false;
            this.indicatedAlt = msfsSdk.ConsumerSubject.create(null, 0);
            this.radarAlt = msfsSdk.ConsumerSubject.create(null, 0);
            this.baroMinimumsSource = msfsSdk.ConsumerSubject.create(null, 0);
            this.radarMinimumsSource = msfsSdk.ConsumerSubject.create(null, 0);
            this.isAdcOperating = msfsSdk.Subject.create(true);
            this.isRadarAltimeterOperating = msfsSdk.Subject.create(true);
            this.minimumsDelta = msfsSdk.Subject.create(null);
            this.isOnGround = msfsSdk.ConsumerSubject.create(null, false);
            this.isAlertInhibited = msfsSdk.MappedSubject.create(([minimumsDelta, isOnGround]) => {
                return isOnGround || minimumsDelta === null;
            }, this._minimumsMode, this.isOnGround);
            this.isInit = false;
            this.isAlive = true;
            this.adcIndex = msfsSdk.SubscribableUtils.toSubscribable(adcIndex, true);
        }
        /**
         * Initializes this alerter. Once initialized, this alerter will continuously update its alert state until destroyed.
         * @throws Error if this alerter is dead.
         */
        init() {
            if (!this.isAlive) {
                throw new Error('MinimumsAlerter: cannot initialize a dead alerter');
            }
            if (this.isInit) {
                return;
            }
            const sub = this.bus.getSubscriber();
            this.adcIndexSub = this.adcIndex.sub(index => {
                var _a;
                (_a = this.adcSystemStateSub) === null || _a === void 0 ? void 0 : _a.destroy();
                this.adcSystemStateSub = sub.on(`adc_state_${index}`).handle(state => {
                    if (state.current === msfsSdk.AvionicsSystemState.On || state.current === undefined) {
                        this.isAdcOperating.set(true);
                    }
                    else {
                        this.isAdcOperating.set(false);
                    }
                });
                this.indicatedAlt.setConsumer(sub.on(`adc_indicated_alt_${index}`));
            }, true);
            if (this.supportRadarMins) {
                this.radarAlt.setConsumer(sub.on('radaralt_radio_alt_1'));
                this.radarAltSystemStateSub = sub.on('radaralt_state_1').handle(state => {
                    this.isRadarAltimeterOperating.set(state.current === undefined || state.current === msfsSdk.AvionicsSystemState.On);
                });
            }
            this._minimumsMode.setConsumer(sub.on('minimums_mode'));
            this.baroMinimumsSource.setConsumer(sub.on('decision_altitude_feet'));
            this.radarMinimumsSource.setConsumer(sub.on('decision_height_feet'));
            const baroMinimumsState = msfsSdk.MappedSubject.create(this.indicatedAlt, this.baroMinimumsSource, this.isAdcOperating);
            const baroMinimumsSub = baroMinimumsState.sub(([indicatedAlt, baroMinimumsSource, isAdcOperating]) => {
                if (isAdcOperating) {
                    this.minimumsDelta.set(indicatedAlt - baroMinimumsSource);
                }
                else {
                    this.minimumsDelta.set(null);
                }
            }, false, true);
            const radarMinimumsState = msfsSdk.MappedSubject.create(this.radarAlt, this.radarMinimumsSource, this.isRadarAltimeterOperating);
            const radarMinimumsSub = radarMinimumsState.sub(([radarAlt, radarMinimumsSource, isRadarAltimeterOperating]) => {
                if (isRadarAltimeterOperating) {
                    this.minimumsDelta.set(radarAlt - radarMinimumsSource);
                }
                else {
                    this.minimumsDelta.set(null);
                }
            }, false, true);
            this._minimumsMode.sub(mode => {
                baroMinimumsSub.pause();
                radarMinimumsSub.pause();
                switch (mode) {
                    case msfsSdk.MinimumsMode.BARO:
                        baroMinimumsSub.resume(true);
                        break;
                    case msfsSdk.MinimumsMode.RA:
                        if (this.supportRadarMins) {
                            radarMinimumsSub.resume(true);
                            break;
                        }
                    // eslint-disable-next-line no-fallthrough
                    default:
                        this.minimumsDelta.set(null);
                }
            }, true);
            this.isOnGround.setConsumer(sub.on('on_ground'));
            this.isAlertInhibited.sub(isInhibited => {
                if (isInhibited) {
                    this.changeState(exports.MinimumsAlertState.Inhibited);
                }
                else {
                    this.changeState(exports.MinimumsAlertState.Armed);
                }
            }, true);
        }
        /**
         * Requests a change in the state of this alerter. If no state change is currently in progress, the request will be
         * processed immediately. If a state change is currently in progress, the request will be queued and processed after
         * all pending state change requests are processed if no state change other than the one currently in progress is
         * carried out.
         * @param state The alert state to which to change.
         */
        changeState(state) {
            if (this.isChangingState) {
                this.stateChangeQueue.push(state);
            }
            else {
                this.processStateChange(state);
            }
        }
        /**
         * Processes a state change request.
         * @param state The alert state to which to change.
         */
        processStateChange(state) {
            var _a;
            const currentState = this._state.get();
            if (currentState === state) {
                this.dequeueStateChange();
                return;
            }
            if (state !== exports.MinimumsAlertState.Inhibited && this.isAlertInhibited.get()) {
                this.dequeueStateChange();
                return;
            }
            this.isChangingState = true;
            (_a = this.minimumsDeltaSub) === null || _a === void 0 ? void 0 : _a.destroy();
            this.minimumsDeltaSub = undefined;
            this.stateChangeQueue.length = 0;
            this._state.set(state);
            switch (state) {
                case exports.MinimumsAlertState.Armed:
                    this.minimumsDeltaSub = this.minimumsDelta.sub(delta => {
                        if (delta !== null && delta > 150) {
                            this.changeState(exports.MinimumsAlertState.Above100);
                        }
                    }, true);
                    break;
                case exports.MinimumsAlertState.Above100:
                    this.minimumsDeltaSub = this.minimumsDelta.sub(delta => {
                        if (delta !== null && delta <= 100) {
                            this.changeState(exports.MinimumsAlertState.Within100);
                        }
                    }, true);
                    break;
                case exports.MinimumsAlertState.Within100:
                    this.minimumsDeltaSub = this.minimumsDelta.sub(delta => {
                        if (delta === null) {
                            return;
                        }
                        if (delta > 150) {
                            this.changeState(exports.MinimumsAlertState.Above100);
                        }
                        else if (delta <= 0) {
                            this.changeState(exports.MinimumsAlertState.AtOrBelow);
                        }
                    }, true);
                    break;
                case exports.MinimumsAlertState.AtOrBelow:
                    this.minimumsDeltaSub = this.minimumsDelta.sub(delta => {
                        if (delta !== null && delta > 50) {
                            this.changeState(exports.MinimumsAlertState.Within100);
                        }
                    }, true);
                    break;
            }
            this.isChangingState = false;
            this.dequeueStateChange();
        }
        /**
         * Processes the next state change request in the queue, if one exists.
         */
        dequeueStateChange() {
            const state = this.stateChangeQueue.shift();
            if (state !== undefined) {
                this.processStateChange(state);
            }
        }
        /**
         * Destroys this alerter. Once destroyed, this alerter will no longer update its alert state.
         */
        destroy() {
            var _a, _b, _c;
            this.isAlive = false;
            this.indicatedAlt.destroy();
            this.radarAlt.destroy();
            this._minimumsMode.destroy();
            this.baroMinimumsSource.destroy();
            this.radarMinimumsSource.destroy();
            this.isOnGround.destroy();
            (_a = this.adcIndexSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.adcSystemStateSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.radarAltSystemStateSub) === null || _c === void 0 ? void 0 : _c.destroy();
        }
    }

    /**
     * A next-generation (NXi, G3000, etc) Garmin PFD altimeter.
     */
    class Altimeter extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.tapeRef = msfsSdk.FSComponent.createRef();
            this.selectedAltitudeRef = msfsSdk.FSComponent.createRef();
            this.baroSettingRef = msfsSdk.FSComponent.createRef();
            this.rootCssClass = msfsSdk.SetSubject.create(['altimeter']);
            this.showTopBottomDisplays = this.props.declutter.map(msfsSdk.SubscribableMapFunctions.not());
        }
        /** @inheritdoc */
        onAfterRender() {
            this.showTopBottomDisplays.sub(show => {
                this.rootCssClass.toggle('altimeter-top-visible', show);
                this.rootCssClass.toggle('altimeter-bottom-visible', show);
            }, true);
            this.minimumsAlertSub = this.props.minimumsAlertState.sub(state => {
                this.rootCssClass.delete('minimums-alert-within100');
                this.rootCssClass.delete('minimums-alert-atorbelow');
                switch (state) {
                    case exports.MinimumsAlertState.Within100:
                        this.rootCssClass.add('minimums-alert-within100');
                        break;
                    case exports.MinimumsAlertState.AtOrBelow:
                        this.rootCssClass.add('minimums-alert-atorbelow');
                        break;
                }
            }, true);
            this.isDataFailedSub = this.props.dataProvider.isDataFailed.sub(isDataFailed => {
                if (isDataFailed) {
                    this.rootCssClass.add('data-failed');
                }
                else {
                    this.rootCssClass.delete('data-failed');
                }
            }, true);
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: this.rootCssClass, "data-checklist": "checklist-altimeter" },
                msfsSdk.FSComponent.buildComponent(AltimeterTape, Object.assign({ ref: this.tapeRef, dataProvider: this.props.dataProvider }, this.props.tapeScaleOptions, this.props.trendVectorOptions, { showMetric: this.props.settingManager.getSetting('altMetric') })),
                msfsSdk.FSComponent.buildComponent("div", { class: 'altimeter-top-container', "data-checklist": "checklist-altimeter-top" },
                    msfsSdk.FSComponent.buildComponent(SelectedAltitudeDisplay, { show: this.showTopBottomDisplays, selectedAlt: this.props.dataProvider.selectedAlt, altitudeAlertState: this.props.altitudeAlertState })),
                msfsSdk.FSComponent.buildComponent("div", { class: 'altimeter-bottom-container', "data-checklist": "checklist-altimeter-bottom" },
                    msfsSdk.FSComponent.buildComponent(BaroSettingDisplay, { show: this.showTopBottomDisplays, baroSetting: this.props.dataProvider.baroSetting, isStdActive: this.props.dataProvider.baroIsStdActive, baroPreselect: this.props.supportBaroPreselect ? this.props.dataProvider.baroPreselect : undefined, isMetric: this.props.settingManager.getSetting('altimeterBaroMetric') })),
                msfsSdk.FSComponent.buildComponent("div", { class: 'failed-box' })));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c, _d, _e;
            super.destroy();
            (_a = this.tapeRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.selectedAltitudeRef.getOrDefault()) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.baroSettingRef.getOrDefault()) === null || _c === void 0 ? void 0 : _c.destroy();
            this.showTopBottomDisplays.destroy();
            (_d = this.minimumsAlertSub) === null || _d === void 0 ? void 0 : _d.destroy();
            (_e = this.isDataFailedSub) === null || _e === void 0 ? void 0 : _e.destroy();
        }
    }
    /**
     * A next-generation (NXi, G3000, etc) Garmin altimeter tape.
     */
    class AltimeterTape extends msfsSdk.DisplayComponent {
        /** @inheritdoc */
        constructor(props) {
            super(props);
            this.indicatedAltBoxRef = msfsSdk.FSComponent.createRef();
            this.metricIndicatedAltDisplayRef = msfsSdk.FSComponent.createRef();
            this.metricSelectedAltDisplayRef = msfsSdk.FSComponent.createRef();
            this.minorTickContainerRef = msfsSdk.FSComponent.createRef();
            this.majorTickContainerRef = msfsSdk.FSComponent.createRef();
            this.labelContainerRef = msfsSdk.FSComponent.createRef();
            this.selectedAltBugRef = msfsSdk.FSComponent.createRef();
            this.minimumsBugRef = msfsSdk.FSComponent.createRef();
            this.rootCssClass = msfsSdk.SetSubject.create(['altimeter-tape-container']);
            this.labelAltitudes = [];
            this.tapeStyle = msfsSdk.ObjectSubject.create({
                position: 'absolute',
                left: '0%',
                bottom: '50%',
                width: '100%',
                height: '100%',
                transform: 'translate3d(0, 0, 0)'
            });
            this.tapeClipStyle = msfsSdk.ObjectSubject.create({
                position: 'absolute',
                left: '0%',
                bottom: '0%',
                width: '100%',
                height: '100%',
                overflow: 'hidden'
            });
            this.tapeOverflowTopStyle = msfsSdk.ObjectSubject.create({
                position: 'absolute',
                left: '0%',
                bottom: '100%',
                width: '100%',
                height: '50%'
            });
            this.trendVectorStyle = msfsSdk.ObjectSubject.create({
                display: '',
                position: 'absolute',
                bottom: '50%',
                height: '0%',
                transform: 'rotateX(0deg)',
                'transform-origin': '50% 100%'
            });
            this.vSpeedOffScaleContainerStyle = msfsSdk.ObjectSubject.create({
                display: 'flex',
                'flex-flow': 'column-reverse nowrap',
                position: 'absolute',
                bottom: '0%',
                overflow: 'hidden'
            });
            this.currentLength = msfsSdk.Subject.create(0);
            this.currentMinimum = 0;
            this.currentTranslate = msfsSdk.Subject.create(0);
            this.minimum = msfsSdk.SubscribableUtils.toSubscribable(this.props.minimum, true);
            this.maximum = msfsSdk.SubscribableUtils.toSubscribable(this.props.maximum, true);
            this.window = msfsSdk.SubscribableUtils.toSubscribable(this.props.window, true);
            this.majorTickInterval = msfsSdk.SubscribableUtils.toSubscribable(this.props.majorTickInterval, true);
            this.minorTickFactor = msfsSdk.SubscribableUtils.toSubscribable(this.props.minorTickFactor, true);
            this.options = msfsSdk.MappedSubject.create(this.minimum, this.maximum, this.window, this.majorTickInterval, this.minorTickFactor);
            this.isIndicatedAltBelowScale = msfsSdk.MappedSubject.create(([indicatedAlt, minimum]) => {
                return indicatedAlt < minimum;
            }, this.props.dataProvider.indicatedAlt, this.minimum);
            this.isIndicatedAltAboveScale = msfsSdk.MappedSubject.create(([indicatedAlt, maximum]) => {
                return indicatedAlt > maximum;
            }, this.props.dataProvider.indicatedAlt, this.maximum);
            this.isIndicatedAltOffScale = msfsSdk.MappedSubject.create(([isIndicatedAltBelowScale, isIndicatedAltAboveScale]) => {
                return isIndicatedAltBelowScale || isIndicatedAltAboveScale;
            }, this.isIndicatedAltBelowScale, this.isIndicatedAltAboveScale);
            this.indicatedAltTapeValue = msfsSdk.MappedSubject.create(([indicatedAlt, minimum, maximum, window, isDataFailed]) => {
                return isDataFailed ? minimum + window / 2 : msfsSdk.MathUtils.clamp(indicatedAlt, minimum, maximum);
            }, this.props.dataProvider.indicatedAlt, this.minimum, this.maximum, this.window, this.props.dataProvider.isDataFailed);
            this.indicatedAltBoxValue = msfsSdk.MappedSubject.create(([indicatedAlt, isIndicatedAltOffScale]) => {
                return isIndicatedAltOffScale ? NaN : indicatedAlt;
            }, this.props.dataProvider.indicatedAlt, this.isIndicatedAltOffScale);
            this.showMetricIndicatedAlt = msfsSdk.MappedSubject.create(([showMetric, isDataFailed]) => showMetric && !isDataFailed, this.props.showMetric, this.props.dataProvider.isDataFailed);
            this.metricIndicatedAltValue = msfsSdk.MappedSubject.create(([indicatedAltFeet, minimum, maximum]) => {
                return msfsSdk.UnitType.FOOT.convertTo(msfsSdk.MathUtils.clamp(indicatedAltFeet, minimum, maximum), msfsSdk.UnitType.METER);
            }, this.props.dataProvider.indicatedAlt, this.minimum, this.maximum);
            this.showGroundLine = msfsSdk.MappedSubject.create(([isIndicatedAltOffScale, isDataFailed]) => !isIndicatedAltOffScale && !isDataFailed, this.isIndicatedAltOffScale, this.props.dataProvider.isDataFailed);
            this.trendThreshold = msfsSdk.SubscribableUtils.toSubscribable(this.props.trendThreshold, true);
            this.showTrendVector = msfsSdk.MappedSubject.create(([indicatedAlt, minimum, maximum, threshold, altitudeTrend, isDataFailed]) => {
                return !isDataFailed && indicatedAlt >= minimum && indicatedAlt < maximum && Math.abs(altitudeTrend) >= threshold;
            }, this.props.dataProvider.indicatedAlt, this.minimum, this.maximum, this.trendThreshold, this.props.dataProvider.altitudeTrend, this.props.dataProvider.isDataFailed);
            this.altitudeTrendParams = msfsSdk.MappedSubject.create(this.props.dataProvider.altitudeTrend, this.window);
            this.trendVectorHeight = msfsSdk.Subject.create(0);
            this.trendVectorScale = msfsSdk.Subject.create(1);
            this.selectedAltMeters = this.props.dataProvider.selectedAlt.map(selectedAltFeet => {
                return selectedAltFeet === null ? null : msfsSdk.UnitType.FOOT.convertTo(selectedAltFeet, msfsSdk.UnitType.METER);
            });
            this.updateTapeEvent = new msfsSdk.SubEvent();
            this.updateTapeWindowEvent = new msfsSdk.SubEvent();
            this.showIndicatedAltData = this.props.dataProvider.isDataFailed.map(msfsSdk.SubscribableMapFunctions.not());
            this.indicatedAltTapeValue.pause();
            this.indicatedAltBoxValue.pause();
            this.showTrendVector.pause();
            this.altitudeTrendParams.pause();
        }
        /** @inheritdoc */
        onAfterRender() {
            this.indicatedAltTapeValue.resume();
            this.indicatedAltBoxValue.resume();
            this.indicatedAltTapeValue.sub(this.updateTape.bind(this));
            this.altitudeTrendParams.sub(this.updateTrendVector.bind(this));
            this.showTrendVector.resume();
            this.showTrendVector.sub(show => {
                if (show) {
                    this.trendVectorStyle.set('display', '');
                    this.altitudeTrendParams.resume();
                }
                else {
                    this.altitudeTrendParams.pause();
                    this.trendVectorStyle.set('display', 'none');
                }
            }, true);
            this.trendVectorHeight.sub(height => { this.trendVectorStyle.set('height', `${height}%`); });
            this.trendVectorScale.sub(scale => { this.trendVectorStyle.set('transform', `scale(${scale}) rotateX(0deg)`); });
            this.currentTranslate.sub(translate => {
                this.tapeStyle.set('transform', `translate3d(0, ${translate * 100}%, 0)`);
            });
            this.options.sub(this.rebuildTape.bind(this), true);
            this.isIndicatedAltBelowScale.sub(isIasBelowScale => {
                this.vSpeedOffScaleContainerStyle.set('display', isIasBelowScale ? 'flex' : 'none');
            }, true);
        }
        /**
         * Calculates the absolute vertical position on the tape at which a particular altitude is located, with `0` at the
         * top of the tape and `1` at the bottom.
         * @param indicatedAlt An altitude, in feet.
         * @param clamp Whether the altitude should be clamped to the range defined by this tape's minimum and maximum
         * representable altitudes. Defaults to `false`.
         * @returns The absolute vertical position on the tape at which the specified altitude is located, with `0` at the
         * top of the tape and `1` at the bottom.
         */
        calculateAbsoluteTapePosition(indicatedAlt, clamp = false) {
            if (clamp) {
                indicatedAlt = msfsSdk.MathUtils.clamp(indicatedAlt, this.minimum.get(), this.maximum.get());
            }
            return 1 - (indicatedAlt - this.currentMinimum) / this.currentLength.get();
        }
        /**
         * Calculates the vertical position on the tape window at which a particular altitude is located, with `0` at the top
         * of the tape window and `1` at the bottom.
         * @param indicatedAlt An altitude, in knots.
         * @param clamp Whether the altitude should be clamped to the range defined by this tape's minimum and maximum
         * representable altitudes. Defaults to `false`.
         * @returns The vertical position on the tape window at which the specified altitude is located, with `0` at the top
         * of the tape window and `1` at the bottom.
         */
        calculateWindowTapePosition(indicatedAlt, clamp = false) {
            return (this.calculateAbsoluteTapePosition(indicatedAlt, clamp) - 1 + this.currentTranslate.get()) * this.currentLength.get() / this.window.get() + 0.5;
        }
        /**
         * Rebuilds this tape's ticks and labels.
         * @param options Scale options for this tape, as `[minimum, maximum, window, majorTickInterval, minorTickFactor]`.
         */
        rebuildTape(options) {
            const [minimum, maximum, window, majorTickInterval, minorTickFactor] = options;
            this.labelAltitudes.length = 0;
            this.minorTickContainerRef.instance.innerHTML = '';
            this.majorTickContainerRef.instance.innerHTML = '';
            this.labelContainerRef.instance.innerHTML = '';
            const majorTickCount = Math.ceil(window / majorTickInterval) * 2 + 1;
            const desiredRange = (majorTickCount - 1) * majorTickInterval;
            this.currentLength.set(desiredRange);
            const maxRange = maximum - minimum;
            const trueRange = Math.min(maxRange, desiredRange);
            const heightFactor = trueRange / desiredRange;
            const len = (majorTickCount - 1) * minorTickFactor;
            for (let i = 0; i <= len; i++) {
                const y = 100 - (i / len) * 100 / heightFactor;
                if (i % minorTickFactor === 0) {
                    // major tick
                    msfsSdk.FSComponent.render(msfsSdk.FSComponent.buildComponent("path", { d: `M 0 ${y} L 100 ${y}`, "vector-effect": 'non-scaling-stroke', class: 'altimeter-tape-tick altimeter-tape-tick-major' }, "."), this.majorTickContainerRef.instance);
                    const altitude = msfsSdk.Subject.create(0);
                    msfsSdk.FSComponent.render(msfsSdk.FSComponent.buildComponent("div", { class: 'altimeter-tape-label', style: `position: absolute; right: 0%; top: ${y}%; transform: translateY(-50%)` },
                        msfsSdk.FSComponent.buildComponent("span", { class: 'altimeter-tape-label-hundreds' }, altitude.map(alt => Math.trunc(alt / 100).toString())),
                        msfsSdk.FSComponent.buildComponent("span", { class: 'altimeter-tape-label-tens' }, altitude.map(alt => (Math.abs(alt) % 100).toFixed(0).padStart(2, '0')))), this.labelContainerRef.instance);
                    this.labelAltitudes.push(altitude);
                }
                else {
                    // minor tick
                    msfsSdk.FSComponent.render(msfsSdk.FSComponent.buildComponent("path", { d: `M 0 ${y} L 100 ${y}`, "vector-effect": 'non-scaling-stroke', class: 'altimeter-tape-tick altimeter-tape-tick-minor' }, "."), this.minorTickContainerRef.instance);
                }
            }
            this.tapeStyle.set('height', `${100 * desiredRange / window}%`);
            this.tapeClipStyle.set('height', `${100 * heightFactor}%`);
            this.currentMinimum = minimum;
            this.updateTapeEvent.notify(this);
            this.updateTapeLabels();
            this.updateTapeOverflow();
            this.updateTape(this.indicatedAltTapeValue.get());
        }
        /**
         * Updates the tape based on the current indicated altitude.
         * @param indicatedAlt The current indicated altitude, in feet.
         */
        updateTape(indicatedAlt) {
            let tapePos = this.calculateAbsoluteTapePosition(indicatedAlt);
            if (tapePos <= 0.25 || tapePos >= 0.75) {
                const [minimum, maximum, window, majorTickInterval] = this.options.get();
                const desiredMinimum = Math.floor((indicatedAlt - window) / majorTickInterval) * majorTickInterval;
                const constrainedMinimum = Math.ceil((maximum - this.currentLength.get()) / majorTickInterval) * majorTickInterval;
                const minimumToSet = Math.max(minimum, Math.min(constrainedMinimum, desiredMinimum));
                if (this.currentMinimum !== minimumToSet) {
                    this.currentMinimum = minimumToSet;
                    this.updateTapeEvent.notify(this);
                    this.updateTapeLabels();
                    this.updateTapeOverflow();
                    tapePos = msfsSdk.MathUtils.clamp(this.calculateAbsoluteTapePosition(indicatedAlt), 0, 1);
                }
            }
            this.currentTranslate.set(msfsSdk.MathUtils.round(1 - tapePos, 1e-3));
            this.updateTapeWindowEvent.notify(this);
        }
        /**
         * Updates this tape's labels.
         */
        updateTapeLabels() {
            const interval = this.majorTickInterval.get();
            for (let i = 0; i < this.labelAltitudes.length; i++) {
                this.labelAltitudes[i].set(this.currentMinimum + interval * i);
            }
        }
        /**
         * Updates this tape's overflow regions.
         */
        updateTapeOverflow() {
            const maximumPos = this.calculateAbsoluteTapePosition(this.maximum.get());
            this.tapeOverflowTopStyle.set('bottom', `${Math.min(100, 100 - maximumPos * 100)}%`);
        }
        /**
         * Updates this tape's speed trend vector.
         * @param params Parameters for the speed trend vector, as `[iasTrend, window]`.
         */
        updateTrendVector(params) {
            const [iasTrend, window] = params;
            this.trendVectorHeight.set(msfsSdk.MathUtils.clamp(msfsSdk.MathUtils.round(Math.abs(iasTrend) / window * 100, 0.1), 0, 50));
            this.trendVectorScale.set(iasTrend < 0 ? -1 : 1);
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: this.rootCssClass, "data-checklist": "checklist-altimeter-tape" },
                msfsSdk.FSComponent.buildComponent("div", { class: 'altimeter-tape-border-top' }),
                msfsSdk.FSComponent.buildComponent("div", { class: 'altimeter-tape-border-bottom' }),
                msfsSdk.FSComponent.buildComponent("div", { class: 'altimeter-tape-window', style: 'overflow: hidden;' },
                    msfsSdk.FSComponent.buildComponent("div", { class: 'altimeter-tape', style: this.tapeStyle },
                        msfsSdk.FSComponent.buildComponent("div", { class: 'altimeter-tape-clip', style: this.tapeClipStyle },
                            msfsSdk.FSComponent.buildComponent("svg", { ref: this.minorTickContainerRef, class: 'altimeter-tape-tick-minor-container', viewBox: '0 0 100 100', preserveAspectRatio: 'none', style: 'position: absolute; top: 0; height: 100%;' }),
                            msfsSdk.FSComponent.buildComponent("svg", { ref: this.majorTickContainerRef, class: 'altimeter-tape-tick-major-container', viewBox: '0 0 100 100', preserveAspectRatio: 'none', style: 'position: absolute; top: 0; height: 100%;' }),
                            msfsSdk.FSComponent.buildComponent("div", { ref: this.labelContainerRef, class: 'altimeter-tape-label-container', style: 'position: absolute; top: 0; height: 100%; text-align: right;' })),
                        msfsSdk.FSComponent.buildComponent("div", { class: 'altimeter-tape-overflow', style: this.tapeOverflowTopStyle }),
                        msfsSdk.FSComponent.buildComponent("div", { class: 'altimeter-tape-overflow', style: 'position: absolute; left: 0; top: 100%; width: 100%; height: 50%;' }))),
                msfsSdk.FSComponent.buildComponent(GroundLine, { show: this.showGroundLine, indicatedAlt: this.props.dataProvider.indicatedAlt, radarAlt: this.props.dataProvider.radarAlt, updateEvent: this.updateTapeWindowEvent, getPosition: this.calculateWindowTapePosition.bind(this) }),
                msfsSdk.FSComponent.buildComponent("div", { class: 'altimeter-trend', style: this.trendVectorStyle }),
                msfsSdk.FSComponent.buildComponent(IndicatedAltDisplayBox, { ref: this.indicatedAltBoxRef, show: this.showIndicatedAltData, indicatedAlt: this.indicatedAltBoxValue }),
                msfsSdk.FSComponent.buildComponent(MetricIndicatedAltDisplay, { ref: this.metricIndicatedAltDisplayRef, show: this.showMetricIndicatedAlt, indicatedAltMeters: this.metricIndicatedAltValue }),
                msfsSdk.FSComponent.buildComponent(MetricSelectedAltitudeDisplay, { ref: this.metricSelectedAltDisplayRef, show: this.props.showMetric, selectedAltMeters: this.selectedAltMeters }),
                msfsSdk.FSComponent.buildComponent("div", { class: 'altimeter-bug-container', style: 'position: absolute; left: 0; top: 0; width: 100%; height: 100%; overflow: hidden;' },
                    msfsSdk.FSComponent.buildComponent(MinimumsBug, { ref: this.minimumsBugRef, show: this.showIndicatedAltData, minimums: this.props.dataProvider.minimums, updateEvent: this.updateTapeWindowEvent, getPosition: this.calculateWindowTapePosition.bind(this) }),
                    msfsSdk.FSComponent.buildComponent(SelectedAltitudeBug, { ref: this.selectedAltBugRef, show: this.showIndicatedAltData, selectedAlt: this.props.dataProvider.selectedAlt, updateEvent: this.updateTapeWindowEvent, getPosition: this.calculateWindowTapePosition.bind(this) }))));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c, _d, _e;
            (_a = this.indicatedAltBoxRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.metricIndicatedAltDisplayRef.getOrDefault()) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.metricSelectedAltDisplayRef.getOrDefault()) === null || _c === void 0 ? void 0 : _c.destroy();
            (_d = this.selectedAltBugRef.getOrDefault()) === null || _d === void 0 ? void 0 : _d.destroy();
            (_e = this.minimumsBugRef.getOrDefault()) === null || _e === void 0 ? void 0 : _e.destroy();
            this.options.destroy();
            this.isIndicatedAltBelowScale.destroy();
            this.isIndicatedAltAboveScale.destroy();
            this.isIndicatedAltOffScale.destroy();
            this.indicatedAltTapeValue.destroy();
            this.indicatedAltBoxValue.destroy();
            this.metricIndicatedAltValue.destroy();
            this.altitudeTrendParams.destroy();
            this.selectedAltMeters.destroy();
            this.showIndicatedAltData.destroy();
            this.showMetricIndicatedAlt.destroy();
            super.destroy();
        }
    }
    /**
     * An indicated altitude display box for a next-generation (NXi, G3000, etc) Garmin altimeter tape.
     */
    class IndicatedAltDisplayBox extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.scrollerRefs = [];
            this.rootStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.negativeSignStyles = Array.from({ length: 3 }, () => {
                return msfsSdk.ComputedSubject.create(false, show => show ? '' : 'display: none;');
            });
            this.indicatedAlt = this.props.indicatedAlt.map(msfsSdk.SubscribableMapFunctions.identity()).pause();
            this.showNegativeSign = Array.from({ length: 3 }, (val, index) => {
                const topThreshold = index === 0 ? 0 : Math.pow(10, index + 1) - 20;
                const bottomThreshold = Math.pow(10, index + 2) - 20;
                return this.indicatedAlt.map(indicatedAlt => {
                    return indicatedAlt < -topThreshold && indicatedAlt >= -bottomThreshold;
                });
            });
        }
        /** @inheritdoc */
        onAfterRender() {
            this.showNegativeSign.forEach((show, index) => {
                show.pipe(this.negativeSignStyles[index]);
            });
            this.showSub = this.props.show.sub(show => {
                if (show) {
                    this.rootStyle.set('display', '');
                    this.indicatedAlt.resume();
                }
                else {
                    this.rootStyle.set('display', 'none');
                    this.indicatedAlt.pause();
                }
            }, true);
        }
        /** @inheritdoc */
        render() {
            const tensScrollerRef = msfsSdk.FSComponent.createRef();
            const hundredsScrollerRef = msfsSdk.FSComponent.createRef();
            const thousandsScrollerRef = msfsSdk.FSComponent.createRef();
            const tenThousandsScrollerRef = msfsSdk.FSComponent.createRef();
            this.scrollerRefs.push(tensScrollerRef, tensScrollerRef, hundredsScrollerRef, tenThousandsScrollerRef);
            return (msfsSdk.FSComponent.buildComponent("div", { class: 'altimeter-indicatedalt-box', style: this.rootStyle },
                msfsSdk.FSComponent.buildComponent("svg", { viewBox: "0 0 105 70", class: 'altimeter-indicatedalt-box-bg', preserveAspectRatio: 'none' },
                    msfsSdk.FSComponent.buildComponent("path", { "vector-effect": 'non-scaling-stroke', d: 'M 4 35 l 8 -6 l 0 -13 c 0 -1 1 -2 2 -2 l 54 0 l 0 -11 c 0 -1 1 -2 2 -2 l 30 0 c 1 0 2 1 2 2 l 0 64 c 0 1 -1 2 -2 2 l -30 0 c -1 0 -2 -1 -2 -2 l 0 -11 l -54 0 c -1 0 -2 -1 -2 -2 l 0 -13 z' })),
                msfsSdk.FSComponent.buildComponent("div", { class: 'altimeter-indicatedalt-box-scrollers', style: 'position: absolute; right: 2.8%; top: 1.5%; width: 85.8%; height: 97%;' },
                    msfsSdk.FSComponent.buildComponent("div", { class: 'altimeter-indicatedalt-box-digit-container altimeter-indicatedalt-box-ten-thousands' },
                        msfsSdk.FSComponent.buildComponent("div", { class: 'altimeter-indicatedalt-box-digit-bg' }),
                        msfsSdk.FSComponent.buildComponent(msfsSdk.DigitScroller, { ref: tenThousandsScrollerRef, value: this.indicatedAlt, base: 10, factor: 10000, scrollThreshold: 9980, renderDigit: (digit) => digit === 0 ? ' ' : (Math.abs(digit) % 10).toString() }),
                        msfsSdk.FSComponent.buildComponent("div", { class: 'altimeter-indicatedalt-box-negative-sign', style: this.negativeSignStyles[2] }, "\u2013")),
                    msfsSdk.FSComponent.buildComponent("div", { class: 'altimeter-indicatedalt-box-digit-container altimeter-indicatedalt-box-thousands' },
                        msfsSdk.FSComponent.buildComponent("div", { class: 'altimeter-indicatedalt-box-digit-bg' }),
                        msfsSdk.FSComponent.buildComponent(msfsSdk.DigitScroller, { ref: thousandsScrollerRef, value: this.indicatedAlt, base: 10, factor: 1000, scrollThreshold: 980, renderDigit: (digit) => digit === 0 ? ' ' : (Math.abs(digit) % 10).toString() }),
                        msfsSdk.FSComponent.buildComponent("div", { class: 'altimeter-indicatedalt-box-negative-sign', style: this.negativeSignStyles[1] }, "\u2013")),
                    msfsSdk.FSComponent.buildComponent("div", { class: 'altimeter-indicatedalt-box-digit-container altimeter-indicatedalt-box-hundreds' },
                        msfsSdk.FSComponent.buildComponent("div", { class: 'altimeter-indicatedalt-box-digit-bg' }),
                        msfsSdk.FSComponent.buildComponent(msfsSdk.DigitScroller, { ref: hundredsScrollerRef, value: this.indicatedAlt, base: 10, factor: 100, scrollThreshold: 80, renderDigit: (digit) => digit === 0 ? ' ' : (Math.abs(digit) % 10).toString() }),
                        msfsSdk.FSComponent.buildComponent("div", { class: 'altimeter-indicatedalt-box-negative-sign', style: this.negativeSignStyles[0] }, "\u2013")),
                    msfsSdk.FSComponent.buildComponent("div", { class: 'altimeter-indicatedalt-box-digit-container altimeter-indicatedalt-box-tens' },
                        msfsSdk.FSComponent.buildComponent("div", { class: 'altimeter-indicatedalt-box-digit-bg' }),
                        msfsSdk.FSComponent.buildComponent(msfsSdk.DigitScroller, { ref: tensScrollerRef, value: this.indicatedAlt, base: 5, factor: 20, renderDigit: (digit) => ((Math.abs(digit) % 5) * 20).toString().padStart(2, '0'), nanString: '––' }),
                        msfsSdk.FSComponent.buildComponent("div", { class: 'altimeter-indicatedalt-box-scroller-mask' })))));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b;
            for (const show of this.showNegativeSign) {
                show.destroy();
            }
            for (const ref of this.scrollerRefs) {
                (_a = ref.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
            }
            this.indicatedAlt.destroy();
            (_b = this.showSub) === null || _b === void 0 ? void 0 : _b.destroy();
            super.destroy();
        }
    }
    /**
     * A metric indicated altitude display for a next-generation (NXi, G3000, etc) Garmin altimeter tape.
     */
    class MetricIndicatedAltDisplay extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.style = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.indicatedAlt = msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.METER.createNumber(0));
        }
        /** @inheritdoc */
        onAfterRender() {
            const indicatedAltPipe = this.indicatedAltPipe = this.props.indicatedAltMeters.pipe(this.indicatedAlt, alt => Math.round(alt), true);
            this.showSub = this.props.show.sub(show => {
                if (show) {
                    this.style.set('display', '');
                    indicatedAltPipe.resume(true);
                }
                else {
                    this.style.set('display', 'none');
                    indicatedAltPipe.pause();
                }
            }, true);
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: 'altimeter-metric-indicatedalt', style: this.style },
                msfsSdk.FSComponent.buildComponent(NumberUnitDisplay, { value: this.indicatedAlt, displayUnit: null, formatter: msfsSdk.NumberFormatter.create({ precision: 1 }), class: 'altimeter-metric-indicatedalt-value' })));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b;
            super.destroy();
            (_a = this.showSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.indicatedAltPipe) === null || _b === void 0 ? void 0 : _b.destroy();
        }
    }
    /**
     * A metric selected altitude display for a next-generation (NXi, G3000, etc) Garmin altimeter tape.
     */
    class MetricSelectedAltitudeDisplay extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.rootStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.textStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.defaultStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.selectedAlt = msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.METER.createNumber(0));
        }
        /** @inheritdoc */
        onAfterRender() {
            this.selectedAltSub = this.props.selectedAltMeters.sub(selectedAlt => {
                if (selectedAlt === null) {
                    this.textStyle.set('display', 'none');
                    this.defaultStyle.set('display', '');
                }
                else {
                    this.textStyle.set('display', '');
                    this.defaultStyle.set('display', 'none');
                    this.selectedAlt.set(Math.round(selectedAlt));
                }
            }, false, true);
            this.showSub = this.props.show.sub(show => {
                if (show) {
                    this.rootStyle.set('display', '');
                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                    this.selectedAltSub.resume(true);
                }
                else {
                    this.rootStyle.set('display', 'none');
                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                    this.selectedAltSub.pause();
                }
            }, true);
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: 'altimeter-metric-selectedalt', style: this.rootStyle },
                msfsSdk.FSComponent.buildComponent("div", { class: 'altimeter-metric-selectedalt-text', style: this.textStyle },
                    msfsSdk.FSComponent.buildComponent(NumberUnitDisplay, { value: this.selectedAlt, displayUnit: null, formatter: msfsSdk.NumberFormatter.create({ precision: 1 }), class: 'altimeter-metric-selectedalt-value' })),
                msfsSdk.FSComponent.buildComponent("div", { class: 'altimeter-metric-selectedalt-default', style: this.defaultStyle }, "\u2013\u2013\u2013\u2013")));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b;
            super.destroy();
            (_a = this.showSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.selectedAltSub) === null || _b === void 0 ? void 0 : _b.destroy();
        }
    }
    /**
     * A radar altimeter ground line for a next-generation (NXi, G3000, etc) Garmin altimeter tape. Depicts the
     * position of the ground on the altitude tape as determined by the radar altimeter.
     */
    class GroundLine extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.style = msfsSdk.ObjectSubject.create({
                display: '',
                position: 'absolute',
                bottom: '0%',
                height: '0%',
                transform: 'rotateX(0deg)'
            });
            this.position = msfsSdk.Subject.create(0);
            this.groundAltitudeRounded = msfsSdk.MappedSubject.create(([indicatedAlt, radarAlt]) => {
                return radarAlt === null ? null : Math.round(indicatedAlt - radarAlt);
            }, this.props.indicatedAlt, this.props.radarAlt).pause();
        }
        /** @inheritdoc */
        onAfterRender() {
            const updateHandler = this.updatePosition.bind(this);
            const groundAltitudeRoundedSub = this.groundAltitudeRounded.sub(updateHandler, false, true);
            const updateEventSub = this.updateEventSub = this.props.updateEvent.on(updateHandler, true);
            this.position.sub(translate => {
                this.style.set('height', `${100 - translate}%`);
            });
            this.showSub = this.props.show.sub(show => {
                if (show) {
                    this.groundAltitudeRounded.resume();
                    groundAltitudeRoundedSub.resume(true);
                    updateEventSub.resume();
                }
                else {
                    this.style.set('display', 'none');
                    this.groundAltitudeRounded.pause();
                    groundAltitudeRoundedSub.pause();
                    updateEventSub.pause();
                }
            }, true);
        }
        /**
         * Updates this line's position on its parent altimeter tape window.
         */
        updatePosition() {
            const groundAltitudeRounded = this.groundAltitudeRounded.get();
            const pos = groundAltitudeRounded === null ? NaN : this.props.getPosition(groundAltitudeRounded);
            if (isNaN(pos) || pos > 1) {
                this.style.set('display', 'none');
            }
            else {
                this.style.set('display', '');
                this.position.set(msfsSdk.MathUtils.round(Math.max(pos, 0) * 100, 0.1));
            }
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: 'altimeter-ground-line', style: this.style }));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b;
            this.groundAltitudeRounded.destroy();
            (_a = this.showSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.updateEventSub) === null || _b === void 0 ? void 0 : _b.destroy();
            super.destroy();
        }
    }
    /**
     * An altitude bug for a next-generation (NXi, G3000, etc) Garmin altimeter tape.
     */
    class AltitudeBug extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.style = msfsSdk.ObjectSubject.create({
                display: '',
                position: 'absolute',
                top: '50%',
                transform: 'translate3d(0, -50%, 0)'
            });
            this.position = msfsSdk.Subject.create(0);
            this.altitudeFeetRounded = this.props.altitudeFeet.map(msfsSdk.SubscribableMapFunctions.withPrecision(1)).pause();
        }
        /** @inheritdoc */
        onAfterRender() {
            const updateHandler = this.updatePosition.bind(this);
            const altitudeFeetRoundedSub = this.altitudeFeetRounded.sub(updateHandler);
            const updateSub = this.updateEventSub = this.props.updateEvent.on(updateHandler, true);
            this.position.sub(translate => {
                this.style.set('top', `${translate}%`);
            });
            this.showSub = this.props.show.sub(show => {
                if (show) {
                    this.altitudeFeetRounded.resume();
                    altitudeFeetRoundedSub.resume();
                    updateSub.resume();
                    this.updatePosition();
                    this.style.set('display', '');
                }
                else {
                    this.altitudeFeetRounded.pause();
                    altitudeFeetRoundedSub.pause();
                    updateSub.pause();
                    this.style.set('display', 'none');
                }
            }, true);
        }
        /**
         * Updates this altitude bug's position on its parent altimeter tape window.
         */
        updatePosition() {
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            const pos = this.props.getPosition(this.altitudeFeetRounded.get());
            this.position.set(msfsSdk.MathUtils.round(pos * 100, 0.1));
        }
        /** @inheritdoc */
        render() {
            var _a;
            let cssClass;
            if (typeof this.props.class === 'object') {
                cssClass = msfsSdk.SetSubject.create(['altimeter-altitude-bug']);
                this.cssClassSub = msfsSdk.FSComponent.bindCssClassSet(cssClass, this.props.class, ['altimeter-altitude-bug']);
            }
            else {
                cssClass = `altimeter-altitude-bug ${(_a = this.props.class) !== null && _a !== void 0 ? _a : ''}`;
            }
            return (msfsSdk.FSComponent.buildComponent("div", { class: cssClass, style: this.style }, this.props.children));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c;
            this.altitudeFeetRounded.destroy();
            (_a = this.cssClassSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.showSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.updateEventSub) === null || _c === void 0 ? void 0 : _c.destroy();
            super.destroy();
        }
    }
    /**
     * A selected altitude bug.
     */
    class SelectedAltitudeBug extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.bugRef = msfsSdk.FSComponent.createRef();
            this.visibilityState = msfsSdk.MappedSubject.create(this.props.show, this.props.selectedAlt).pause();
            this.show = msfsSdk.Subject.create(false);
            this.selectedAltFeet = msfsSdk.Subject.create(0);
        }
        /** @inheritdoc */
        onAfterRender() {
            this.visibilityState.resume();
            this.visibilityState.sub(([show, selectedAlt]) => {
                if (show && selectedAlt !== null) {
                    this.show.set(true);
                    this.selectedAltFeet.set(selectedAlt);
                }
                else {
                    this.show.set(false);
                }
            }, true);
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent(AltitudeBug, { ref: this.bugRef, altitudeFeet: this.selectedAltFeet, show: this.show, updateEvent: this.props.updateEvent, getPosition: (indicatedAlt) => msfsSdk.MathUtils.clamp(this.props.getPosition(indicatedAlt, true), 0, 1), class: 'altimeter-selectedalt-bug' },
                msfsSdk.FSComponent.buildComponent("svg", { viewBox: '0 0 100 100', preserveAspectRatio: 'none', class: 'altimeter-selectedalt-bug-icon' },
                    msfsSdk.FSComponent.buildComponent("path", { d: 'M 95 5 h -90 v 90 h 90 v -30 L 45 50 L 95 30 Z', "vector-effect": 'non-scaling-stroke' }))));
        }
        /** @inheritdoc */
        destroy() {
            var _a;
            (_a = this.bugRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
            this.visibilityState.destroy();
            super.destroy();
        }
    }
    /**
     * A minimums bug.
     */
    class MinimumsBug extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.bugRef = msfsSdk.FSComponent.createRef();
            this.visibilityState = msfsSdk.MappedSubject.create(this.props.show, this.props.minimums).pause();
            this.show = msfsSdk.Subject.create(false);
            this.minimumsFeet = msfsSdk.Subject.create(0);
        }
        /** @inheritdoc */
        onAfterRender() {
            this.visibilityState.resume();
            this.visibilityState.sub(([show, minimums]) => {
                if (show && minimums !== null) {
                    this.show.set(true);
                    this.minimumsFeet.set(minimums);
                }
                else {
                    this.show.set(false);
                }
            }, true);
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent(AltitudeBug, { ref: this.bugRef, altitudeFeet: this.minimumsFeet, show: this.show, updateEvent: this.props.updateEvent, getPosition: (indicatedAlt) => msfsSdk.MathUtils.clamp(this.props.getPosition(indicatedAlt, true), -0.5, 1.5), class: 'altimeter-minimums-bug' },
                msfsSdk.FSComponent.buildComponent("svg", { viewBox: '0 0 20 40', preserveAspectRatio: 'none', class: 'altimeter-minimums-bug-icon' },
                    msfsSdk.FSComponent.buildComponent("path", { d: 'M 5 20 l 8 -5 l 0 -14 l 3 0 l 0 14 l -9 5 l 9 5 l 0 14 l -3 0 l 0 -14 z', "vector-effect": 'non-scaling-stroke' }))));
        }
        /** @inheritdoc */
        destroy() {
            var _a;
            (_a = this.bugRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
            this.visibilityState.destroy();
            super.destroy();
        }
    }
    /**
     * A display for a selected altitude value.
     */
    class SelectedAltitudeDisplay extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.rootStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.textStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.defaultStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.cssClassSet = msfsSdk.SetSubject.create(['altimeter-selectedalt-container']);
            this.selectedAlt = msfsSdk.Subject.create(0);
            this.animationDebounceTimer = new msfsSdk.DebounceTimer();
            this.lastAlertState = undefined;
        }
        /** @inheritdoc */
        onAfterRender() {
            const selectedAltSub = this.selectedAltSub = this.props.selectedAlt.sub(selectedAlt => {
                if (selectedAlt === null) {
                    this.textStyle.set('display', 'none');
                    this.defaultStyle.set('display', '');
                }
                else {
                    this.textStyle.set('display', '');
                    this.defaultStyle.set('display', 'none');
                    this.selectedAlt.set(Math.round(selectedAlt));
                }
            }, false, true);
            this.alertStateSub = this.props.altitudeAlertState.sub(state => {
                this.cssClassSet.delete('alt-alert-within1000-flash');
                this.cssClassSet.delete('alt-alert-within1000');
                this.cssClassSet.delete('alt-alert-deviation-flash');
                this.cssClassSet.delete('alt-alert-deviation');
                // Do not clear the flash animation if we are going from within 200 to captured.
                if (!(state === exports.AltitudeAlertState.Captured && this.lastAlertState === exports.AltitudeAlertState.Within200)) {
                    this.cssClassSet.delete('alt-alert-within200-flash');
                    this.animationDebounceTimer.clear();
                }
                switch (state) {
                    case exports.AltitudeAlertState.Within1000:
                        if (this.lastAlertState === exports.AltitudeAlertState.Armed) {
                            this.cssClassSet.add('alt-alert-within1000-flash');
                            this.animationDebounceTimer.schedule(() => {
                                this.cssClassSet.delete('alt-alert-within1000-flash');
                                this.cssClassSet.add('alt-alert-within1000');
                            }, SelectedAltitudeDisplay.ALERT_FLASH_DURATION);
                        }
                        else {
                            this.cssClassSet.add('alt-alert-within1000');
                        }
                        break;
                    case exports.AltitudeAlertState.Within200:
                        if (this.lastAlertState === exports.AltitudeAlertState.Within1000) {
                            this.cssClassSet.add('alt-alert-within200-flash');
                            this.animationDebounceTimer.schedule(() => {
                                this.cssClassSet.delete('alt-alert-within200-flash');
                            }, SelectedAltitudeDisplay.ALERT_FLASH_DURATION);
                        }
                        break;
                    case exports.AltitudeAlertState.Deviation:
                        this.cssClassSet.add('alt-alert-deviation-flash');
                        this.animationDebounceTimer.schedule(() => {
                            this.cssClassSet.delete('alt-alert-deviation-flash');
                            this.cssClassSet.add('alt-alert-deviation');
                        }, SelectedAltitudeDisplay.ALERT_FLASH_DURATION);
                        break;
                }
                this.lastAlertState = state;
            }, true);
            this.showSub = this.props.show.sub(show => {
                if (show) {
                    this.rootStyle.set('display', '');
                    selectedAltSub.resume(true);
                }
                else {
                    this.rootStyle.set('display', 'none');
                    selectedAltSub.pause();
                }
            }, true);
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: this.cssClassSet, style: this.rootStyle },
                msfsSdk.FSComponent.buildComponent("svg", { class: 'altimeter-selectedalt-icon', viewBox: '0 0 100 100', preserveAspectRatio: 'none' },
                    msfsSdk.FSComponent.buildComponent("path", { d: 'M 95 5 h -90 v 90 h 90 v -25 L 50 50 L 95 25 Z', "vector-effect": 'non-scaling-stroke' })),
                msfsSdk.FSComponent.buildComponent("div", { class: 'altimeter-selectedalt-text', style: this.textStyle },
                    msfsSdk.FSComponent.buildComponent("span", { class: 'altimeter-selectedalt-text-hundreds' }, this.selectedAlt.map(alt => Math.abs(alt) < 100 ? '' : Math.trunc(alt / 100).toString())),
                    msfsSdk.FSComponent.buildComponent("span", { class: 'altimeter-selectedalt-text-tens' }, this.selectedAlt.map(alt => (Math.abs(alt) % 100).toFixed(0).padStart(alt === 0 ? 1 : 2, '0')))),
                msfsSdk.FSComponent.buildComponent("div", { class: 'altimeter-selectedalt-default', style: this.defaultStyle }, "\u2013\u2013\u2013\u2013")));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c;
            super.destroy();
            (_a = this.showSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.selectedAltSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.alertStateSub) === null || _c === void 0 ? void 0 : _c.destroy();
        }
    }
    SelectedAltitudeDisplay.ALERT_FLASH_DURATION = 5000; // milliseconds
    /**
     * A display for altimeter barometric pressure setting.
     */
    class BaroSettingDisplay extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.rootStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.baroSettingValueStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.baroStdStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.baroPreselectStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.cssClassSet = msfsSdk.SetSubject.create(['altimeter-baro-container']);
            this.baroSetting = msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.IN_HG.createNumber(29.92));
            this.baroPreselect = msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.IN_HG.createNumber(29.92));
            this.displayUnit = this.props.isMetric.map(isMetric => isMetric ? msfsSdk.UnitType.HPA : msfsSdk.UnitType.IN_HG);
            this.animationDebounceTimer = new msfsSdk.DebounceTimer();
        }
        /** @inheritdoc */
        onAfterRender() {
            var _a;
            const baroSettingPipe = this.baroSettingPipe = this.props.baroSetting.pipe(this.baroSetting, true);
            this.baroPreselectPipe = (_a = this.props.baroPreselect) === null || _a === void 0 ? void 0 : _a.pipe(this.baroPreselect, true);
            const isStdActiveSub = this.isStdActiveSub = this.props.isStdActive.sub(isStdActive => {
                if (isStdActive) {
                    this.baroSettingValueStyle.set('display', 'none');
                    this.baroStdStyle.set('display', '');
                    this.baroPreselectStyle.set('display', '');
                }
                else {
                    this.baroSettingValueStyle.set('display', '');
                    this.baroStdStyle.set('display', 'none');
                    this.baroPreselectStyle.set('display', 'none');
                }
            }, false, true);
            this.showSub = this.props.show.sub(show => {
                var _a, _b;
                if (show) {
                    this.rootStyle.set('display', '');
                    baroSettingPipe.resume(true);
                    (_a = this.baroPreselectPipe) === null || _a === void 0 ? void 0 : _a.resume(true);
                    isStdActiveSub.resume(true);
                }
                else {
                    this.rootStyle.set('display', 'none');
                    this.animationDebounceTimer.clear();
                    baroSettingPipe.pause();
                    (_b = this.baroPreselectPipe) === null || _b === void 0 ? void 0 : _b.pause();
                    isStdActiveSub.pause();
                }
            }, true);
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: this.cssClassSet, style: this.rootStyle },
                msfsSdk.FSComponent.buildComponent("div", { class: 'altimeter-baro-setting-value', style: this.baroSettingValueStyle },
                    msfsSdk.FSComponent.buildComponent(NumberUnitDisplay, { value: this.baroSetting, displayUnit: this.displayUnit, formatter: (number) => {
                            return (this.props.isMetric.get() ? BaroSettingDisplay.HPA_FORMATTER : BaroSettingDisplay.IN_HG_FORMATTER)(number);
                        } })),
                msfsSdk.FSComponent.buildComponent("div", { class: 'altimeter-baro-std', style: this.baroStdStyle }, "STD BARO"),
                this.props.baroPreselect === undefined
                    ? null
                    : (msfsSdk.FSComponent.buildComponent("div", { class: 'altimeter-baro-preselect', style: this.baroPreselectStyle },
                        msfsSdk.FSComponent.buildComponent("div", { class: 'altimeter-baro-preselect-title' }, "PRE"),
                        msfsSdk.FSComponent.buildComponent(NumberUnitDisplay, { value: this.baroPreselect, displayUnit: this.displayUnit, formatter: (number) => {
                                return (this.props.isMetric.get() ? BaroSettingDisplay.HPA_FORMATTER : BaroSettingDisplay.IN_HG_FORMATTER)(number);
                            }, class: 'altimeter-baro-preselect-value' })))));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c, _d;
            super.destroy();
            this.displayUnit.destroy();
            (_a = this.showSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.baroSettingPipe) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.baroPreselectPipe) === null || _c === void 0 ? void 0 : _c.destroy();
            (_d = this.isStdActiveSub) === null || _d === void 0 ? void 0 : _d.destroy();
        }
    }
    BaroSettingDisplay.IN_HG_FORMATTER = msfsSdk.NumberFormatter.create({ precision: 0.01 });
    BaroSettingDisplay.HPA_FORMATTER = msfsSdk.NumberFormatter.create({ precision: 1 });

    /**
     * A default implementation of {@link AltimeterDataProvider}.
     */
    class DefaultAltimeterDataProvider {
        /**
         * Constructor.
         * @param bus The event bus.
         * @param adcIndex The index of the ADC that is the source of this provider's data.
         * @param options Configuration options for this provider.
         * @param radarAltimeterDataProvider A radar altimeter data provider. If not defined, this data provider will not
         * support radar altitude or radar minimums.
         */
        constructor(bus, adcIndex, options, radarAltimeterDataProvider) {
            this.bus = bus;
            this.radarAltimeterDataProvider = radarAltimeterDataProvider;
            this._indicatedAlt = msfsSdk.ConsumerSubject.create(null, 0);
            /** @inheritdoc */
            this.indicatedAlt = this._indicatedAlt;
            this.verticalSpeed = msfsSdk.ConsumerSubject.create(null, 0);
            this._baroSetting = msfsSdk.ConsumerSubject.create(null, 0);
            /** @inheritdoc */
            this.baroSetting = this._baroSetting;
            this._baroIsStdActive = msfsSdk.ConsumerSubject.create(null, false);
            /** @inheritdoc */
            this.baroIsStdActive = this._baroIsStdActive;
            this._baroPreselect = msfsSdk.ConsumerSubject.create(null, 0);
            /** @inheritdoc */
            this.baroPreselect = this._baroPreselect;
            this.selectedAltSource = msfsSdk.ConsumerSubject.create(null, 0);
            this.selectedAltIsInit = msfsSdk.ConsumerSubject.create(null, false);
            this._selectedAlt = msfsSdk.MappedSubject.create(([selectedAltSource, isInit]) => {
                return isInit ? selectedAltSource : null;
            }, this.selectedAltSource, this.selectedAltIsInit);
            /** @inheritdoc */
            this.selectedAlt = this._selectedAlt;
            this.minimumsMode = msfsSdk.ConsumerSubject.create(null, msfsSdk.MinimumsMode.OFF);
            this.baroMinimumsSource = msfsSdk.ConsumerSubject.create(null, 0);
            this.radarMinimumsSource = msfsSdk.ConsumerSubject.create(null, 0);
            this._minimums = msfsSdk.Subject.create(null);
            /** @inheritdoc */
            this.minimums = this._minimums;
            this._radarAlt = msfsSdk.Subject.create(null);
            this.radarAlt = this._radarAlt;
            this.isAltitudeDataValid = msfsSdk.ConsumerSubject.create(null, false);
            /** @inheritdoc */
            this.isDataFailed = this.isAltitudeDataValid.map(msfsSdk.SubscribableMapFunctions.not());
            this.simTime = msfsSdk.ConsumerSubject.create(null, 0);
            this.isOnGround = msfsSdk.ConsumerSubject.create(null, false);
            this.isInit = false;
            this.isAlive = true;
            this.isPaused = false;
            this.adcIndex = msfsSdk.SubscribableUtils.toSubscribable(adcIndex, true);
            this.trendLookahead = msfsSdk.SubscribableUtils.toSubscribable(options.trendLookahead, true);
            this._altitudeTrend = msfsSdk.MappedSubject.create(([verticalSpeed, lookahead]) => {
                return verticalSpeed * lookahead / 60;
            }, this.verticalSpeed, this.trendLookahead);
            this.altitudeTrend = this._altitudeTrend;
        }
        /**
         * Initializes this data provider. Once initialized, this data provider will continuously update its data until
         * paused or destroyed.
         * @param paused Whether to initialize this data provider as paused. If `true`, this data provider will provide an
         * initial set of data but will not update the provided data until it is resumed. Defaults to `false`.
         * @throws Error if this data provider is dead.
         */
        init(paused = false) {
            if (!this.isAlive) {
                throw new Error('DefaultAltimeterDataProvider: cannot initialize a dead provider');
            }
            if (this.isInit) {
                return;
            }
            this.isInit = true;
            this.isPaused = paused;
            const sub = this.bus.getSubscriber();
            this.simTime.setConsumer(sub.on('simTime'));
            this.isOnGround.setConsumer(sub.on('on_ground'));
            this.adcIndexSub = this.adcIndex.sub(index => {
                this._indicatedAlt.setConsumer(sub.on(`adc_indicated_alt_${index}`));
                this._baroSetting.setConsumer(sub.on(`adc_altimeter_baro_setting_inhg_${index}`));
                this._baroIsStdActive.setConsumer(sub.on(`adc_altimeter_baro_is_std_${index}`));
                this._baroPreselect.setConsumer(sub.on(`adc_altimeter_baro_preselect_inhg_${index}`));
                this.verticalSpeed.setConsumer(sub.on(`adc_vertical_speed_${index}`));
                this.isAltitudeDataValid.setConsumer(sub.on(`adc_altitude_data_valid_${index}`));
            }, true);
            this.selectedAltSource.setConsumer(sub.on('ap_altitude_selected'));
            this.selectedAltIsInit.setConsumer(sub.on('alt_select_is_initialized'));
            this.minimumsMode.setConsumer(sub.on('minimums_mode'));
            this.baroMinimumsSource.setConsumer(sub.on('decision_altitude_feet'));
            const baroMinimums = msfsSdk.MappedSubject.create(([minimumsMode, baroMinimumsSource]) => {
                return minimumsMode === msfsSdk.MinimumsMode.BARO ? baroMinimumsSource : null;
            }, this.minimumsMode, this.baroMinimumsSource);
            if (this.radarAltimeterDataProvider !== undefined) {
                this.radarAltPipe = this.radarAltimeterDataProvider.radarAlt.pipe(this._radarAlt, true);
                this.radarAltIsFailedSub = this.radarAltimeterDataProvider.isDataFailed.sub(isFailed => {
                    if (isFailed) {
                        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                        this.radarAltPipe.pause();
                        this._radarAlt.set(null);
                    }
                    else {
                        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                        this.radarAltPipe.resume(true);
                    }
                }, true);
                this.radarMinimumsSource.setConsumer(sub.on('decision_height_feet'));
                baroMinimums.pause();
                const baroPipe = baroMinimums.pipe(this._minimums, true);
                const radarMinimums = msfsSdk.MappedSubject.create(([minimumsMode, radarMinimumsSource, radarAlt, indicatedAlt]) => {
                    return minimumsMode === msfsSdk.MinimumsMode.RA && radarAlt !== null && !isNaN(radarAlt)
                        ? indicatedAlt - radarAlt + radarMinimumsSource
                        : null;
                }, this.minimumsMode, this.radarMinimumsSource, this.radarAlt, this._indicatedAlt);
                radarMinimums.pause();
                const radarPipe = radarMinimums.pipe(this._minimums, true);
                this.minimumsMode.sub(minimumsMode => {
                    baroMinimums.pause();
                    baroPipe.pause();
                    radarMinimums.pause();
                    radarPipe.pause();
                    switch (minimumsMode) {
                        case msfsSdk.MinimumsMode.BARO:
                            baroMinimums.resume();
                            baroPipe.resume(true);
                            break;
                        case msfsSdk.MinimumsMode.RA:
                            radarMinimums.resume();
                            radarPipe.resume(true);
                            break;
                        default:
                            this._minimums.set(null);
                    }
                }, true);
            }
            else {
                baroMinimums.pipe(this._minimums);
            }
            if (paused) {
                this.pause();
            }
        }
        /**
         * Resumes this data provider. Once resumed, this data provider will continuously update its data until paused or
         * destroyed.
         * @throws Error if this data provider is dead.
         */
        resume() {
            var _a;
            if (!this.isAlive) {
                throw new Error('DefaultAltimeterDataProvider: cannot resume a dead provider');
            }
            if (!this.isPaused || !this.isInit) {
                return;
            }
            this.isPaused = false;
            this.simTime.resume();
            this.isOnGround.resume();
            this._indicatedAlt.resume();
            this._baroSetting.resume();
            this._baroIsStdActive.resume();
            this._baroPreselect.resume();
            this.verticalSpeed.resume();
            this._altitudeTrend.resume();
            this.selectedAltSource.resume();
            this.selectedAltIsInit.resume();
            (_a = this.radarAltIsFailedSub) === null || _a === void 0 ? void 0 : _a.resume(true);
            this.minimumsMode.resume();
            this.baroMinimumsSource.resume();
            this.radarMinimumsSource.resume();
            this.isAltitudeDataValid.resume();
        }
        /**
         * Pauses this data provider. Once paused, this data provider will not update its data until it is resumed.
         * @throws Error if this data provider is dead.
         */
        pause() {
            var _a, _b;
            if (!this.isAlive) {
                throw new Error('DefaultAltimeterDataProvider: cannot pause a dead provider');
            }
            if (this.isPaused || !this.isInit) {
                return;
            }
            this.simTime.pause();
            this.isOnGround.pause();
            this._indicatedAlt.pause();
            this._baroSetting.pause();
            this._baroIsStdActive.pause();
            this._baroPreselect.pause();
            this.verticalSpeed.pause();
            this._altitudeTrend.pause();
            this.selectedAltSource.pause();
            this.selectedAltIsInit.pause();
            (_a = this.radarAltIsFailedSub) === null || _a === void 0 ? void 0 : _a.pause();
            (_b = this.radarAltPipe) === null || _b === void 0 ? void 0 : _b.pause();
            this.minimumsMode.pause();
            this.baroMinimumsSource.pause();
            this.radarMinimumsSource.pause();
            this.isAltitudeDataValid.pause();
            this.isPaused = true;
        }
        /**
         * Destroys this data provider. Once destroyed, this data provider will no longer update its provided data, and can
         * no longer be paused or resumed.
         */
        destroy() {
            var _a, _b, _c;
            this.isAlive = false;
            this.simTime.destroy();
            this.isOnGround.destroy();
            this._indicatedAlt.destroy();
            this._baroSetting.destroy();
            this._baroIsStdActive.destroy();
            this._baroPreselect.destroy();
            this.verticalSpeed.destroy();
            this._altitudeTrend.destroy();
            this.selectedAltSource.destroy();
            this.selectedAltIsInit.destroy();
            this.minimumsMode.destroy();
            this.baroMinimumsSource.destroy();
            this.radarMinimumsSource.destroy();
            this.isAltitudeDataValid.destroy();
            (_a = this.adcIndexSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.radarAltIsFailedSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.radarAltPipe) === null || _c === void 0 ? void 0 : _c.destroy();
        }
    }

    /**
     * A next-generation (NXi, G3000, etc) Garmin PFD radar altimeter.
     */
    class RadarAltimeter extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.rootStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.failedStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.operatingStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.rootCssClass = msfsSdk.SetSubject.create(['radar-altimeter']);
            this.valueText = msfsSdk.Subject.create('');
        }
        /** @inheritdoc */
        onAfterRender() {
            const minimumsAlertSub = this.minimumsAlertSub = this.props.minimumsAlertState.sub(state => {
                this.rootCssClass.delete('minimums-alert-atorbelow');
                if (state === exports.MinimumsAlertState.AtOrBelow) {
                    this.rootCssClass.add('minimums-alert-atorbelow');
                }
            }, false, true);
            const minimumsModeSub = this.minimumsModeSub = this.props.minimumsMode.sub(mode => {
                if (mode === msfsSdk.MinimumsMode.RA) {
                    minimumsAlertSub.resume(true);
                }
                else {
                    minimumsAlertSub.pause();
                    this.rootCssClass.delete('minimums-alert-atorbelow');
                }
            }, false, true);
            const radarAltSub = this.radarAltSub = this.props.dataProvider.radarAlt.sub(radarAlt => {
                this.valueText.set(isNaN(radarAlt) ? '' : radarAlt.toFixed(0));
            }, false, true);
            const isVisibleSub = this.isVisibleSub = this.props.dataProvider.radarAlt.sub(radarAlt => {
                if (isNaN(radarAlt)) {
                    radarAltSub.pause();
                    minimumsModeSub.pause();
                    minimumsAlertSub.pause();
                    this.rootStyle.set('display', 'none');
                }
                else {
                    radarAltSub.resume(true);
                    minimumsModeSub.resume(true);
                    this.rootStyle.set('display', '');
                }
            }, false, true);
            this.failedSub = this.props.dataProvider.isDataFailed.sub(isFailed => {
                if (isFailed) {
                    isVisibleSub.pause();
                    radarAltSub.pause();
                    minimumsModeSub.pause();
                    minimumsAlertSub.pause();
                    this.rootCssClass.delete('minimums-alert-atorbelow');
                    this.rootStyle.set('display', '');
                    this.operatingStyle.set('display', 'none');
                    this.failedStyle.set('display', '');
                }
                else {
                    this.failedStyle.set('display', 'none');
                    this.operatingStyle.set('display', '');
                    isVisibleSub.resume(true);
                }
            }, true);
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: this.rootCssClass, style: this.rootStyle },
                msfsSdk.FSComponent.buildComponent("div", { class: 'radar-altimeter-failed', style: this.failedStyle }, "RA FAIL"),
                msfsSdk.FSComponent.buildComponent("div", { class: 'radar-altimeter-operating', style: this.operatingStyle },
                    msfsSdk.FSComponent.buildComponent("div", { class: 'radar-altimeter-title' }, "RA"),
                    msfsSdk.FSComponent.buildComponent("div", { class: 'radar-altimeter-value' }, this.valueText))));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c, _d, _e;
            super.destroy();
            (_a = this.isVisibleSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.radarAltSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.failedSub) === null || _c === void 0 ? void 0 : _c.destroy();
            (_d = this.minimumsModeSub) === null || _d === void 0 ? void 0 : _d.destroy();
            (_e = this.minimumsAlertSub) === null || _e === void 0 ? void 0 : _e.destroy();
        }
    }

    /**
     * A default implementation of {@link RadarAltimeterDataProvider}.
     */
    class DefaultRadarAltimeterDataProvider {
        /**
         * Constructor.
         * @param bus The event bus.
         */
        constructor(bus) {
            this.bus = bus;
            this.radarAltSource = msfsSdk.ConsumerSubject.create(null, 0);
            this.radarAltRounded = this.radarAltSource.map(radarAlt => {
                if (radarAlt > 2500) {
                    return NaN;
                }
                else {
                    return DefaultRadarAltimeterDataProvider.ROUND_FUNC(radarAlt);
                }
            }, msfsSdk.SubscribableUtils.NUMERIC_NAN_EQUALITY);
            this._radarAlt = msfsSdk.Subject.create(0, msfsSdk.SubscribableUtils.NUMERIC_NAN_EQUALITY);
            /** @inheritdoc */
            this.radarAlt = this._radarAlt;
            this._isDataFailed = msfsSdk.Subject.create(false);
            /** @inheritdoc */
            this.isDataFailed = this._isDataFailed;
            this.servo = new msfsSdk.LinearServo(150);
            this.currentValue = NaN;
            this.isInit = false;
            this.isAlive = true;
            this.isPaused = false;
        }
        /**
         * Initializes this data provider. Once initialized, this data provider will continuously update its data until
         * paused or destroyed.
         * @param paused Whether to initialize this data provider as paused. If `true`, this data provider will provide an
         * initial set of data but will not update the provided data until it is resumed. Defaults to `false`.
         * @throws Error if this data provider is dead.
         */
        init(paused = false) {
            if (!this.isAlive) {
                throw new Error('DefaultRadarAltimeterDataProvider: cannot initialize a dead provider');
            }
            if (this.isInit) {
                return;
            }
            this.isInit = true;
            this.isPaused = paused;
            const sub = this.bus.getSubscriber();
            this.radarAltSystemStateSub = sub.on('radaralt_state_1').handle(state => {
                if (state.current === undefined || state.current === msfsSdk.AvionicsSystemState.On) {
                    this._isDataFailed.set(false);
                }
                else {
                    this._isDataFailed.set(true);
                }
            });
            this.radarAltSource.setConsumer(sub.on('radaralt_radio_alt_1'));
            this.radarAltRoundedSub = this.radarAltRounded.sub(() => {
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                this.radarAltRoundedSub.pause();
                this.servo.reset(); // reset servo's internal clock
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                this.clockSub.resume(true);
            }, false, true);
            this.clockSub = sub.on('realTime').handle(() => {
                const rounded = this.radarAltRounded.get();
                if (isNaN(rounded) || isNaN(this.currentValue)) {
                    this.currentValue = this.servo.drive(rounded, rounded);
                }
                else {
                    this.currentValue = this.servo.drive(this.currentValue, rounded);
                }
                this._radarAlt.set(isNaN(this.currentValue) ? NaN : DefaultRadarAltimeterDataProvider.ROUND_FUNC(this.currentValue));
                if (isNaN(this.currentValue) || this.currentValue === rounded) {
                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                    this.clockSub.pause();
                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                    this.radarAltRoundedSub.resume();
                }
            }, true);
            this.radarAltRoundedSub.resume(true);
            if (paused) {
                this.pause();
            }
        }
        /**
         * Resumes this data provider. Once resumed, this data provider will continuously update its data until paused or
         * destroyed.
         * @throws Error if this data provider is dead.
         */
        resume() {
            var _a, _b;
            if (!this.isAlive) {
                throw new Error('DefaultRadarAltimeterDataProvider: cannot resume a dead provider');
            }
            if (!this.isPaused || !this.isInit) {
                return;
            }
            this.isPaused = false;
            this.radarAltSource.resume();
            (_a = this.radarAltRoundedSub) === null || _a === void 0 ? void 0 : _a.resume(true);
            (_b = this.radarAltSystemStateSub) === null || _b === void 0 ? void 0 : _b.resume(true);
        }
        /**
         * Pauses this data provider. Once paused, this data provider will not update its data until it is resumed.
         * @throws Error if this data provider is dead.
         */
        pause() {
            var _a, _b;
            if (!this.isAlive) {
                throw new Error('DefaultRadarAltimeterDataProvider: cannot pause a dead provider');
            }
            if (this.isPaused || !this.isInit) {
                return;
            }
            (_a = this.radarAltSystemStateSub) === null || _a === void 0 ? void 0 : _a.pause();
            this.radarAltSource.pause();
            (_b = this.clockSub) === null || _b === void 0 ? void 0 : _b.pause();
            this.currentValue = NaN;
            this.isPaused = true;
        }
        /**
         * Destroys this data provider. Once destroyed, this data provider will no longer update its provided data, and can
         * no longer be paused or resumed.
         */
        destroy() {
            var _a, _b;
            this.isAlive = false;
            this.radarAltSource.destroy();
            (_a = this.clockSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.radarAltSystemStateSub) === null || _b === void 0 ? void 0 : _b.destroy();
        }
    }
    DefaultRadarAltimeterDataProvider.ROUND_FUNC = (radarAlt) => {
        if (radarAlt > 1500) {
            return msfsSdk.MathUtils.round(radarAlt, 50);
        }
        else if (radarAlt > 200) {
            return msfsSdk.MathUtils.round(radarAlt, 10);
        }
        else {
            return Math.max(msfsSdk.MathUtils.round(radarAlt, 5), 0);
        }
    };

    /**
     * A default implementation of {@link AoaDataProvider}.
     */
    class DefaultAoaDataProvider {
        /**
         * Constructor.
         * @param bus The event bus.
         * @param aoaIndex The index of the AoA computer that is the source of this provider's data.
         * @param adcIndex The index of the ADC that is the source of this provider's data.
         */
        constructor(bus, aoaIndex, adcIndex) {
            this.bus = bus;
            this._aoa = msfsSdk.ConsumerSubject.create(null, 0);
            /** @inheritdoc */
            this.aoa = this._aoa;
            this._normAoa = msfsSdk.ConsumerSubject.create(null, 0);
            /** @inheritdoc */
            this.normAoa = this._normAoa;
            this._stallAoa = msfsSdk.ConsumerSubject.create(null, 0);
            /** @inheritdoc */
            this.stallAoa = this._stallAoa;
            this._zeroLiftAoa = msfsSdk.ConsumerSubject.create(null, 0);
            /** @inheritdoc */
            this.zeroLiftAoa = this._zeroLiftAoa;
            this.ias = msfsSdk.ConsumerSubject.create(null, 0);
            this._isOnGround = msfsSdk.ConsumerSubject.create(null, false);
            /** @inheritdoc */
            this.isOnGround = this._isOnGround;
            this.aoaCoefSmoother = new msfsSdk.ExpSmoother(DefaultAoaDataProvider.AOA_COEF_SMOOTHING_TAU);
            this._normAoaIasCoef = msfsSdk.Subject.create(null);
            /** @inheritdoc */
            this.normAoaIasCoef = this._normAoaIasCoef;
            this._isDataFailed = msfsSdk.Subject.create(false);
            /** @inheritdoc */
            this.isDataFailed = this._isDataFailed;
            this.aoaSystemState = msfsSdk.ConsumerSubject.create(null, { previous: undefined, current: undefined });
            this.adcSystemState = msfsSdk.ConsumerSubject.create(null, { previous: undefined, current: undefined });
            this.isAdcDataFailed = false;
            this.isInit = false;
            this.isAlive = true;
            this.isPaused = false;
            this.aoaIndex = msfsSdk.SubscribableUtils.toSubscribable(aoaIndex, true);
            this.adcIndex = msfsSdk.SubscribableUtils.toSubscribable(adcIndex, true);
        }
        /**
         * Initializes this data provider. Once initialized, this data provider will continuously update its data until
         * paused or destroyed.
         * @param paused Whether to initialize this data provider as paused. If `true`, this data provider will provide an
         * initial set of data but will not update the provided data until it is resumed. Defaults to `false`.
         * @throws Error if this data provider is dead.
         */
        init(paused = false) {
            if (!this.isAlive) {
                throw new Error('DefaultAoaDataProvider: cannot initialize a dead provider');
            }
            if (this.isInit) {
                return;
            }
            this.isInit = true;
            this.isPaused = paused;
            const sub = this.bus.getSubscriber();
            this._isOnGround.setConsumer(sub.on('on_ground'));
            this.aoaIndexSub = this.aoaIndex.sub(index => {
                this._aoa.setConsumer(sub.on(`aoa_aoa_${index}`));
                this._normAoa.setConsumer(sub.on(`aoa_norm_aoa_${index}`));
                this._stallAoa.setConsumer(sub.on(`aoa_stall_aoa_${index}`));
                this._zeroLiftAoa.setConsumer(sub.on(`aoa_zero_lift_aoa_${index}`));
                this.aoaSystemState.setConsumer(sub.on(`aoa_state_${index}`));
            }, true);
            this.adcIndexSub = this.adcIndex.sub(index => {
                this.ias.setConsumer(sub.on(`adc_ias_${index}`));
                this.adcSystemState.setConsumer(sub.on(`adc_state_${index}`));
            }, true);
            this.aoaSystemState.sub(state => {
                if (state.current === undefined || state.current === msfsSdk.AvionicsSystemState.On) {
                    this._isDataFailed.set(false);
                }
                else {
                    this._isDataFailed.set(true);
                }
            }, true);
            this.adcSystemState.sub(state => {
                this.isAdcDataFailed = !(state.current === undefined || state.current === msfsSdk.AvionicsSystemState.On);
            }, true);
            this.clockSub = sub.on('realTime').handle(this.update.bind(this));
            if (paused) {
                this.pause();
            }
        }
        /** @inheritdoc */
        aoaToNormAoa(aoa) {
            const zeroLiftAoa = this._zeroLiftAoa.get();
            return (aoa - zeroLiftAoa) / (this._stallAoa.get() - zeroLiftAoa);
        }
        /** @inheritdoc */
        normAoaToAoa(normAoa) {
            const zeroLiftAoa = this._zeroLiftAoa.get();
            return normAoa * (this._stallAoa.get() - zeroLiftAoa) + zeroLiftAoa;
        }
        /** @inheritdoc */
        estimateIasFromAoa(aoa) {
            return this.estimateIasFromNormAoa(this.aoaToNormAoa(aoa));
        }
        /** @inheritdoc */
        estimateIasFromNormAoa(normAoa) {
            var _a;
            return Math.sqrt(((_a = this._normAoaIasCoef.get()) !== null && _a !== void 0 ? _a : NaN) / normAoa);
        }
        /**
         * Resumes this data provider. Once resumed, this data provider will continuously update its data until paused or
         * destroyed.
         * @throws Error if this data provider is dead.
         */
        resume() {
            var _a;
            if (!this.isAlive) {
                throw new Error('DefaultAoaDataProvider: cannot resume a dead provider');
            }
            if (!this.isPaused) {
                return;
            }
            this.isPaused = false;
            this._aoa.resume();
            this._normAoa.resume();
            this._stallAoa.resume();
            this._zeroLiftAoa.resume();
            this.ias.resume();
            this._isOnGround.resume();
            this.aoaSystemState.resume();
            this.adcSystemState.resume();
            (_a = this.clockSub) === null || _a === void 0 ? void 0 : _a.resume(true);
        }
        /**
         * Pauses this data provider. Once paused, this data provider will not update its data until it is resumed.
         * @throws Error if this data provider is dead.
         */
        pause() {
            var _a;
            if (!this.isAlive) {
                throw new Error('DefaultAoaDataProvider: cannot pause a dead provider');
            }
            if (this.isPaused) {
                return;
            }
            this._aoa.pause();
            this._normAoa.pause();
            this._stallAoa.pause();
            this._zeroLiftAoa.pause();
            this.ias.pause();
            this._isOnGround.pause();
            this.aoaSystemState.pause();
            this.adcSystemState.pause();
            (_a = this.clockSub) === null || _a === void 0 ? void 0 : _a.pause();
            this.lastAoaCoefTime = undefined;
            this.aoaCoefSmoother.reset();
            this.isPaused = true;
        }
        /**
         * Updates this data provider.
         * @param time The current real (operating system) time, as a UNIX timestamp in milliseconds.
         */
        update(time) {
            if (this._isDataFailed.get() || this.isAdcDataFailed || this._isOnGround.get()) {
                this._normAoaIasCoef.set(null);
                this.aoaCoefSmoother.reset();
                this.lastAoaCoefTime = undefined;
                return;
            }
            const dt = this.lastAoaCoefTime === undefined ? 0 : Math.max(0, time - this.lastAoaCoefTime);
            this.lastAoaCoefTime = time;
            const normAoa = this._normAoa.get();
            const ias = this.ias.get();
            const iasSquared = ias * ias;
            const coef = normAoa * iasSquared;
            this._normAoaIasCoef.set(this.aoaCoefSmoother.next(coef, dt));
        }
        /**
         * Destroys this data provider. Once destroyed, this data provider will no longer update its provided data, and can
         * no longer be paused or resumed.
         */
        destroy() {
            var _a, _b, _c;
            this.isAlive = false;
            this._aoa.destroy();
            this._normAoa.destroy();
            this._stallAoa.destroy();
            this._zeroLiftAoa.destroy();
            this.ias.destroy();
            this._isOnGround.destroy();
            this.aoaSystemState.destroy();
            this.adcSystemState.destroy();
            (_a = this.aoaIndexSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.adcIndexSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.clockSub) === null || _c === void 0 ? void 0 : _c.destroy();
        }
    }
    DefaultAoaDataProvider.AOA_COEF_SMOOTHING_TAU = 2000 / Math.LN2;

    /**
     * A next-generation (NXi, G3000, etc) Garmin angle of attack indicator.
     */
    class AoaIndicator extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.rootStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.needleStyle = msfsSdk.ObjectSubject.create({
                transform: 'rotate3d(0, 0, 1, 0deg)'
            });
            this.rootCssClass = msfsSdk.SetSubject.create(['aoa', this.props.advanced ? 'aoa-advanced' : 'aoa-simple']);
            this.rotationSign = this.props.advanced ? -1 : 1;
            this.minAoa = this.props.advanced ? 0.2 : 0;
            this.aoaRange = 1 - this.minAoa;
            this.angleRange = this.props.advanced ? 180 : 90;
            this.redMin = 0.9;
            this.yellowMin = this.props.advanced ? 0.7 : 0.9;
            this.needleRotation = msfsSdk.Subject.create(0);
        }
        /** @inheritdoc */
        onAfterRender() {
            this.needleRotation.sub(rotation => {
                this.needleStyle.set('transform', `rotate3d(0, 0, 1, ${rotation}deg)`);
            }, true);
            const aoaSub = this.aoaSub = this.props.dataProvider.normAoa.sub(aoa => {
                const angle = msfsSdk.MathUtils.clamp((aoa - this.minAoa) / this.aoaRange, 0, 1) * this.angleRange * this.rotationSign;
                this.needleRotation.set(msfsSdk.MathUtils.round(angle, 0.1));
                if (aoa >= this.redMin) {
                    this.rootCssClass.delete('aoa-yellow');
                    this.rootCssClass.add('aoa-red');
                }
                else if (aoa >= this.yellowMin) {
                    this.rootCssClass.delete('aoa-red');
                    this.rootCssClass.add('aoa-yellow');
                }
                else {
                    this.rootCssClass.delete('aoa-red');
                    this.rootCssClass.delete('aoa-yellow');
                }
            }, false, true);
            const isDataFailedSub = this.isDataFailedSub = this.props.dataProvider.isDataFailed.sub(isFailed => {
                if (isFailed) {
                    this.rootCssClass.add('aoa-data-failed');
                    aoaSub.pause();
                }
                else {
                    this.rootCssClass.delete('aoa-data-failed');
                    aoaSub.resume(true);
                }
            }, false, true);
            this.declutterSub = this.props.declutter.sub(declutter => {
                if (declutter) {
                    this.rootStyle.set('display', 'none');
                    isDataFailedSub.pause();
                    aoaSub.pause();
                }
                else {
                    this.rootStyle.set('display', '');
                    isDataFailedSub.resume(true);
                }
            }, true);
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: this.rootCssClass, style: this.rootStyle },
                msfsSdk.FSComponent.buildComponent("div", { class: 'aoa-label' }, "AOA"),
                this.props.advanced ? this.renderAdvancedGauge() : this.renderSimpleGauge(),
                msfsSdk.FSComponent.buildComponent("div", { class: 'failed-box' })));
        }
        /**
         * Renders the simple gauge version for this indicator.
         * @returns The simple gauge version for this indicator, as a VNode.
         */
        renderSimpleGauge() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: 'aoa-gauge' },
                msfsSdk.FSComponent.buildComponent("svg", { viewBox: '-70 -70 140 140', class: 'aoa-gauge-scale' },
                    msfsSdk.FSComponent.buildComponent("path", { class: 'aoa-gauge-tick aoa-gauge-tick-white', d: 'M -39.67 -54.61 L -48.03 -66.1', "stroke-width": '2' }),
                    msfsSdk.FSComponent.buildComponent("path", { class: 'aoa-gauge-arc aoa-gauge-arc-white', d: 'M -67.5 0 A 67.5 67.5 90 0 1 -10.56 -66.67', "stroke-width": '5' }),
                    msfsSdk.FSComponent.buildComponent("path", { class: 'aoa-gauge-arc aoa-gauge-arc-red', d: 'M -10.56 -66.67 A 67.5 67.5 90 0 1 0 -67.5', "stroke-width": '5' })),
                msfsSdk.FSComponent.buildComponent("svg", { viewBox: '-70 -70 140 140', class: 'aoa-gauge-needle', style: this.needleStyle },
                    msfsSdk.FSComponent.buildComponent("path", { d: 'M -67.5 0 l 12 -6 l 0 12 z', "stroke-width": '0.5' }))));
        }
        /**
         * Renders the advanced gauge version for this indicator.
         * @returns The advanced gauge version for this indicator, as a VNode.
         */
        renderAdvancedGauge() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: 'aoa-gauge' },
                msfsSdk.FSComponent.buildComponent("svg", { viewBox: '-40 -40 80 80', class: 'aoa-gauge-scale' },
                    msfsSdk.FSComponent.buildComponent("path", { class: 'aoa-gauge-tick aoa-gauge-tick-white', d: 'M 0 37 l 0 -10 M 26.16 26.16 l -7.07 -7.07 M 37 0 l -10 0', "stroke-width": '2' }),
                    msfsSdk.FSComponent.buildComponent("path", { class: 'aoa-gauge-tick aoa-gauge-tick-yellow', d: 'M 26.16 -26.16 l -7.07 7.07', "stroke-width": '2' }),
                    msfsSdk.FSComponent.buildComponent("path", { class: 'aoa-gauge-tick aoa-gauge-tick-red', d: 'M 0 -37 l 0 10', "stroke-width": '2' }),
                    msfsSdk.FSComponent.buildComponent("path", { class: 'aoa-gauge-arc aoa-gauge-arc-white', d: 'M 0 35 A 35 35 0 0 0 32.34 -13.39', "stroke-width": '4' }),
                    msfsSdk.FSComponent.buildComponent("path", { class: 'aoa-gauge-arc aoa-gauge-arc-yellow', d: 'M 32.34 -13.39 A 35 35 0 0 0 13.39 -32.34', "stroke-width": '4' }),
                    msfsSdk.FSComponent.buildComponent("path", { class: 'aoa-gauge-arc aoa-gauge-arc-red', d: 'M 13.39 -32.34 A 35 35 0 0 0 0 -35', "stroke-width": '4' }),
                    msfsSdk.FSComponent.buildComponent("text", { x: '0', y: '23', class: 'aoa-gauge-label', "text-anchor": 'middle', "dominant-baseline": 'auto' }, ".2"),
                    msfsSdk.FSComponent.buildComponent("text", { x: '23', y: '0', class: 'aoa-gauge-label', "text-anchor": 'end', "dominant-baseline": 'central' }, ".6"),
                    msfsSdk.FSComponent.buildComponent("text", { x: '0', y: '-23', class: 'aoa-gauge-label', "text-anchor": 'middle', "dominant-baseline": 'hanging' }, "1.0")),
                msfsSdk.FSComponent.buildComponent("svg", { viewBox: '-40 -40 80 80', class: 'aoa-gauge-needle', style: this.needleStyle },
                    msfsSdk.FSComponent.buildComponent("path", { d: 'M 0.5 2 l 2.5 5 q 0.5 1 0 4 l -3 22 l -3 -22 q -0.5 -3 0 -4 l 2.5 -5 q 0.5 -1 1 0' }))));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c;
            (_a = this.aoaSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.isDataFailedSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.declutterSub) === null || _c === void 0 ? void 0 : _c.destroy();
            super.destroy();
        }
    }

    /**
     * Display states for an FMA master display slot.
     */
    exports.FmaMasterSlotState = void 0;
    (function (FmaMasterSlotState) {
        FmaMasterSlotState["Off"] = "Off";
        FmaMasterSlotState["On"] = "On";
        FmaMasterSlotState["Failed"] = "Failed";
    })(exports.FmaMasterSlotState || (exports.FmaMasterSlotState = {}));
    /**
     * An FMA master display slot.
     */
    class FmaMasterSlot extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.rootStyle = msfsSdk.ObjectSubject.create({
                visibility: 'hidden'
            });
            this.rootCssClass = msfsSdk.SetSubject.create(['fma-master']);
            this.alertTimer = new msfsSdk.DebounceTimer();
            this.isStateOn = false;
        }
        /** @inheritdoc */
        onAfterRender() {
            this.stateSub = this.props.state.sub(this.onStateChanged.bind(this), true);
        }
        /**
         * Responds to changes in this slot's state.
         * @param state The slot's current state.
         */
        onStateChanged(state) {
            this.alertTimer.clear();
            switch (state) {
                case exports.FmaMasterSlotState.Failed:
                    this.alertTimer.clear();
                    this.rootCssClass.delete('fma-master-alert');
                    this.rootCssClass.add('fma-master-failed');
                    this.rootStyle.set('visibility', '');
                    break;
                case exports.FmaMasterSlotState.On:
                    this.alertTimer.clear();
                    this.rootCssClass.delete('fma-master-alert');
                    this.rootCssClass.delete('fma-master-failed');
                    this.rootStyle.set('visibility', '');
                    break;
                case exports.FmaMasterSlotState.Off:
                    this.rootCssClass.delete('fma-master-failed');
                    if (this.isStateOn) {
                        this.rootCssClass.add('fma-master-alert');
                        this.alertTimer.schedule(() => {
                            this.rootCssClass.delete('fma-master-alert');
                            this.rootStyle.set('visibility', 'hidden');
                        }, FmaMasterSlot.ALERT_DURATION);
                    }
                    else {
                        this.alertTimer.clear();
                        this.rootCssClass.delete('fma-master-alert');
                        this.rootStyle.set('visibility', 'hidden');
                    }
                    break;
            }
            this.isStateOn = state === exports.FmaMasterSlotState.On;
        }
        /** @inheritdoc */
        render() {
            if (this.props.class !== undefined) {
                if (typeof this.props.class === 'string') {
                    msfsSdk.FSComponent.parseCssClassesFromString(this.props.class, cssClass => !FmaMasterSlot.RESERVED_CSS_CLASSES.includes(cssClass))
                        .forEach(cssClass => { this.rootCssClass.add(cssClass); });
                }
                else {
                    this.cssClassSub = msfsSdk.FSComponent.bindCssClassSet(this.rootCssClass, this.props.class, FmaMasterSlot.RESERVED_CSS_CLASSES);
                }
            }
            return (msfsSdk.FSComponent.buildComponent("div", { class: this.rootCssClass, style: this.rootStyle }, this.props.children));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b;
            (_a = this.cssClassSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.stateSub) === null || _b === void 0 ? void 0 : _b.destroy();
            super.destroy();
        }
    }
    FmaMasterSlot.RESERVED_CSS_CLASSES = ['fma-master', 'fma-master-alert', 'fma-master-failed'];
    FmaMasterSlot.ALERT_DURATION = 5000; // milliseconds

    /**
     * An FMA mode display slot.
     */
    class FmaModeSlot extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.rootCssClass = msfsSdk.SetSubject.create(['fma-mode']);
            this.activeModeText = msfsSdk.Subject.create('');
            this.alertTimer = new msfsSdk.DebounceTimer();
        }
        /** @inheritdoc */
        onAfterRender() {
            this.activeModeSub = this.props.active.sub(data => {
                this.onActiveModeChanged(data.active, data.armedTransition, data.secondaryArmedTransition, data.failed);
            }, true);
        }
        /**
         * Responds to changes in this slot's active mode.
         * @param active The active mode.
         * @param armedTransition The armed mode from which the active mode transitioned.
         * @param secondaryArmedTransition The secondary armed mode from which the active mode transitioned.
         * @param failed The failed mode that caused the transition to the current active mode.
         */
        onActiveModeChanged(active, armedTransition, secondaryArmedTransition, failed) {
            if (failed !== undefined) {
                this.activeModeText.set(failed);
                this.rootCssClass.delete('fma-mode-alert');
                this.rootCssClass.add('fma-mode-failed');
                this.alertTimer.schedule(() => {
                    this.rootCssClass.delete('fma-mode-failed');
                    this.onActiveModeChanged(this.props.active.get().active, undefined, undefined, undefined);
                }, FmaModeSlot.FAILED_DURATION);
            }
            else {
                this.alertTimer.clear();
                this.rootCssClass.delete('fma-mode-failed');
                this.activeModeText.set(active);
                if (armedTransition === active || secondaryArmedTransition === active || this.isDualVerticalTransition(active, secondaryArmedTransition)) {
                    this.rootCssClass.add('fma-mode-alert');
                    this.alertTimer.schedule(() => {
                        this.rootCssClass.delete('fma-mode-alert');
                    }, FmaModeSlot.ALERT_DURATION);
                }
                else {
                    this.rootCssClass.delete('fma-mode-alert');
                }
            }
        }
        /**
         * Checks whether the active mode transitioned from a combined dual vertical armed mode.
         * @param active The active mode.
         * @param secondaryArmedTransition The secondary armed mode from which the active mode transitioned.
         * @returns Whether the active mode transitioned from a combined dual vertical armed mode.
         */
        isDualVerticalTransition(active, secondaryArmedTransition) {
            return (secondaryArmedTransition === 'GP/V' && (active === 'GP' || active === 'VPTH'))
                || (secondaryArmedTransition === 'GS/V' && (active === 'GS' || active === 'VPTH'));
        }
        /** @inheritdoc */
        render() {
            if (this.props.class !== undefined) {
                const reservedClasses = ['fma-mode', 'fma-mode-alert', 'fma-mode-failed'];
                if (typeof this.props.class === 'string') {
                    msfsSdk.FSComponent.parseCssClassesFromString(this.props.class)
                        .filter(cssClass => !reservedClasses.includes(cssClass))
                        .forEach(cssClass => { this.rootCssClass.add(cssClass); });
                }
                else {
                    this.cssClassSub = msfsSdk.FSComponent.bindCssClassSet(this.rootCssClass, this.props.class, reservedClasses);
                }
            }
            return (msfsSdk.FSComponent.buildComponent("div", { class: this.rootCssClass }, this.activeModeText));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c;
            (_a = this.cssClassSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.activeModeSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.isFailedSub) === null || _c === void 0 ? void 0 : _c.destroy();
            super.destroy();
        }
    }
    FmaModeSlot.ALERT_DURATION = 10000; // milliseconds
    FmaModeSlot.FAILED_DURATION = 5000; // milliseconds

    /**
     * A PFD artificial horizon. Renders sky and ground boxes.
     */
    class ArtificialHorizon extends msfsSdk.HorizonSharedCanvasSubLayer {
        constructor() {
            super(...arguments);
            this.vec2Cache = [msfsSdk.Vec2Math.create(), msfsSdk.Vec2Math.create(), msfsSdk.Vec2Math.create(), msfsSdk.Vec2Math.create()];
            this.bgTranslation = msfsSdk.Vec2Math.create();
            this.bgRotation = 0;
            this.windowTransform = new msfsSdk.Transform2D();
            this.groundColors = this.props.options.groundColors
                ? ArtificialHorizon.createColorGradient(this.props.options.groundColors)
                : undefined;
            this.skyColors = this.props.options.skyColors
                ? ArtificialHorizon.createColorGradient(this.props.options.skyColors)
                : undefined;
            this.needUpdate = false;
        }
        /** @inheritdoc */
        onAttached() {
            super.onAttached();
            this.showSub = this.props.show.sub(() => { this.needUpdate = true; }, true);
            this.needUpdate = true;
        }
        /** @inheritdoc */
        onProjectionChanged(projection, changeFlags) {
            if (msfsSdk.BitFlags.isAny(changeFlags, ArtificialHorizon.UPDATE_FLAGS)) {
                this.needUpdate = true;
            }
        }
        /** @inheritdoc */
        shouldInvalidate() {
            return this.needUpdate && this.isVisible();
        }
        /** @inheritdoc */
        onUpdated() {
            if (!this.display.isInvalidated || !this.isVisible()) {
                return;
            }
            if (this.props.show.get()) {
                // Approximate translation due to pitch using a constant pitch resolution (pixels per degree of pitch) derived
                // from the projection's current field of view. The error of this approximation increases with the absolute
                // deviation of the pitch angle from 0 degrees. We do this instead of simply projecting the true horizon line
                // because we need to keep the line in sync with the attitude pitch ladder, which uses the same approximation.
                const projection = this.projection;
                const pitchResolution = projection.getScaleFactor() / projection.getFov();
                const pitch = projection.getPitch();
                const roll = projection.getRoll();
                msfsSdk.Vec2Math.set(0, pitchResolution * pitch, this.bgTranslation);
                this.bgRotation = -roll;
                this.drawHorizonRects(this.display.context, projection);
            }
            this.needUpdate = false;
        }
        /**
         * Draws the horizon rects.
         * @param context The canvas rendering context to which to draw.
         * @param projection The horizon projection.
         */
        drawHorizonRects(context, projection) {
            const projectedCenter = projection.getOffsetCenterProjected();
            const projectedSize = projection.getProjectedSize();
            this.windowTransform.toIdentity();
            const transform = this.windowTransform
                .addTranslation(-projectedCenter[0], -projectedCenter[1])
                .addRotation(-this.bgRotation * Avionics.Utils.DEG2RAD)
                .addTranslation(-this.bgTranslation[0], -this.bgTranslation[1]);
            const windowUl = transform.apply(msfsSdk.Vec2Math.set(0, 0, this.vec2Cache[0]), this.vec2Cache[0]);
            const windowUr = transform.apply(msfsSdk.Vec2Math.set(projectedSize[0], 0, this.vec2Cache[1]), this.vec2Cache[1]);
            const windowLl = transform.apply(msfsSdk.Vec2Math.set(0, projectedSize[1], this.vec2Cache[2]), this.vec2Cache[2]);
            const windowLr = transform.apply(msfsSdk.Vec2Math.set(projectedSize[0], projectedSize[1], this.vec2Cache[3]), this.vec2Cache[3]);
            const minX = Math.min(windowUl[0], windowUr[0], windowLl[0], windowLr[0]);
            const maxX = Math.max(windowUl[0], windowUr[0], windowLl[0], windowLr[0]);
            const minY = Math.min(windowUl[1], windowUr[1], windowLl[1], windowLr[1]);
            const maxY = Math.max(windowUl[1], windowUr[1], windowLl[1], windowLr[1]);
            const inverted = transform.invert();
            const invertedParams = inverted.getParameters();
            context.setTransform(invertedParams[0], invertedParams[3], invertedParams[1], invertedParams[4], invertedParams[2], invertedParams[5]);
            if (maxY > 0) {
                if (this.groundColors) {
                    this.drawGradientRect(context, this.groundColors, minX, maxX, maxY);
                }
                else if (this.props.options.groundColor) {
                    this.drawSolidRect(context, this.props.options.groundColor, minX, maxX, maxY);
                }
            }
            if (minY < 0) {
                if (this.skyColors) {
                    this.drawGradientRect(context, this.skyColors, minX, maxX, minY);
                }
                else if (this.props.options.skyColor) {
                    this.drawSolidRect(context, this.props.options.skyColor, minX, maxX, minY);
                }
            }
            context.resetTransform();
        }
        /**
         * Draws a solid rectangle.
         * @param context The canvas rendering context to which to draw.
         * @param color The color of the rectangle.
         * @param x1 The minimum x-coordinate to which to extend the rectangle.
         * @param x2 The maximum x-coordinate to which to extend the rectangle.
         * @param y The y-coordinate to which to extend the rectangle.
         */
        drawSolidRect(context, color, x1, x2, y) {
            const width = x2 - x1;
            const height = y * Math.sign(y);
            context.fillStyle = color;
            context.fillRect(x1, Math.min(y, 0), width, height);
        }
        /**
         * Draws a gradient rectangle.
         * @param context The canvas rendering context to which to draw.
         * @param stops The interpolated color stops of the rectangle gradient.
         * @param x1 The minimum x-coordinate to which to extend the rectangle.
         * @param x2 The maximum x-coordinate to which to extend the rectangle.
         * @param y The y-coordinate to which to extend the rectangle.
         */
        drawGradientRect(context, stops, x1, x2, y) {
            // We avoid using actual CanvasGradient objects to render the gradient because they are bugged in Coherent.
            const sign = y < 0 ? -1 : 1;
            const width = x2 - x1;
            const maxDistance = y * sign;
            let prevDistance = 0;
            for (let i = 0; i < stops.length; i++) {
                const stop = stops[i];
                // Overlap each stripe with the next one to avoid sub-pixel rendering artifacts.
                const currentDistance = Math.min(stop.distance, maxDistance) + 1;
                const height = currentDistance - prevDistance;
                if (height > 0) {
                    context.fillStyle = stop.color;
                    context.fillRect(x1, Math.min(prevDistance * sign, currentDistance * sign), width, height);
                }
                if (stop.distance >= maxDistance) {
                    break;
                }
                prevDistance = stop.distance;
            }
            const lastStop = stops[stops.length - 1];
            if (lastStop && prevDistance < maxDistance) {
                const currentDistance = maxDistance + 1;
                const height = currentDistance - prevDistance;
                context.fillStyle = lastStop.color;
                context.fillRect(x1, Math.min(prevDistance * sign, currentDistance * sign), width, height);
            }
        }
        /** @inheritdoc */
        destroy() {
            var _a;
            (_a = this.showSub) === null || _a === void 0 ? void 0 : _a.destroy();
            super.destroy();
        }
        /**
         * Creates an array of interpolated color gradient stops, ordered by increasing distance from the horizon line.
         * @param colors The gradient's defined color stops.
         * @returns An array of interpolated color gradient stops for the specified defined stops.
         */
        static createColorGradient(colors) {
            return msfsSdk.ArrayUtils.flatMap(colors.slice().sort((a, b) => a[0] - b[0]), (stop, index, array) => {
                var _a;
                const next = array[index + 1];
                // If this is the last stop, then we will return the stop with no further interpolation.
                if (!next) {
                    return { distance: stop[0], color: stop[1] };
                }
                const step = Math.max(Math.round((_a = next[2]) !== null && _a !== void 0 ? _a : 4), 2);
                // If this stop and the next one has the same color or the distance between them is less than the interpolation
                // step, then we will return the current stop with no interpolation.
                if (next[0] - stop[0] <= step || stop[1] === next[1]) {
                    return { distance: stop[0], color: stop[1] };
                }
                const distance = next[0] - stop[0];
                const stepCount = Math.ceil(distance / step);
                const steps = msfsSdk.ColorUtils.interpolateHex(stop[1], next[1], msfsSdk.ArrayUtils.range(stepCount, 0, step / distance))
                    .map((stepColor, stepIndex) => {
                    return {
                        distance: stop[0] + stepIndex * step,
                        color: stepColor
                    };
                });
                // If there are at least two steps and the last step is less than 2 pixels from the next stop, then remove the
                // last step.
                if (steps.length > 1 && distance - steps[steps.length - 1].distance < 2) {
                    steps.length--;
                }
                return steps;
            });
        }
    }
    ArtificialHorizon.UPDATE_FLAGS = msfsSdk.HorizonProjectionChangeType.ScaleFactor
        | msfsSdk.HorizonProjectionChangeType.Fov
        | msfsSdk.HorizonProjectionChangeType.Pitch
        | msfsSdk.HorizonProjectionChangeType.Roll
        | msfsSdk.HorizonProjectionChangeType.ProjectedSize;

    /**
     * Aircraft symbol formats.
     */
    exports.AttitudeAircraftSymbolFormat = void 0;
    (function (AttitudeAircraftSymbolFormat) {
        AttitudeAircraftSymbolFormat["SingleCue"] = "SingleCue";
        AttitudeAircraftSymbolFormat["DualCue"] = "DualCue";
    })(exports.AttitudeAircraftSymbolFormat || (exports.AttitudeAircraftSymbolFormat = {}));
    /**
     * An aircraft symbol for the PFD attitude indicator.
     */
    class AttitudeAircraftSymbol extends msfsSdk.HorizonLayer {
        constructor() {
            super(...arguments);
            this.style = msfsSdk.ObjectSubject.create({
                'display': '',
                'position': 'absolute',
                'left': '0px',
                'top': '0px',
                'width': '0px',
                'height': '0px'
            });
            this.singleCueDisplay = msfsSdk.Subject.create('');
            this.dualCueDisplay = msfsSdk.Subject.create('');
        }
        /** @inheritdoc */
        onVisibilityChanged(isVisible) {
            this.style.set('display', isVisible ? '' : 'none');
        }
        /** @inheritdoc */
        onAttached() {
            super.onAttached();
            if (msfsSdk.SubscribableUtils.isSubscribable(this.props.format)) {
                this.formatSub = this.props.format.sub(format => {
                    if (format === exports.AttitudeAircraftSymbolFormat.DualCue) {
                        this.singleCueDisplay.set('none');
                        this.dualCueDisplay.set('');
                    }
                    else {
                        this.dualCueDisplay.set('none');
                        this.singleCueDisplay.set('');
                    }
                }, true);
            }
            this.showSub = this.props.show.sub(this.setVisible.bind(this), true);
            this.updatePosition();
        }
        /** @inheritdoc */
        onProjectionChanged(projection, changeFlags) {
            if (msfsSdk.BitFlags.isAny(changeFlags, msfsSdk.HorizonProjectionChangeType.OffsetCenterProjected)) {
                this.updatePosition();
            }
        }
        /**
         * Updates the position of this symbol.
         */
        updatePosition() {
            const center = this.props.projection.getOffsetCenterProjected();
            this.style.set('left', `${center[0]}px`);
            this.style.set('top', `${center[1]}px`);
        }
        /** @inheritdoc */
        onDetached() {
            super.onDetached();
            this.destroy();
        }
        /** @inheritdoc */
        render() {
            const isDynamicFormat = msfsSdk.SubscribableUtils.isSubscribable(this.props.format);
            const renderSingleCue = isDynamicFormat || this.props.format === exports.AttitudeAircraftSymbolFormat.SingleCue;
            const renderDualCue = isDynamicFormat || this.props.format === exports.AttitudeAircraftSymbolFormat.DualCue;
            return (msfsSdk.FSComponent.buildComponent("div", { class: `attitude-aircraft-symbol attitude-aircraft-symbol-${this.props.color}`, style: this.style },
                renderSingleCue && this.renderSingleCue(),
                renderDualCue && this.renderDualCue()));
        }
        /**
         * Renders the single-cue aircraft symbol.
         * @returns The single-cue aircraft symbol, as a VNode.
         */
        renderSingleCue() {
            return (msfsSdk.FSComponent.buildComponent("svg", { viewBox: '-204 -30 408 60', class: 'attitude-aircraft-symbol-single-cue', style: {
                    'display': this.singleCueDisplay,
                    'position': 'absolute',
                    'left': '0px',
                    'top': '0px',
                    'transform': 'translate(-50%, -50%)',
                    'overflow': 'visible'
                } },
                msfsSdk.FSComponent.buildComponent("path", { d: 'M -160 0 l -3 -4 l -43 0 l 0 4', fill: 'var(--attitude-aircraft-symbol-fill-light)', stroke: 'var(--attitude-aircraft-symbol-single-cue-stroke)', "stroke-width": 'var(--attitude-aircraft-symbol-single-cue-bar-stroke-width)' }),
                msfsSdk.FSComponent.buildComponent("path", { d: 'M -160 0 l -3 4 l -43 0 l 0 -4', fill: 'var(--attitude-aircraft-symbol-fill-dark)', stroke: 'var(--attitude-aircraft-symbol-single-cue-stroke)', "stroke-width": 'var(--attitude-aircraft-symbol-single-cue-bar-stroke-width)' }),
                msfsSdk.FSComponent.buildComponent("path", { d: 'M 158 0 l 3 -4 l 43 0 l 0 4', fill: 'var(--attitude-aircraft-symbol-fill-light)', stroke: 'var(--attitude-aircraft-symbol-single-cue-stroke)', "stroke-width": 'var(--attitude-aircraft-symbol-single-cue-bar-stroke-width)' }),
                msfsSdk.FSComponent.buildComponent("path", { d: 'M 158 0 l 3 4 l 43 0 l 0 -4', fill: 'var(--attitude-aircraft-symbol-fill-dark)', stroke: 'var(--attitude-aircraft-symbol-single-cue-stroke)', "stroke-width": 'var(--attitude-aircraft-symbol-single-cue-bar-stroke-width)' }),
                msfsSdk.FSComponent.buildComponent("path", { d: 'M 0 0 l 0 -1 l -120 31 l 35 0', fill: 'var(--attitude-aircraft-symbol-fill-light)', stroke: 'var(--attitude-aircraft-symbol-single-cue-stroke)', "stroke-width": 'var(--attitude-aircraft-symbol-single-cue-arrow-stroke-width)' }),
                msfsSdk.FSComponent.buildComponent("path", { d: 'M 0 0 l -66 30 l -19 0', fill: 'var(--attitude-aircraft-symbol-fill-dark)', stroke: 'var(--attitude-aircraft-symbol-single-cue-stroke)', "stroke-width": 'var(--attitude-aircraft-symbol-single-cue-arrow-stroke-width)' }),
                msfsSdk.FSComponent.buildComponent("path", { d: 'M 0 0 l 0 -1 l 120 31 l -35 0', fill: 'var(--attitude-aircraft-symbol-fill-light)', stroke: 'var(--attitude-aircraft-symbol-single-cue-stroke)', "stroke-width": 'var(--attitude-aircraft-symbol-single-cue-arrow-stroke-width)' }),
                msfsSdk.FSComponent.buildComponent("path", { d: 'M 0 0 l 66 30 l 19 0', fill: 'var(--attitude-aircraft-symbol-fill-dark)', stroke: 'var(--attitude-aircraft-symbol-single-cue-stroke)', "stroke-width": 'var(--attitude-aircraft-symbol-single-cue-arrow-stroke-width)' })));
        }
        /**
         * Renders the dual-cue aircraft symbol.
         * @returns The dual-cue aircraft symbol, as a VNode.
         */
        renderDualCue() {
            return (msfsSdk.FSComponent.buildComponent("svg", { viewBox: '-163 -23 326 46', class: 'attitude-aircraft-symbol-dual-cue', style: {
                    'display': this.dualCueDisplay,
                    'position': 'absolute',
                    'left': '0px',
                    'top': '0px',
                    'transform': 'translate(-50%, -50%)',
                    'overflow': 'visible'
                } },
                msfsSdk.FSComponent.buildComponent("path", { d: 'M -6 -8 l 12 0 a 2 2 0 0 1 2 2 l 0 12 a 2 2 0 0 1 -2 2 l -12 0 a 2 2 0 0 1 -2 -2 l 0 -12 a 2 2 0 0 1 2 -2 M -161 -5 l 86 0 a 2 2 0 0 1 2 2 l 0 24 a 2 2 0 0 1 -2 2 l -6 0 a 2 2 0 0 1 -2 -2 l 0 -14 a 2 2 0 0 0 -2 -2 l -76 0 a 2 2 0 0 1 -2 -2 l 0 -6 a 2 2 0 0 1 2 -2 M 161 -5 l -86 0 a 2 2 90 0 0 -2 2 l 0 24 a 2 2 90 0 0 2 2 l 6 0 a 2 2 90 0 0 2 -2 l 0 -14 a 2 2 90 0 1 2 -2 l 76 0 a 2 2 90 0 0 2 -2 l 0 -6 a 2 2 90 0 0 -2 -2', fill: 'none', stroke: 'var(--attitude-aircraft-symbol-fill-light)', "stroke-width": 'var(--attitude-aircraft-symbol-dual-cue-outline-width)' }),
                msfsSdk.FSComponent.buildComponent("path", { d: 'M -6 -8 l 12 0 a 2 2 0 0 1 2 2 l 0 12 a 2 2 0 0 1 -2 2 l -12 0 a 2 2 0 0 1 -2 -2 l 0 -12 a 2 2 0 0 1 2 -2 M -161 -5 l 86 0 a 2 2 0 0 1 2 2 l 0 24 a 2 2 0 0 1 -2 2 l -6 0 a 2 2 0 0 1 -2 -2 l 0 -14 a 2 2 0 0 0 -2 -2 l -76 0 a 2 2 0 0 1 -2 -2 l 0 -6 a 2 2 0 0 1 2 -2 M 161 -5 l -86 0 a 2 2 90 0 0 -2 2 l 0 24 a 2 2 90 0 0 2 2 l 6 0 a 2 2 90 0 0 2 -2 l 0 -14 a 2 2 90 0 1 2 -2 l 76 0 a 2 2 90 0 0 2 -2 l 0 -6 a 2 2 90 0 0 -2 -2', fill: 'var(--attitude-aircraft-symbol-fill-light)', stroke: 'var(--attitude-aircraft-symbol-dual-cue-stroke)', "stroke-width": 'var(--attitude-aircraft-symbol-dual-cue-stroke-width)' })));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b;
            (_a = this.showSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.formatSub) === null || _b === void 0 ? void 0 : _b.destroy();
            super.destroy();
        }
    }

    /**
     * A PFD dual-cue flight director.
     */
    class FlightDirectorDualCue extends msfsSdk.HorizonLayer {
        constructor() {
            var _a, _b;
            super(...arguments);
            this.pitchErrorFactor = msfsSdk.SubscribableUtils.toSubscribable((_a = this.props.pitchErrorFactor) !== null && _a !== void 0 ? _a : 1, true);
            this.bankErrorFactor = msfsSdk.SubscribableUtils.toSubscribable((_b = this.props.bankErrorFactor) !== null && _b !== void 0 ? _b : 1, true);
            this.bankErrorConstant = this.props.bankErrorConstant !== undefined ? msfsSdk.SubscribableUtils.toSubscribable(this.props.bankErrorConstant, true) : undefined;
            this.conformalBounds = msfsSdk.SubscribableUtils.toSubscribable(this.props.conformalBounds, true);
            this.display = msfsSdk.Subject.create('');
            this.pitchTransform = msfsSdk.CssTransformSubject.create(msfsSdk.CssTransformBuilder.concat(msfsSdk.CssTransformBuilder.translate('%'), msfsSdk.CssTransformBuilder.translate3d('px')));
            this.bankTransform = msfsSdk.CssTransformSubject.create(msfsSdk.CssTransformBuilder.concat(msfsSdk.CssTransformBuilder.translate('%'), msfsSdk.CssTransformBuilder.translate3d('px')));
            this.needUpdate = false;
            this.pauseable = [];
        }
        /** @inheritdoc */
        onVisibilityChanged(isVisible) {
            if (isVisible) {
                this.display.set('');
                for (const pauseable of this.pauseable) {
                    pauseable.resume();
                }
                this.needUpdate = true;
            }
            else {
                this.display.set('none');
                for (const pauseable of this.pauseable) {
                    pauseable.pause();
                }
            }
        }
        /** @inheritdoc */
        onAttached() {
            var _a, _b;
            super.onAttached();
            this.pitchTransform.transform.getChild(0).set(-50, -50);
            this.bankTransform.transform.getChild(0).set(-50, -50);
            const updateHandler = () => { this.needUpdate = true; };
            this.pauseable.push(this.props.fdPitch.sub(updateHandler), this.props.fdBank.sub(updateHandler), this.pitchErrorFactor.sub(updateHandler), (_b = (_a = this.bankErrorConstant) === null || _a === void 0 ? void 0 : _a.sub(updateHandler)) !== null && _b !== void 0 ? _b : this.bankErrorFactor.sub(updateHandler), this.conformalBounds.sub(updateHandler));
            this.showSub = this.props.show.sub(this.setVisible.bind(this), true);
            this.needUpdate = true;
        }
        /** @inheritdoc */
        onProjectionChanged() {
            this.needUpdate = true;
        }
        /** @inheritdoc */
        onUpdated() {
            if (!this.needUpdate || !this.isVisible()) {
                return;
            }
            const projection = this.props.projection;
            const center = projection.getOffsetCenterProjected();
            const angularResolution = projection.getScaleFactor() / projection.getFov();
            const conformalBounds = this.conformalBounds.get();
            const pitchError = this.props.fdPitch.get() - projection.getPitch();
            const bankError = this.props.fdBank.get() - projection.getRoll();
            const bankErrorConstant = this.bankErrorConstant !== undefined ? this.bankErrorConstant.get() : this.bankErrorFactor.get() * angularResolution;
            const xOffset = msfsSdk.MathUtils.clamp(bankError * bankErrorConstant, conformalBounds[0], conformalBounds[2]);
            const yOffset = msfsSdk.MathUtils.clamp(-pitchError * angularResolution * this.pitchErrorFactor.get(), conformalBounds[1], conformalBounds[3]);
            this.pitchTransform.transform.getChild(1).set(center[0], center[1] + yOffset, 0, 0.1, 0.1);
            this.pitchTransform.resolve();
            this.bankTransform.transform.getChild(1).set(center[0] + xOffset, center[1], 0, 0.1, 0.1);
            this.bankTransform.resolve();
            this.needUpdate = false;
        }
        /** @inheritdoc */
        onDetached() {
            super.onDetached();
            this.destroy();
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: 'flight-director-dual-cue', style: {
                    'display': this.display,
                    'position': 'absolute',
                    'left': '0px',
                    'top': '0px'
                } },
                msfsSdk.FSComponent.buildComponent("div", { class: 'flight-director-dual-cue-pitch flight-director-dual-cue-outline', style: {
                        'position': 'absolute',
                        'left': '0px',
                        'top': '0px',
                        'transform': this.pitchTransform
                    } }),
                msfsSdk.FSComponent.buildComponent("div", { class: 'flight-director-dual-cue-bank flight-director-dual-cue-outline', style: {
                        'position': 'absolute',
                        'left': '0px',
                        'top': '0px',
                        'transform': this.bankTransform
                    } }),
                msfsSdk.FSComponent.buildComponent("div", { class: 'flight-director-dual-cue-pitch flight-director-dual-cue-bar', style: {
                        'position': 'absolute',
                        'left': '0px',
                        'top': '0px',
                        'transform': this.pitchTransform
                    } }),
                msfsSdk.FSComponent.buildComponent("div", { class: 'flight-director-dual-cue-bank flight-director-dual-cue-bar', style: {
                        'position': 'absolute',
                        'left': '0px',
                        'top': '0px',
                        'transform': this.bankTransform
                    } })));
        }
        /** @inheritdoc */
        destroy() {
            var _a;
            for (const pauseable of this.pauseable) {
                pauseable.destroy();
            }
            (_a = this.showSub) === null || _a === void 0 ? void 0 : _a.destroy();
            super.destroy();
        }
    }

    /**
     * Flight director formats.
     */
    exports.FlightDirectorFormat = void 0;
    (function (FlightDirectorFormat) {
        FlightDirectorFormat["SingleCue"] = "SingleCue";
        FlightDirectorFormat["DualCue"] = "DualCue";
    })(exports.FlightDirectorFormat || (exports.FlightDirectorFormat = {}));

    /**
     * A PFD single-cue flight director.
     */
    class FlightDirectorSingleCue extends msfsSdk.HorizonLayer {
        constructor() {
            super(...arguments);
            this.conformalBounds = msfsSdk.SubscribableUtils.toSubscribable(this.props.conformalBounds, true);
            this.conformalBankLimit = msfsSdk.SubscribableUtils.toSubscribable(this.props.conformalBankLimit, true);
            this.display = msfsSdk.Subject.create('');
            this.transform = msfsSdk.CssTransformSubject.create(msfsSdk.CssTransformBuilder.concat(msfsSdk.CssTransformBuilder.translate('%'), msfsSdk.CssTransformBuilder.translate3d('px'), msfsSdk.CssTransformBuilder.rotate('deg')));
            this.needUpdate = false;
            this.pauseable = [];
        }
        /** @inheritdoc */
        onVisibilityChanged(isVisible) {
            if (isVisible) {
                this.display.set('');
                for (const pauseable of this.pauseable) {
                    pauseable.resume();
                }
                this.needUpdate = true;
            }
            else {
                this.display.set('none');
                for (const pauseable of this.pauseable) {
                    pauseable.pause();
                }
            }
        }
        /** @inheritdoc */
        onAttached() {
            super.onAttached();
            this.transform.transform.getChild(0).set(-50, -50);
            const updateHandler = () => { this.needUpdate = true; };
            this.pauseable.push(this.props.fdPitch.sub(updateHandler), this.props.fdBank.sub(updateHandler), this.conformalBounds.sub(updateHandler), this.conformalBankLimit.sub(updateHandler));
            this.showSub = this.props.show.sub(this.setVisible.bind(this), true);
            this.needUpdate = true;
        }
        /** @inheritdoc */
        onProjectionChanged() {
            this.needUpdate = true;
        }
        /** @inheritdoc */
        onUpdated() {
            if (!this.needUpdate || !this.isVisible()) {
                return;
            }
            const projection = this.props.projection;
            const center = projection.getOffsetCenterProjected();
            const pitchResolution = projection.getScaleFactor() / projection.getFov();
            const conformalBounds = this.conformalBounds.get();
            const conformalBankLimit = this.conformalBankLimit.get();
            const pitchError = this.props.fdPitch.get() - projection.getPitch();
            const bankError = msfsSdk.MathUtils.clamp(this.props.fdBank.get() - projection.getRoll(), -conformalBankLimit, conformalBankLimit);
            const yOffset = msfsSdk.MathUtils.clamp(-pitchError * pitchResolution, conformalBounds[0], conformalBounds[1]);
            this.transform.transform.getChild(1).set(center[0], center[1] + yOffset, 0, 0.1, 0.1);
            this.transform.transform.getChild(2).set(bankError, 0.1);
            this.transform.resolve();
            this.needUpdate = false;
        }
        /** @inheritdoc */
        onDetached() {
            super.onDetached();
            this.destroy();
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("svg", { viewBox: '-134 -30 268 60', class: 'flight-director-single-cue', style: {
                    'display': this.display,
                    'position': 'absolute',
                    'left': '0px',
                    'top': '0px',
                    'transform': this.transform,
                    'overflow': 'visible'
                } },
                msfsSdk.FSComponent.buildComponent("path", { d: 'M 0 0 l -120 30 l -14 -9 l 134 -22 z', fill: 'var(--flight-director-fill)', stroke: 'var(--flight-director-stroke)', "stroke-width": 'var(--flight-director-stroke-width)' }),
                msfsSdk.FSComponent.buildComponent("path", { d: 'M -134 21 l 0 9 l 14 0 z', fill: 'var(--flight-director-fill)', stroke: 'var(--flight-director-stroke)', "stroke-width": 'var(--flight-director-stroke-width)' }),
                msfsSdk.FSComponent.buildComponent("path", { d: 'M 0 0 l 120 30 l 14 -9 l -134 -22 z', fill: 'var(--flight-director-fill)', stroke: 'var(--flight-director-stroke)', "stroke-width": 'var(--flight-director-stroke-width)' }),
                msfsSdk.FSComponent.buildComponent("path", { d: 'M 134 21 l 0 9 l -14 0 z', fill: 'var(--flight-director-fill)', stroke: 'var(--flight-director-stroke)', "stroke-width": 'var(--flight-director-stroke-width)' })));
        }
        /** @inheritdoc */
        destroy() {
            var _a;
            for (const pauseable of this.pauseable) {
                pauseable.destroy();
            }
            (_a = this.showSub) === null || _a === void 0 ? void 0 : _a.destroy();
            super.destroy();
        }
    }

    /**
     * A default implementation of {@link FlightDirectorDataProvider} which smooths pitch and bank commands.
     */
    class DefaultFlightDirectorDataProvider {
        /**
         * Constructor.
         * @param bus The event bus.
         * @param pitchSmoothingTau The time constant used for smoothing pitch commands, in milliseconds.
         * @param bankSmoothingTau The time constant used for smoothing bank commands, in milliseconds.
         */
        constructor(bus, pitchSmoothingTau, bankSmoothingTau) {
            this.bus = bus;
            this._isFdActive = msfsSdk.Subject.create(false);
            /** @inheritdoc */
            this.isFdActive = this._isFdActive;
            this._fdPitch = msfsSdk.Subject.create(0);
            /** @inheritdoc */
            this.fdPitch = this._fdPitch;
            this._fdBank = msfsSdk.Subject.create(0);
            /** @inheritdoc */
            this.fdBank = this._fdBank;
            this.isFdActiveSource = msfsSdk.ConsumerSubject.create(null, false).pause();
            this.fdPitchSource = msfsSdk.ConsumerValue.create(null, 0).pause();
            this.fdBankSource = msfsSdk.ConsumerValue.create(null, 0).pause();
            this.simRate = msfsSdk.ConsumerValue.create(null, 1).pause();
            this.isFdNotInstalled = false;
            this.lastUpdateTime = undefined;
            this.pauseable = [
                this.isFdActiveSource,
                this.fdPitchSource,
                this.fdBankSource,
                this.simRate
            ];
            this.isInit = false;
            this.isAlive = true;
            this.isPaused = true;
            this.pitchSmoother = new msfsSdk.ExpSmoother(pitchSmoothingTau);
            this.bankSmoother = new msfsSdk.ExpSmoother(bankSmoothingTau);
        }
        /**
         * Initializes this data provider. Once initialized, this data provider will continuously update its data until
         * paused or destroyed.
         * @param paused Whether to initialize this data provider as paused. If `true`, this data provider will provide an
         * initial set of data but will not update the provided data until it is resumed. Defaults to `false`.
         * @throws Error if this data provider has been destroyed.
         */
        init(paused = false) {
            if (!this.isAlive) {
                throw new Error('DefaultFlightDirectorDataProvider: cannot initialize a dead provider');
            }
            if (this.isInit) {
                return;
            }
            this.isInit = true;
            const sub = this.bus.getSubscriber();
            this.isFdActiveSource.pipe(this._isFdActive);
            this.isFdActiveSource.setConsumer(sub.on('flight_director_is_active_1'));
            this.fdPitchSource.setConsumer(sub.on('flight_director_pitch'));
            this.fdBankSource.setConsumer(sub.on('flight_director_bank'));
            this.simRate.setConsumer(sub.on('simRate'));
            this.fdNotInstalledSub = sub.on('fd_not_installed').handle(value => {
                if (value) {
                    this.isFdNotInstalled = true;
                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                    this.fdNotInstalledSub.destroy();
                    this.isFdActiveSource.setConsumer(null);
                    this.fdPitchSource.setConsumer(null);
                    this.fdBankSource.setConsumer(null);
                    this.simRate.setConsumer(null);
                    this._isFdActive.set(false);
                    this._fdPitch.set(0);
                    this._fdBank.set(0);
                }
            }, true);
            this.fdNotInstalledSub.resume(true);
            if (!paused) {
                this.resume();
            }
        }
        /**
         * Resumes this data provider. Once resumed, this data provider will continuously update its data until paused or
         * destroyed.
         * @throws Error if this data provider has been destroyed.
         */
        resume() {
            if (!this.isAlive) {
                throw new Error('DefaultFlightDirectorDataProvider: cannot resume a dead provider');
            }
            if (!this.isPaused || !this.isInit) {
                return;
            }
            this.isPaused = false;
            for (const pauseable of this.pauseable) {
                pauseable.resume();
            }
        }
        /**
         * Pauses this data provider. Once paused, this data provider will not update its data until it is resumed.
         * @throws Error if this data provider has been destroyed.
         */
        pause() {
            if (!this.isAlive) {
                throw new Error('DefaultFlightDirectorDataProvider: cannot pause a dead provider');
            }
            if (this.isPaused || !this.isInit) {
                return;
            }
            this.isPaused = true;
            for (const pauseable of this.pauseable) {
                pauseable.pause();
            }
            this.pitchSmoother.reset();
            this.bankSmoother.reset();
        }
        /**
         * Updates this data provider.
         * @param time The current real (operating system) time, as a UNIX timestamp in milliseconds.
         */
        update(time) {
            if (this.isFdNotInstalled) {
                return;
            }
            const dt = (this.lastUpdateTime === undefined ? 0 : Math.max(0, time - this.lastUpdateTime)) * this.simRate.get();
            this.lastUpdateTime = time;
            // Sim flight director pitch/bank are positive-down/left, respectively.
            this._fdPitch.set(this.pitchSmoother.next(-this.fdPitchSource.get(), dt));
            this._fdBank.set(this.bankSmoother.next(-this.fdBankSource.get(), dt));
        }
        /**
         * Destroys this data provider. Once destroyed, this data provider will no longer update its provided data, and can
         * no longer be paused or resumed.
         */
        destroy() {
            var _a;
            this.isAlive = false;
            for (const pauseable of this.pauseable) {
                pauseable.destroy();
            }
            (_a = this.fdNotInstalledSub) === null || _a === void 0 ? void 0 : _a.destroy();
        }
    }

    /**
     * A PFD synthetic vision technology (SVT) flight path marker. Displays an icon depicting the estimated position of the
     * airplane projected forward in time given the airplane's current horizontal and vertical speed and track.
     */
    class FlightPathMarker extends msfsSdk.HorizonLayer {
        constructor() {
            var _a, _b, _c;
            super(...arguments);
            this.style = msfsSdk.ObjectSubject.create({
                position: 'absolute',
                left: 0,
                top: 0,
                display: '',
                transform: 'translate(-50%, -50%) translate3d(0, 0, 0)'
            });
            this.minGs = (_a = this.props.minGroundSpeed) !== null && _a !== void 0 ? _a : FlightPathMarker.DEFAULT_MIN_GS;
            this.lookahead = (_b = this.props.lookahead) !== null && _b !== void 0 ? _b : FlightPathMarker.DEFAULT_LOOKAHEAD;
            this.smoothingTimeConstant = (_c = this.props.smoothingTimeConstant) !== null && _c !== void 0 ? _c : FlightPathMarker.DEFAULT_SMOOTHING_TIME_CONSTANT;
            this.gs = msfsSdk.ConsumerSubject.create(null, 0);
            this.track = msfsSdk.ConsumerSubject.create(null, 0);
            this.vs = msfsSdk.ConsumerSubject.create(null, 0);
            this.isFpmVisible = msfsSdk.MappedSubject.create(([show, gs]) => {
                return show && gs >= this.minGs;
            }, this.props.show, this.gs);
            this.groundTrackSmoother = new msfsSdk.ExpSmoother(this.smoothingTimeConstant);
            this.gsSmoother = new msfsSdk.ExpSmoother(this.smoothingTimeConstant);
            this.projectedPosition = msfsSdk.Vec2Subject.createFromVector(msfsSdk.Vec2Math.create());
            this.needUpdate = false;
        }
        /** @inheritdoc */
        onVisibilityChanged(isVisible) {
            if (isVisible) {
                this.style.set('display', '');
            }
            else {
                this.style.set('display', 'none');
                this.groundTrackSmoother.reset();
                this.gsSmoother.reset();
            }
        }
        /** @inheritdoc */
        onAttached() {
            super.onAttached();
            const sub = this.props.bus.getSubscriber();
            this.gs.setConsumer(sub.on('ground_speed'));
            this.track.setConsumer(sub.on('track_deg_true'));
            this.vs.setConsumer(sub.on('vertical_speed'));
            this.isFpmVisible.sub(show => { this.setVisible(show); }, true);
            this.gs.sub(() => { this.needUpdate = true; });
            this.track.sub(() => { this.needUpdate = true; });
            this.vs.sub(() => { this.needUpdate = true; });
            this.projectedPosition.sub(position => {
                this.style.set('transform', `translate(-50%, -50%) translate3d(${position[0]}px, ${position[1]}px, 0)`);
            });
            this.needUpdate = true;
        }
        /** @inheritdoc */
        onProjectionChanged(projection, changeFlags) {
            if (msfsSdk.BitFlags.isAny(changeFlags, msfsSdk.HorizonProjectionChangeType.Fov
                | msfsSdk.HorizonProjectionChangeType.ScaleFactor
                | msfsSdk.HorizonProjectionChangeType.Offset
                | msfsSdk.HorizonProjectionChangeType.ProjectedOffset
                | msfsSdk.HorizonProjectionChangeType.Heading
                | msfsSdk.HorizonProjectionChangeType.Pitch
                | msfsSdk.HorizonProjectionChangeType.Roll)) {
                this.needUpdate = true;
            }
        }
        /** @inheritdoc */
        onUpdated(time, elapsed) {
            if (!this.needUpdate || !this.isVisible()) {
                return;
            }
            const smoothedGs = this.gsSmoother.next(this.gs.get(), elapsed);
            const smoothedTrack = this.smoothGroundTrack(this.track.get(), elapsed);
            const distance = msfsSdk.UnitType.KNOT.convertTo(smoothedGs, msfsSdk.UnitType.MPS) * this.lookahead;
            const height = msfsSdk.UnitType.FPM.convertTo(this.vs.get(), msfsSdk.UnitType.MPS) * this.lookahead; // no need to smooth VS since the data we get is already effectively smoothed
            const projected = this.props.projection.projectRelativeSpherical(smoothedTrack, distance, height, FlightPathMarker.vec2Cache[0]);
            this.projectedPosition.set(msfsSdk.MathUtils.round(projected[0], 0.1), msfsSdk.MathUtils.round(projected[1], 0.1));
        }
        /**
         * Smooths a ground track value.
         * @param track A ground track value.
         * @param dt The elapsed time, in milliseconds, since the last smoothed value was calculated.
         * @returns A smoothed ground track value.
         */
        smoothGroundTrack(track, dt) {
            const last = this.groundTrackSmoother.last();
            if (last !== null && !isNaN(last)) {
                // need to handle wraparounds
                let delta = track - last;
                if (delta > 180) {
                    delta = delta - 360;
                }
                else if (delta < -180) {
                    delta = delta + 360;
                }
                track = last + delta;
            }
            const next = last !== null && isNaN(last) ? this.groundTrackSmoother.reset(track) : this.groundTrackSmoother.next(track, dt);
            const normalized = (next + 360) % 360; // enforce range 0-359
            return this.groundTrackSmoother.reset(normalized);
        }
        /** @inheritdoc */
        onDetached() {
            super.onDetached();
            this.destroy();
        }
        /**
         * Renders the component.
         * @returns The component VNode.
         */
        render() {
            return (msfsSdk.FSComponent.buildComponent("svg", { viewBox: '-30 -30 60 60', class: 'flight-path-marker', style: this.style },
                msfsSdk.FSComponent.buildComponent("path", { d: 'M -14 0 a 14 14 0 1 0 28 0 m 16 0 l -16 0 a 14 14 0 1 0 -28 0 l -16 0 m 30 -14 l 0 -12', stroke: 'var(--flight-path-marker-outline-stroke)', "stroke-width": 'var(--flight-path-marker-outline-stroke-width)', fill: 'none' }),
                msfsSdk.FSComponent.buildComponent("path", { d: 'M -14 0 a 14 14 0 1 0 28 0 m 14 0 l -14 0 a 14 14 0 1 0 -28 0 l -14 0 m 28 -14 l 0 -10', stroke: 'var(--flight-path-marker-stroke)', "stroke-width": 'var(--flight-path-marker-stroke-width)', fill: 'none' })));
        }
        /** @inheritdoc */
        destroy() {
            super.destroy();
            this.isFpmVisible.destroy();
            this.gs.destroy();
            this.track.destroy();
            this.vs.destroy();
        }
    }
    FlightPathMarker.DEFAULT_MIN_GS = 30; // knots
    FlightPathMarker.DEFAULT_LOOKAHEAD = 60; // seconds
    FlightPathMarker.DEFAULT_SMOOTHING_TIME_CONSTANT = 500 / Math.LN2; // milliseconds
    FlightPathMarker.vec2Cache = [msfsSdk.Vec2Math.create()];

    /**
     * A PFD horizon line with optional heading reference pointer, optional heading tick marks every 10 degrees, and
     * optional heading labels every 30 degrees.
     */
    class HorizonLine extends msfsSdk.HorizonSharedCanvasSubLayer {
        constructor() {
            var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
            super(...arguments);
            this.lineStrokeWidth = (_a = this.props.options.strokeWidth) !== null && _a !== void 0 ? _a : HorizonLine.DEFAULT_LINE_STROKE_WIDTH;
            this.lineStrokeColor = (_b = this.props.options.strokeColor) !== null && _b !== void 0 ? _b : HorizonLine.DEFAULT_LINE_STROKE_COLOR;
            this.lineOutlineWidth = (_c = this.props.options.outlineWidth) !== null && _c !== void 0 ? _c : HorizonLine.DEFAULT_LINE_OUTLINE_WIDTH;
            this.lineOutlineColor = (_d = this.props.options.outlineColor) !== null && _d !== void 0 ? _d : HorizonLine.DEFAULT_LINE_OUTLINE_COLOR;
            this.tickStrokeWidth = (_e = this.props.options.headingTickWidth) !== null && _e !== void 0 ? _e : HorizonLine.DEFAULT_TICK_STROKE_WIDTH;
            this.tickStrokeColor = (_f = this.props.options.headingTickColor) !== null && _f !== void 0 ? _f : HorizonLine.DEFAULT_TICK_STROKE_COLOR;
            this.font = `${this.props.options.fontSize}px ${this.props.options.font}`;
            this.fontColor = (_g = this.props.options.fontColor) !== null && _g !== void 0 ? _g : HorizonLine.DEFAULT_FONT_COLOR;
            this.fontOutlineWidth = (_h = this.props.options.fontOutlineWidth) !== null && _h !== void 0 ? _h : HorizonLine.DEFAULT_FONT_OUTLINE_WIDTH;
            this.fontOutlineColor = (_j = this.props.options.fontOutlineColor) !== null && _j !== void 0 ? _j : HorizonLine.DEFAULT_FONT_OUTLINE_COLOR;
            this.labelOffset = (_k = this.props.options.labelOffset) !== null && _k !== void 0 ? _k : 0;
            this.approximate = msfsSdk.SubscribableUtils.toSubscribable(this.props.approximate, true);
            this.showHeadingLabels = msfsSdk.SubscribableUtils.toSubscribable(this.props.showHeadingLabels, true);
            this.bounds = msfsSdk.VecNSubject.create(msfsSdk.VecNMath.create(4, -HorizonLine.BOUNDS_BUFFER, -HorizonLine.BOUNDS_BUFFER, HorizonLine.BOUNDS_BUFFER, HorizonLine.BOUNDS_BUFFER));
            this.clipPathStream = new msfsSdk.ClippedPathStream(msfsSdk.NullPathStream.INSTANCE, this.bounds);
            this.transformPathStream = new msfsSdk.AffineTransformPathStream(this.clipPathStream);
            this.nodes = Array.from({ length: HorizonLine.TICK_COUNT }, (v, index) => {
                const heading = index * HorizonLine.TICK_INCREMENT;
                return {
                    heading,
                    labelText: index % HorizonLine.LABEL_FACTOR === 0 ? (heading === 0 ? 360 : heading).toFixed(0).padStart(3, '0') : undefined,
                    projected: msfsSdk.Vec2Math.create(),
                    drawTick: false,
                    tickEndProjected: msfsSdk.Vec2Math.create(),
                    drawLabel: false,
                    labelFontSize: 0
                };
            });
            this.approximateTransform = new msfsSdk.Transform2D();
            this.needUpdate = false;
            this.subscriptions = [];
        }
        /** @inheritdoc */
        onAttached() {
            super.onAttached();
            this.updateBounds();
            this.clipPathStream.setConsumer(this.display.context);
            const scheduleUpdate = () => { this.needUpdate = true; };
            this.subscriptions.push(this.props.show.sub(scheduleUpdate), this.approximate.sub(scheduleUpdate), this.showHeadingLabels.sub(scheduleUpdate), this.props.useMagneticHeading.sub(scheduleUpdate), this.props.occlusions.sub(scheduleUpdate));
            this.needUpdate = true;
        }
        /** @inheritdoc */
        onProjectionChanged(projection, changeFlags) {
            if (msfsSdk.BitFlags.isAll(changeFlags, msfsSdk.HorizonProjectionChangeType.ProjectedSize)) {
                this.updateBounds();
            }
            this.needUpdate = true;
        }
        /**
         * Updates this layer's drawing bounds.
         */
        updateBounds() {
            const projectedSize = this.projection.getProjectedSize();
            this.bounds.set(-HorizonLine.BOUNDS_BUFFER, -HorizonLine.BOUNDS_BUFFER, projectedSize[0] + HorizonLine.BOUNDS_BUFFER, projectedSize[1] + HorizonLine.BOUNDS_BUFFER);
        }
        /** @inheritdoc */
        shouldInvalidate() {
            return this.needUpdate && this.isVisible();
        }
        /** @inheritdoc */
        onUpdated() {
            if (!this.display.isInvalidated || !this.isVisible()) {
                return;
            }
            if (this.props.show.get()) {
                const context = this.display.context;
                context.font = this.font;
                context.textAlign = 'center';
                context.fillStyle = this.fontColor;
                const projection = this.projection;
                const position = projection.getPosition();
                const useMagnetic = this.props.useMagneticHeading.get();
                const headingOffset = useMagnetic ? msfsSdk.MagVar.get(position.lat, position.lon) : 0;
                const approximate = this.approximate.get();
                if (approximate) {
                    const center = projection.getOffsetCenterProjected();
                    const pitchResolution = projection.getScaleFactor() / projection.getFov();
                    this.approximateTransform
                        .toTranslation(0, pitchResolution * projection.getPitch())
                        .addRotation(-projection.getRoll() * Avionics.Utils.DEG2RAD)
                        .addTranslation(center[0], center[1]);
                    this.approximateNodes(projection, headingOffset);
                }
                else {
                    this.projectNodes(projection, headingOffset);
                }
                this.drawLine(context);
                if (this.showHeadingLabels.get()) {
                    const occlusionsApplied = this.applyOcclusionClipPath(context, this.props.occlusions.getArray());
                    if (occlusionsApplied) {
                        context.save();
                    }
                    this.drawTicks(context, projection);
                    if (occlusionsApplied) {
                        context.restore();
                    }
                    this.drawHeadingPointer(context, projection);
                }
            }
            this.needUpdate = false;
        }
        /**
         * Applies a clip path based on this layer's occlusion areas. If there are no occlusion areas, then a clip path will
         * not be applied.
         * @param context The canvas rendering context to which to apply the clip path.
         * @param occlusions The occlusion areas to apply.
         * @returns Whether a clip path was applied.
         */
        applyOcclusionClipPath(context, occlusions) {
            if (occlusions.length === 0) {
                return false;
            }
            const size = this.projection.getProjectedSize();
            context.beginPath();
            context.moveTo(0, 0);
            context.lineTo(size[0], 0);
            context.lineTo(size[0], size[1]);
            context.lineTo(0, size[1]);
            context.lineTo(0, 0);
            for (let i = 0; i < occlusions.length; i++) {
                occlusions[i].path(context);
            }
            context.clip('evenodd');
            return true;
        }
        /**
         * Recalculates the positions of this horizon line's nodes using projection.
         * @param projection The horizon projection.
         * @param headingOffset The offset, in degrees, of the heading ticks with respect to true heading.
         */
        projectNodes(projection, headingOffset) {
            // Model the horizon line and ticks as a virtual ring of arbitrary radius (the exact value of the radius does not
            // matter because it gets factored out with the perspective projection) within the zero-pitch/zero-roll plane
            // centered on the projection camera.
            const drawLabels = this.showHeadingLabels.get();
            // Compute the virtual tick length and label font size required to achieve the desired projected tick lengths and
            // font sizes, respectively.
            const scaledFocalLength = projection.getScaleFactor() * projection.getFocalLength();
            const virtualTickLength = this.props.options.headingTickLength / scaledFocalLength;
            const virtualFontSize = this.props.options.fontSize / scaledFocalLength;
            for (let i = 0; i < this.nodes.length; i++) {
                const drawLabel = i % HorizonLine.LABEL_FACTOR === 0 && drawLabels;
                const node = this.nodes[i];
                const nominalHeading = node.heading + headingOffset;
                projection.projectCameraRelativeEuclidean(nominalHeading, 1, 0, node.projected);
                const isInBounds = projection.isInProjectedBounds(node.projected, this.bounds.get());
                if (isInBounds && drawLabels) {
                    node.drawTick = true;
                    projection.projectCameraRelativeEuclidean(nominalHeading, 1, virtualTickLength, node.tickEndProjected);
                }
                else {
                    node.drawTick = false;
                }
                if (isInBounds && drawLabel) {
                    node.drawLabel = true;
                    const labelOriginProjected = projection.projectCameraRelativeEuclidean(nominalHeading, 1, virtualTickLength, HorizonLine.vec2Cache[0]);
                    const labelTopProjected = projection.projectCameraRelativeEuclidean(nominalHeading, 1, virtualTickLength + virtualFontSize, HorizonLine.vec2Cache[1]);
                    const delta = msfsSdk.Vec2Math.sub(labelTopProjected, labelOriginProjected, HorizonLine.vec2Cache[1]);
                    node.labelFontSize = msfsSdk.Vec2Math.abs(delta);
                }
                else {
                    node.drawLabel = false;
                }
            }
        }
        /**
         * Recalculates the positions of this horizon line's nodes using an approximated pitch scale based on FOV.
         * @param projection The horizon projection.
         * @param headingOffset The offset, in degrees, of the heading ticks with respect to true heading.
         */
        approximateNodes(projection, headingOffset) {
            // Approximate the position of the horizon line and heading ticks as follows: assume the plane is at 0 pitch and 0
            // roll and project the line and ticks via a perspective transform. Then, approximate translation due to pitch
            // using a constant pitch resolution (pixels per degree of pitch) derived from the projection's current field of
            // view. Finally, apply the rotation transformation due to roll. The error of this approximation increases with
            // the absolute deviation of the pitch and roll angles from 0 degrees.
            const drawLabels = this.showHeadingLabels.get();
            const scaleFactor = projection.getScaleFactor();
            const headingRad = projection.getHeading() * Avionics.Utils.DEG2RAD;
            for (let i = 0; i < this.nodes.length; i++) {
                const node = this.nodes[i];
                const drawLabel = drawLabels && node.labelText !== undefined;
                const angle = (msfsSdk.MathUtils.diffAngle(headingRad, (node.heading + headingOffset) * Avionics.Utils.DEG2RAD) + Math.PI) % msfsSdk.MathUtils.TWO_PI - Math.PI;
                if (Math.abs(angle) < msfsSdk.MathUtils.HALF_PI) {
                    const offset = msfsSdk.Vec2Math.setFromPolar(1, angle, HorizonLine.vec2Cache[0]);
                    const z = offset[0];
                    const ratio = 1 / z;
                    const projectedX = offset[1] * ratio * scaleFactor;
                    this.approximateTransform.apply(msfsSdk.Vec2Math.set(projectedX, 0, HorizonLine.vec2Cache[0]), node.projected);
                    const isInBounds = projection.isInProjectedBounds(node.projected, this.bounds.get());
                    if (isInBounds && drawLabels) {
                        node.drawTick = true;
                        const tickLength = this.props.options.headingTickLength * ratio;
                        this.approximateTransform.apply(msfsSdk.Vec2Math.set(projectedX, -tickLength, HorizonLine.vec2Cache[0]), node.tickEndProjected);
                    }
                    else {
                        node.drawTick = false;
                    }
                    if (isInBounds && drawLabel) {
                        node.drawLabel = true;
                        node.labelFontSize = this.props.options.fontSize * ratio;
                    }
                    else {
                        node.drawLabel = false;
                    }
                }
                else {
                    msfsSdk.Vec2Math.set(NaN, NaN, node.projected);
                    node.drawTick = false;
                    node.drawLabel = false;
                }
            }
        }
        /**
         * Draws the horizon line on a canvas.
         * @param context The canvas rendering context to which to draw the line.
         */
        drawLine(context) {
            this.clipPathStream.beginPath();
            let needMoveTo = true;
            for (let i = 0; i < this.nodes.length; i++) {
                const projected = this.nodes[i].projected;
                if (msfsSdk.Vec2Math.isFinite(projected)) {
                    if (needMoveTo) {
                        this.clipPathStream.moveTo(projected[0], projected[1]);
                        needMoveTo = false;
                    }
                    else {
                        this.clipPathStream.lineTo(projected[0], projected[1]);
                    }
                }
                else {
                    needMoveTo = true;
                }
            }
            const first = this.nodes[0].projected;
            if (!needMoveTo && msfsSdk.Vec2Math.isFinite(first)) {
                this.clipPathStream.lineTo(first[0], first[1]);
            }
            this.strokePath(context, this.lineStrokeWidth, this.lineStrokeColor, this.lineOutlineWidth, this.lineOutlineColor);
        }
        /**
         * Draws this horizon line's heading reference pointer.
         * @param context The canvas rendering context to which to draw the pointer.
         * @param projection The horizon projection.
         */
        drawHeadingPointer(context, projection) {
            const size = this.props.options.headingPointerSize;
            const currentHeading = projection.getHeading();
            const currentHeadingProjected = HorizonLine.vec2Cache[0];
            if (this.approximate.get()) {
                this.approximateTransform.apply(msfsSdk.Vec2Math.set(0, 0, currentHeadingProjected), currentHeadingProjected);
            }
            else {
                projection.projectCameraRelativeAngular(1, currentHeading, 0, currentHeadingProjected);
            }
            if (!projection.isInProjectedBounds(currentHeadingProjected, this.bounds.get())) {
                return;
            }
            const halfWidth = size[0] / 2;
            this.transformPathStream
                .resetTransform()
                .addTranslation(0, -(this.lineStrokeWidth / 2 + this.lineOutlineWidth))
                .addRotation(-projection.getRoll() * Avionics.Utils.DEG2RAD)
                .addTranslation(currentHeadingProjected[0], currentHeadingProjected[1]);
            this.transformPathStream.beginPath();
            this.transformPathStream.moveTo(0, 0);
            this.transformPathStream.lineTo(-halfWidth, -size[1]);
            this.transformPathStream.lineTo(halfWidth, -size[1]);
            this.transformPathStream.closePath();
            context.fillStyle = this.lineStrokeColor;
            context.fill();
            this.strokePath(context, this.lineOutlineWidth, this.lineOutlineColor);
        }
        /**
         * Draws this horizon line's heading ticks on a canvas.
         * @param context The canvas rendering context to which to draw the ticks.
         * @param projection The horizon projection.
         */
        drawTicks(context, projection) {
            for (let i = 0; i < this.nodes.length; i++) {
                const node = this.nodes[i];
                this.drawTick(context, projection, node.projected, node.tickEndProjected, node.drawTick ? this.tickStrokeWidth : 0, node.labelText, node.drawLabel ? node.labelFontSize : undefined, this.labelOffset);
            }
        }
        /**
         * Draws a heading tick on a canvas.
         * @param context The canvas rendering context to which to draw the tick.
         * @param projection The horizon projection.
         * @param startProjected The projected position of the start of the tick.
         * @param endProjected The projected position of the end of the tick.
         * @param tickStrokeWidth The stroke width of the tick, in pixels.
         * @param labelText The text to render for the tick's heading label, or `undefined` if there is no label.
         * @param fontSize The virtual font size of the tick's heading label, or `undefined` if there is no label.
         * @param labelOffset The virtual offset of the tick's heading label, or `undefined` if there is no label.
         */
        drawTick(context, projection, startProjected, endProjected, tickStrokeWidth, labelText, fontSize, labelOffset) {
            if (tickStrokeWidth > 0) {
                context.beginPath();
                context.moveTo(startProjected[0], startProjected[1]);
                context.lineTo(endProjected[0], endProjected[1]);
                this.strokePath(context, tickStrokeWidth, this.tickStrokeColor);
            }
            if (labelText !== undefined && fontSize !== undefined && labelOffset !== undefined) {
                const delta = msfsSdk.Vec2Math.normalize(msfsSdk.Vec2Math.sub(endProjected, startProjected, HorizonLine.vec2Cache[1]), HorizonLine.vec2Cache[1]);
                const labelOriginX = endProjected[0] + delta[0] * labelOffset;
                const labelOriginY = endProjected[1] + delta[1] * labelOffset;
                const fontSizeScale = fontSize / this.props.options.fontSize;
                context.translate(labelOriginX, labelOriginY);
                context.scale(fontSizeScale, fontSizeScale);
                context.rotate(-projection.getRoll() * Avionics.Utils.DEG2RAD);
                if (this.fontOutlineWidth > 0) {
                    context.lineWidth = this.fontOutlineWidth * 2;
                    context.strokeStyle = this.fontOutlineColor;
                    context.strokeText(labelText, 0, 0);
                }
                context.fillText(labelText, 0, 0);
                context.resetTransform();
            }
        }
        /**
         * Strokes a path on a canvas.
         * @param context The canvas rendering context with which to stroke the path.
         * @param strokeWidth The width of the stroke.
         * @param strokeStyle The style of the stroke.
         * @param outlineWidth The outline width of the stroke. Defaults to `0`.
         * @param outlineStyle The outline style of the stroke. Required to draw an outline.
         */
        strokePath(context, strokeWidth, strokeStyle, outlineWidth = 0, outlineStyle) {
            if (outlineWidth > 0 && outlineStyle !== undefined) {
                context.lineWidth = strokeWidth + 2 * outlineWidth;
                context.strokeStyle = outlineStyle;
                context.stroke();
            }
            context.lineWidth = strokeWidth;
            context.strokeStyle = strokeStyle;
            context.stroke();
        }
        /** @inheritdoc */
        destroy() {
            for (const sub of this.subscriptions) {
                sub.destroy();
            }
            super.destroy();
        }
    }
    HorizonLine.TICK_INCREMENT = 10; // degrees per tick
    HorizonLine.TICK_COUNT = 360 / HorizonLine.TICK_INCREMENT;
    HorizonLine.LABEL_FACTOR = 3; // number of ticks per label
    HorizonLine.BOUNDS_BUFFER = 20; // pixels
    HorizonLine.DEFAULT_LINE_STROKE_WIDTH = 2; // pixels
    HorizonLine.DEFAULT_LINE_STROKE_COLOR = 'white';
    HorizonLine.DEFAULT_LINE_OUTLINE_WIDTH = 1; // pixels
    HorizonLine.DEFAULT_LINE_OUTLINE_COLOR = 'black';
    HorizonLine.DEFAULT_TICK_STROKE_WIDTH = 1; // pixels
    HorizonLine.DEFAULT_TICK_STROKE_COLOR = 'white';
    HorizonLine.DEFAULT_FONT_COLOR = 'white';
    HorizonLine.DEFAULT_FONT_OUTLINE_WIDTH = 1;
    HorizonLine.DEFAULT_FONT_OUTLINE_COLOR = 'black';
    HorizonLine.vec2Cache = [msfsSdk.Vec2Math.create(), msfsSdk.Vec2Math.create()];

    /**
     * A PFD pitch ladder. Displays major, medium, and minor pitch lines, each with optional pitch angle labels. Also
     * displays unusual attitude chevrons between major pitch lines above threshold positive and negative pitch angles.
     * Does not display a zero-pitch line.
     */
    class PitchLadder extends msfsSdk.HorizonLayer {
        constructor() {
            super(...arguments);
            this.svgRef = msfsSdk.FSComponent.createRef();
            this.rootStyle = msfsSdk.ObjectSubject.create({
                'display': '',
                'position': 'absolute',
                'left': '0px',
                'top': '0px',
                'width': '0px',
                'height': '0px',
                'overflow': 'hidden'
            });
            this.transform = msfsSdk.CssTransformSubject.create(msfsSdk.CssTransformBuilder.concat(msfsSdk.CssTransformBuilder.translate3d('px'), msfsSdk.CssTransformBuilder.rotate('deg'), msfsSdk.CssTransformBuilder.translateY('px')));
            this.clipBounds = msfsSdk.SubscribableUtils.toSubscribable(this.props.clipBounds, true);
            this.pitchResolution = 0; // pixels per degree
            this.needUpdateClip = false;
            this.needRebuildLadder = false;
            this.needReposition = false;
        }
        /** @inheritdoc */
        onVisibilityChanged(isVisible) {
            this.rootStyle.set('display', isVisible ? '' : 'none');
        }
        /** @inheritdoc */
        onAttached() {
            super.onAttached();
            this.showSub = this.props.show.sub(this.setVisible.bind(this), true);
            this.clipBoundsSub = this.clipBounds.sub(() => {
                this.needUpdateClip = true;
            });
            this.needUpdateClip = true;
            this.needRebuildLadder = true;
            this.needReposition = true;
        }
        /** @inheritdoc */
        onProjectionChanged(projection, changeFlags) {
            if (msfsSdk.BitFlags.isAny(changeFlags, msfsSdk.HorizonProjectionChangeType.Fov | msfsSdk.HorizonProjectionChangeType.ScaleFactor)) {
                this.needRebuildLadder = true;
            }
            else if (msfsSdk.BitFlags.isAny(changeFlags, msfsSdk.HorizonProjectionChangeType.OffsetCenterProjected)) {
                this.needUpdateClip = true;
            }
            this.needReposition = true;
        }
        /** @inheritdoc */
        onUpdated() {
            if (!this.isVisible()) {
                return;
            }
            if (this.needUpdateClip) {
                this.updateClip();
                this.needUpdateClip = false;
            }
            if (this.needRebuildLadder) {
                this.rebuildLadder();
                this.needRebuildLadder = false;
            }
            if (this.needReposition) {
                this.repositionLadder();
                this.needReposition = false;
            }
        }
        /**
         * Updates this ladder's clipping boundaries.
         */
        updateClip() {
            const center = this.props.projection.getOffsetCenterProjected();
            const bounds = this.clipBounds.get();
            this.rootStyle.set('left', `${center[0] + bounds[0]}px`);
            this.rootStyle.set('top', `${center[1] + bounds[1]}px`);
            this.rootStyle.set('width', `${bounds[2] - bounds[0]}px`);
            this.rootStyle.set('height', `${bounds[3] - bounds[1]}px`);
            // After we update the clip bounds we need to update the positioning of the ladder, because the ladder is
            // positioned relative to the clip bounds.
            this.needReposition = true;
        }
        /**
         * Repositions this ladder based on the current pitch and bank.
         */
        repositionLadder() {
            // Approximate translation due to pitch using a constant pitch resolution (pixels per degree of pitch) derived
            // from the projection's current field of view. This approximation always keeps the pitch ladder reading at the
            // center of the projection (i.e. at the symbolic aircraft reference) accurate. However, the error increases as
            // distance from the center of the projection increases because the true pitch resolution is not constant
            // throughout screen space. To get a truly accurate pitch ladder, we would need to project and position each pitch
            // line individually. Doing this via SVG is too performance-intensive (we would be redrawing the SVG every frame
            // that the pitch ladder is moving) and doing it via canvas looks horrible due to it not being able to draw text
            // with sub-pixel resolution.
            const bounds = this.clipBounds.get();
            const pitchOffset = this.props.projection.getPitch() * this.pitchResolution;
            this.transform.transform.getChild(0).set(-bounds[0], -bounds[1], 0, 0.1, 0.1);
            this.transform.transform.getChild(1).set(-this.props.projection.getRoll());
            this.transform.transform.getChild(2).set(pitchOffset, 0.1);
            this.transform.resolve();
        }
        /**
         * Rebuilds this ladder.
         */
        rebuildLadder() {
            this.pitchResolution = this.props.projection.getScaleFactor() / this.props.projection.getFov();
            const styles = this.props.isSVTEnabled.get() ? this.props.options.svtEnabledStyles : this.props.options.svtDisabledStyles;
            this.svgRef.instance.innerHTML = '';
            const majorLineSeparation = styles.majorLineIncrement * this.pitchResolution;
            const minorFactor = styles.minorLineFactor * styles.mediumLineFactor;
            const minorIncrement = styles.majorLineIncrement / minorFactor;
            const len = Math.floor(90 / minorIncrement);
            for (let i = 1; i <= len; i++) {
                const pitch = i * minorIncrement;
                const y = pitch * this.pitchResolution;
                let lineLength;
                let showNumber = false;
                if (i % minorFactor === 0) {
                    // major line
                    lineLength = styles.majorLineLength;
                    showNumber = styles.majorLineShowNumber;
                }
                else if (i % styles.minorLineFactor === 0 && pitch <= styles.mediumLineMaxPitch) {
                    // medium line
                    lineLength = styles.mediumLineLength;
                    showNumber = styles.mediumLineShowNumber;
                }
                else if (pitch <= styles.minorLineMaxPitch) {
                    // minor line
                    lineLength = styles.minorLineLength;
                    showNumber = styles.minorLineShowNumber;
                }
                if (lineLength !== undefined) {
                    if (lineLength > 0) {
                        msfsSdk.FSComponent.render(msfsSdk.FSComponent.buildComponent("line", { x1: -lineLength / 2, y1: -y, x2: lineLength / 2, y2: -y, class: 'pitch-ladder-line' }, "."), this.svgRef.instance);
                        msfsSdk.FSComponent.render(msfsSdk.FSComponent.buildComponent("line", { x1: -lineLength / 2, y1: y, x2: lineLength / 2, y2: y, class: 'pitch-ladder-line' }, "."), this.svgRef.instance);
                        if (i % minorFactor === 0) {
                            // major line
                            const lastMajorPitch = pitch - styles.majorLineIncrement;
                            const widthFactor = 0.5 + (pitch / 90) * 0.5;
                            const height = 0.9 * majorLineSeparation;
                            const width = lineLength * widthFactor;
                            const legWidth = width * 0.3;
                            // positive pitch chevron
                            if (lastMajorPitch >= styles.chevronThresholdPositive) {
                                msfsSdk.FSComponent.render(this.renderChevron(-y + majorLineSeparation / 2, height, width, legWidth, 1), this.svgRef.instance);
                            }
                            // negative pitch chevron
                            if (lastMajorPitch >= styles.chevronThresholdNegative) {
                                msfsSdk.FSComponent.render(this.renderChevron(y - majorLineSeparation / 2, height, width, legWidth, -1), this.svgRef.instance);
                            }
                        }
                    }
                    if (showNumber) {
                        const pitchText = pitch.toString();
                        const leftAnchorX = -lineLength / 2 - styles.numberMargin;
                        const rightAnchorX = lineLength / 2 + styles.numberMargin;
                        const textY = y + styles.numberOffsetY;
                        msfsSdk.FSComponent.render(msfsSdk.FSComponent.buildComponent("text", { x: leftAnchorX, y: -textY, "text-anchor": 'middle', "dominant-baseline": 'central', class: 'pitch-ladder-text' }, pitchText), this.svgRef.instance);
                        msfsSdk.FSComponent.render(msfsSdk.FSComponent.buildComponent("text", { x: rightAnchorX, y: -textY, "text-anchor": 'middle', "dominant-baseline": 'central', class: 'pitch-ladder-text' }, pitchText), this.svgRef.instance);
                        msfsSdk.FSComponent.render(msfsSdk.FSComponent.buildComponent("text", { x: leftAnchorX, y: textY, "text-anchor": 'middle', "dominant-baseline": 'central', class: 'pitch-ladder-text' }, pitchText), this.svgRef.instance);
                        msfsSdk.FSComponent.render(msfsSdk.FSComponent.buildComponent("text", { x: rightAnchorX, y: textY, "text-anchor": 'middle', "dominant-baseline": 'central', class: 'pitch-ladder-text' }, pitchText), this.svgRef.instance);
                    }
                }
            }
        }
        /**
         * Renders a warning chevron.
         * @param centerY The y coordinate of the center of the chevron, in pixels.
         * @param height The height of the chevron, in pixels.
         * @param width The width of the chevron, in pixels.
         * @param legWidth The width of each leg of the chevron, in pixels.
         * @param direction The direction in which the chevron is pointed: `1` for the positive y direction, `-1` for the
         * negative y direction.
         * @returns A warning chevron, as a VNode.
         */
        renderChevron(centerY, height, width, legWidth, direction) {
            const top = centerY - height / 2 * direction;
            const bottom = centerY + height / 2 * direction;
            const halfWidth = width / 2;
            const halfLegWidth = legWidth / 2;
            const legJoinHeight = legWidth * height / (width - legWidth);
            return (msfsSdk.FSComponent.buildComponent("path", { d: `M ${-halfLegWidth} ${bottom} L ${-width / 2} ${top} L ${-halfWidth + legWidth} ${top} L ${0} ${bottom - legJoinHeight * direction} L ${halfWidth - legWidth} ${top} L ${halfWidth} ${top} L ${halfLegWidth} ${bottom} Z`, class: 'pitch-ladder-chevron' }, "."));
        }
        /** @inheritdoc */
        onDetached() {
            super.onDetached();
            this.destroy();
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: 'pitch-ladder-container', style: this.rootStyle },
                msfsSdk.FSComponent.buildComponent("svg", { ref: this.svgRef, class: 'pitch-ladder', style: {
                        'position': 'absolute',
                        'left': '0px',
                        'top': '0px',
                        'transform': this.transform,
                        'transform-origin': '0px 0px',
                        'stroke-linecap': 'round',
                        'overflow': 'visible'
                    } })));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b;
            (_a = this.showSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.clipBoundsSub) === null || _b === void 0 ? void 0 : _b.destroy();
            super.destroy();
        }
    }

    /**
     * A PFD roll indicator. Displays a roll scale, roll pointer, and slip-skid indicator.
     */
    class RollIndicator extends msfsSdk.HorizonLayer {
        constructor() {
            // NOTE: Everything in this component is referenced to a coordinate system with the horizon projection projected
            // center as the origin and axes aligned with the horizon projection.
            super(...arguments);
            this.rootStyle = msfsSdk.ObjectSubject.create({
                'display': '',
                'position': 'absolute',
                'left': '0px',
                'top': '0px',
                'width': '0px',
                'height': '0px',
            });
            this.lowBankArcDisplay = this.props.showLowBankArc.map(show => show ? '' : 'none');
            this.slipSkidDisplay = this.props.showSlipSkid.map(show => show ? '' : 'none');
            this.rollTransform = msfsSdk.CssTransformSubject.create(msfsSdk.CssTransformBuilder.rotate3d('deg'));
            this.slipSkidTransform = msfsSdk.CssTransformSubject.create(msfsSdk.CssTransformBuilder.translate3d('px'));
            this.needUpdateRoll = false;
            this.needUpdateSlipSkid = false;
        }
        /** @inheritdoc */
        onVisibilityChanged(isVisible) {
            this.rootStyle.set('display', isVisible ? '' : 'none');
        }
        /** @inheritdoc */
        onAttached() {
            super.onAttached();
            this.turnCoordinatorBallSub = this.props.turnCoordinatorBall.sub(() => { this.needUpdateSlipSkid = true; });
            this.showSub = this.props.show.sub(this.setVisible.bind(this), true);
            this.updateRootPosition();
            this.needUpdateRoll = true;
            this.needUpdateSlipSkid = true;
        }
        /** @inheritdoc */
        onProjectionChanged(projection, changeFlags) {
            if (msfsSdk.BitFlags.isAll(changeFlags, msfsSdk.HorizonProjectionChangeType.OffsetCenterProjected)) {
                this.updateRootPosition();
            }
            if (msfsSdk.BitFlags.isAny(changeFlags, msfsSdk.HorizonProjectionChangeType.Roll)) {
                this.needUpdateRoll = true;
            }
        }
        /**
         * Updates this indicator's root container position.
         */
        updateRootPosition() {
            const offsetCenter = this.props.projection.getOffsetCenterProjected();
            this.rootStyle.set('left', `${offsetCenter[0]}px`);
            this.rootStyle.set('top', `${offsetCenter[1]}px`);
        }
        /** @inheritdoc */
        onUpdated() {
            if (!this.isVisible()) {
                return;
            }
            if (this.needUpdateRoll) {
                this.updateRoll();
                this.needUpdateRoll = false;
            }
            if (!this.props.showSlipSkid.get()) {
                return;
            }
            if (this.needUpdateSlipSkid) {
                this.updateSlipSkid();
                this.needUpdateSlipSkid = false;
            }
        }
        /**
         * Updates the rotation of the indicator.
         */
        updateRoll() {
            this.rollTransform.transform.set(0, 0, 1, -this.props.projection.getRoll(), 0.1);
            this.rollTransform.resolve();
        }
        /**
         * Updates the position of the slip/skid indicator.
         */
        updateSlipSkid() {
            const ball = this.props.turnCoordinatorBall.get();
            this.slipSkidTransform.transform.set(ball * this.props.options.slipSkidIndicatorTranslateScale, 0, 0, 0.1);
            this.slipSkidTransform.resolve();
        }
        /** @inheritdoc */
        onDetached() {
            super.onDetached();
            this.destroy();
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: 'roll-indicator', style: this.rootStyle },
                this.renderScale(),
                this.renderPointerContainer()));
        }
        /**
         * Renders the bank scale, which includes the bank reference pointer and the scale ticks.
         * @returns The bank scale, as a VNode.
         */
        renderScale() {
            const { radius, showArc, lowBankAngle, majorTickLength, minorTickLength, referencePointerSize, referencePointerOffset } = this.props.options;
            const svgPathStream = new msfsSdk.SvgPathStream(0.01);
            const transformPathStream = new msfsSdk.AffineTransformPathStream(svgPathStream);
            // arc
            let arcPath;
            if (showArc) {
                transformPathStream.addRotation(-Math.PI / 3 - Math.PI / 2);
                transformPathStream.beginPath();
                transformPathStream.moveTo(radius, 0);
                transformPathStream.arc(0, 0, radius, 0, 2 * Math.PI / 3);
                arcPath = svgPathStream.getSvgPath();
            }
            // zero-roll reference pointer
            transformPathStream.resetTransform();
            transformPathStream.addTranslation(0, -(radius + referencePointerOffset));
            transformPathStream.beginPath();
            const halfWidth = referencePointerSize[0] / 2;
            transformPathStream.moveTo(0, 0);
            transformPathStream.lineTo(-halfWidth, -referencePointerSize[1]);
            transformPathStream.lineTo(halfWidth, -referencePointerSize[1]);
            transformPathStream.closePath();
            const pointerPath = svgPathStream.getSvgPath();
            // ticks
            transformPathStream.beginPath();
            for (const angle of [10, 20, 45]) {
                transformPathStream.resetTransform();
                transformPathStream.addRotation(-angle * Avionics.Utils.DEG2RAD);
                transformPathStream.moveTo(0, -radius);
                transformPathStream.lineTo(0, -radius - minorTickLength);
                transformPathStream.addRotation(2 * angle * Avionics.Utils.DEG2RAD);
                transformPathStream.moveTo(0, -radius);
                transformPathStream.lineTo(0, -radius - minorTickLength);
            }
            for (const angle of [30, 60]) {
                transformPathStream.resetTransform();
                transformPathStream.addRotation(-angle * Avionics.Utils.DEG2RAD);
                transformPathStream.moveTo(0, -radius);
                transformPathStream.lineTo(0, -radius - majorTickLength);
                transformPathStream.addRotation(2 * angle * Avionics.Utils.DEG2RAD);
                transformPathStream.moveTo(0, -radius);
                transformPathStream.lineTo(0, -radius - majorTickLength);
            }
            const ticksPath = svgPathStream.getSvgPath();
            // low-bank arc
            let lowBankArcPath;
            if (lowBankAngle !== undefined) {
                transformPathStream.resetTransform();
                transformPathStream.addRotation(-lowBankAngle * Avionics.Utils.DEG2RAD - Math.PI / 2);
                transformPathStream.beginPath();
                transformPathStream.moveTo(radius, 0);
                transformPathStream.arc(0, 0, radius, 0, 2 * lowBankAngle * Avionics.Utils.DEG2RAD);
                lowBankArcPath = svgPathStream.getSvgPath();
            }
            const left = -radius * msfsSdk.MathUtils.SQRT3 / 2;
            const top = -radius;
            const width = -left * 2;
            const height = radius / 2;
            const leftPx = left.toFixed(2);
            const topPx = top.toFixed(2);
            const widthPx = width.toFixed(2);
            const heightPx = height.toFixed(2);
            const viewBox = `${leftPx} ${topPx} ${widthPx} ${heightPx}`;
            return (msfsSdk.FSComponent.buildComponent("div", { style: {
                    'position': 'absolute',
                    'left': '0px',
                    'top': '0px',
                    'width': '0px',
                    'height': '0px',
                    'transform': this.props.options.pointerStyle === 'sky' ? '' : this.rollTransform,
                } },
                msfsSdk.FSComponent.buildComponent("svg", { viewBox: viewBox, class: 'roll-indicator-scale', style: `position: absolute; left: ${leftPx}px; top: ${topPx}px; width: ${widthPx}px; height: ${heightPx}px; overflow: visible;` },
                    arcPath !== undefined && (msfsSdk.FSComponent.buildComponent("path", { d: arcPath, fill: 'none', class: 'roll-scale-arc roll-scale-stroke' })),
                    msfsSdk.FSComponent.buildComponent("path", { d: pointerPath, class: 'roll-scale-ref-pointer roll-scale-fill' }),
                    msfsSdk.FSComponent.buildComponent("path", { d: ticksPath, fill: 'none', class: 'roll-scale-ticks roll-scale-stroke' })),
                lowBankArcPath !== undefined && (msfsSdk.FSComponent.buildComponent("svg", { viewBox: viewBox, class: 'roll-indicator-low-bank-arc', style: {
                        'display': this.lowBankArcDisplay,
                        'position': 'absolute',
                        'left': `${leftPx}px`,
                        'top': `${topPx}px`,
                        'width': `${widthPx}px`,
                        'height': `${heightPx}px`,
                        'overflow': 'visible'
                    } },
                    msfsSdk.FSComponent.buildComponent("path", { d: lowBankArcPath, fill: 'none' })))));
        }
        /**
         * Renders the roll pointer and slip/skid indicator.
         * @returns The roll pointer and slip/skid indicator, as a VNode.
         */
        renderPointerContainer() {
            const pointerSize = this.props.options.rollPointerSize;
            const pointerTipRadius = this.props.options.radius - this.props.options.rollPointerOffset;
            const pointerHalfWidth = pointerSize[0] / 2;
            const pointerLeft = -pointerHalfWidth;
            const pointerTop = -pointerTipRadius;
            const pointerWidth = pointerHalfWidth * 2;
            const pointerHeight = pointerSize[1];
            const pointerPath = `M 0 ${-pointerTipRadius} l ${pointerLeft} ${pointerHeight} l ${pointerWidth} 0 Z`;
            const slipSkidTop = -pointerTipRadius + pointerHeight + Math.max(0, this.props.options.slipSkidIndicatorOffset);
            const slipSkidHeight = this.props.options.slipSkidIndicatorHeight;
            const slipSkidTopHalfWidth = msfsSdk.MathUtils.lerp(slipSkidTop + pointerTipRadius, 0, pointerHeight, 0, pointerHalfWidth);
            const slipSkidBottomHalfWidth = msfsSdk.MathUtils.lerp(slipSkidTop + pointerTipRadius + slipSkidHeight, 0, pointerHeight, 0, pointerHalfWidth);
            const slipSkidDeltaHalfWidth = slipSkidBottomHalfWidth - slipSkidTopHalfWidth;
            const slipSkidLeft = -slipSkidBottomHalfWidth;
            const slipSkidWidth = slipSkidBottomHalfWidth * 2;
            const slipSkidPath = `M ${-slipSkidTopHalfWidth} ${slipSkidTop} l ${slipSkidTopHalfWidth * 2} 0 l ${slipSkidDeltaHalfWidth} ${slipSkidHeight} l ${-slipSkidBottomHalfWidth * 2} 0 Z`;
            return (msfsSdk.FSComponent.buildComponent("div", { style: {
                    'position': 'absolute',
                    'left': '0px',
                    'top': '0px',
                    'width': '0px',
                    'height': '0px',
                    'transform': this.props.options.pointerStyle === 'sky' ? this.rollTransform : ''
                } },
                msfsSdk.FSComponent.buildComponent("svg", { viewBox: `${pointerLeft} ${pointerTop} ${pointerWidth} ${pointerHeight}`, class: 'roll-indicator-pointer roll-indicator-fill', style: `position: absolute; left: ${pointerLeft}px; top: ${pointerTop}px; width: ${pointerWidth}px; height: ${pointerHeight}px; overflow: visible;` },
                    msfsSdk.FSComponent.buildComponent("path", { d: pointerPath })),
                msfsSdk.FSComponent.buildComponent("svg", { viewBox: `${slipSkidLeft} ${slipSkidTop} ${slipSkidWidth} ${slipSkidHeight}`, class: 'roll-indicator-slip-skid roll-indicator-fill', style: {
                        'display': this.slipSkidDisplay,
                        'position': 'absolute',
                        'left': `${slipSkidLeft}px`,
                        'top': `${slipSkidTop}px`,
                        'width': `${slipSkidWidth}px`,
                        'height': `${slipSkidHeight}px`,
                        'transform': this.slipSkidTransform,
                        'overflow': 'visible'
                    } },
                    msfsSdk.FSComponent.buildComponent("path", { d: slipSkidPath }))));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b;
            this.lowBankArcDisplay.destroy();
            this.slipSkidDisplay.destroy();
            (_a = this.showSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.turnCoordinatorBallSub) === null || _b === void 0 ? void 0 : _b.destroy();
            super.destroy();
        }
    }

    /**
     * A synthetic vision technology (SVT) display.
     */
    class SyntheticVision extends msfsSdk.HorizonLayer {
        constructor() {
            super(...arguments);
            this.synVisRef = msfsSdk.FSComponent.createRef();
            this.rootStyle = msfsSdk.ObjectSubject.create({
                position: 'absolute',
                display: '',
                left: '0',
                top: '0',
                width: '100%',
                height: '100%'
            });
            this.resolution = msfsSdk.Vec2Subject.create(msfsSdk.Vec2Math.create(100, 100));
            this.needUpdateVisibility = false;
            this.needUpdate = false;
        }
        /** @inheritDoc */
        onVisibilityChanged() {
            this.needUpdateVisibility = true;
        }
        /** @inheritDoc */
        onAttached() {
            super.onAttached();
            this.isEnabledSub = this.props.isEnabled.sub(this.setVisible.bind(this), true);
            this.needUpdateVisibility = true;
            this.needUpdate = true;
        }
        /** @inheritDoc */
        onProjectionChanged(projection, changeFlags) {
            if (msfsSdk.BitFlags.isAny(changeFlags, msfsSdk.HorizonProjectionChangeType.ProjectedSize | msfsSdk.HorizonProjectionChangeType.ProjectedOffset)) {
                this.needUpdate = true;
            }
        }
        /** @inheritDoc */
        onWake() {
            this.synVisRef.instance.wake();
        }
        /** @inheritDoc */
        onSleep() {
            this.synVisRef.instance.sleep();
        }
        /** @inheritDoc */
        onUpdated() {
            const isVisible = this.isVisible();
            if (this.needUpdateVisibility) {
                this.rootStyle.set('display', isVisible ? '' : 'none');
            }
            if (!this.needUpdate || !isVisible) {
                return;
            }
            const projectedSize = this.props.projection.getProjectedSize();
            const projectedOffset = this.props.projection.getProjectedOffset();
            const offsetCenterProjected = this.props.projection.getOffsetCenterProjected();
            // We need to move the Bing texture such that its center lies at the center of the projection, including offset.
            // If there is an offset, we need to overdraw the Bing texture in order to fill the entire projection window.
            const xOverdraw = Math.abs(projectedOffset[0]);
            const yOverdraw = Math.abs(projectedOffset[1]);
            const bingWidth = projectedSize[0] + xOverdraw * 2;
            const bingHeight = projectedSize[1] + yOverdraw * 2;
            this.resolution.set(bingWidth, bingHeight);
            this.rootStyle.set('left', `${offsetCenterProjected[0] - bingWidth / 2}px`);
            this.rootStyle.set('top', `${offsetCenterProjected[1] - bingHeight / 2}px`);
            this.rootStyle.set('width', `${bingWidth}px`);
            this.rootStyle.set('height', `${bingHeight}px`);
            this.needUpdate = false;
        }
        /** @inheritDoc */
        onDetached() {
            super.onDetached();
            this.destroy();
        }
        /** @inheritDoc */
        render() {
            const colorsDef = SyntheticVision.createEarthColors();
            return (msfsSdk.FSComponent.buildComponent("div", { style: this.rootStyle },
                msfsSdk.FSComponent.buildComponent(msfsSdk.SynVisComponent, { ref: this.synVisRef, bingId: this.props.bingId, bingDelay: this.props.bingDelay, resolution: this.resolution, skyColor: msfsSdk.Subject.create(msfsSdk.BingComponent.hexaToRGBColor(SyntheticVision.SKY_COLOR)), earthColors: msfsSdk.ArraySubject.create(colorsDef.colors), earthColorsElevationRange: msfsSdk.Vec2Subject.create(colorsDef.elevationRange) })));
        }
        /** @inheritDoc */
        destroy() {
            var _a, _b;
            (_a = this.synVisRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.isEnabledSub) === null || _b === void 0 ? void 0 : _b.destroy();
            super.destroy();
        }
        /**
         * Creates an object containing an earth color array and elevation range for an SVT display.
         * @returns An object containing an earth color array and elevation range for an SVT display.
         */
        static createEarthColors() {
            // Get absolute map terrain colors and scale lightness by 0.8.
            const def = MapUtils.absoluteTerrainEarthColors();
            const cache = new Float64Array(3);
            return {
                colors: def.colors.map(color => {
                    const hsl = msfsSdk.ColorUtils.hexToHsl(color, cache, true);
                    hsl[2] *= 0.8;
                    return msfsSdk.ColorUtils.hslToHex(hsl, true);
                }),
                elevationRange: def.elevationRange.slice()
            };
        }
    }
    SyntheticVision.SKY_COLOR = '#0033E6';

    /**
     * A PFD TCAS-II resolution advisory pitch cue layer.
     */
    class TcasRaPitchCueLayer extends msfsSdk.HorizonLayer {
        constructor() {
            var _a, _b;
            super(...arguments);
            this.clipBounds = msfsSdk.SubscribableUtils.toSubscribable(this.props.clipBounds, true);
            this.conformalBounds = msfsSdk.SubscribableUtils.toSubscribable(this.props.conformalBounds, true);
            this.rootStyle = msfsSdk.ObjectSubject.create({
                'display': '',
                'position': 'absolute',
                'left': '0px',
                'top': '0px',
                'width': '0px',
                'height': '0px',
                'overflow': 'hidden'
            });
            this.clipCenterX = msfsSdk.Subject.create('');
            this.clipCenterY = msfsSdk.Subject.create('');
            this.noflyAboveDisplay = msfsSdk.Subject.create('');
            this.noflyAboveTransform = msfsSdk.CssTransformSubject.create(msfsSdk.CssTransformBuilder.concat(msfsSdk.CssTransformBuilder.rotate('deg'), msfsSdk.CssTransformBuilder.translate3d('px'), msfsSdk.CssTransformBuilder.translate('%')));
            this.noflyBelowDisplay = msfsSdk.Subject.create('');
            this.noflyBelowTransform = msfsSdk.CssTransformSubject.create(msfsSdk.CssTransformBuilder.concat(msfsSdk.CssTransformBuilder.rotate('deg'), msfsSdk.CssTransformBuilder.translate3d('px'), msfsSdk.CssTransformBuilder.translate('%')));
            this.flytoDisplay = msfsSdk.Subject.create('');
            this.flytoHeight = msfsSdk.Subject.create(0);
            this.flyToTransform = msfsSdk.CssTransformSubject.create(msfsSdk.CssTransformBuilder.concat(msfsSdk.CssTransformBuilder.rotate('deg'), msfsSdk.CssTransformBuilder.translate3d('px'), msfsSdk.CssTransformBuilder.translate('%')));
            this.tasSmoother = new msfsSdk.ExpSmoother((_a = this.props.tasSmoothingTau) !== null && _a !== void 0 ? _a : 2000 / Math.LN2);
            this.pitchSmoother = new msfsSdk.ExpSmoother((_b = this.props.pitchSmoothingTau) !== null && _b !== void 0 ? _b : 2000 / Math.LN2);
            this.smoothedTas = msfsSdk.Subject.create(0);
            this.smoothedPitch = msfsSdk.Subject.create(0);
            this.needUpdateClipBounds = false;
            this.needUpdateNoflyAbove = false;
            this.needUpdateNoflyBelow = false;
            this.needUpdateFlyto = false;
            this.pauseable = [];
        }
        /** @inheritdoc */
        onVisibilityChanged(isVisible) {
            if (isVisible) {
                this.rootStyle.set('display', '');
                for (const pauseable of this.pauseable) {
                    pauseable.resume();
                }
                this.checkNeedUpdateCues();
            }
            else {
                this.rootStyle.set('display', 'none');
                for (const pauseable of this.pauseable) {
                    pauseable.pause();
                }
                this.tasSmoother.reset();
                this.pitchSmoother.reset();
            }
        }
        /** @inheritdoc */
        onAttached() {
            super.onAttached();
            this.noflyAboveTransform.transform.getChild(2).set(-50, -100);
            this.noflyBelowTransform.transform.getChild(2).set(-50, 0);
            this.flyToTransform.transform.getChild(2).set(-50, 0);
            this.clipBoundsSub = this.clipBounds.sub(() => { this.needUpdateClipBounds = true; });
            const checkNeedUpdateCues = this.checkNeedUpdateCues.bind(this);
            const paused = !this.isVisible();
            this.pauseable.push(this.props.dataProvider.raMaxVs.sub(() => { this.needUpdateNoflyAbove = true; }, false, paused), this.props.dataProvider.raMinVs.sub(() => { this.needUpdateNoflyBelow = true; }, false, paused), this.props.dataProvider.raFlyToMaxVs.sub(() => { this.needUpdateFlyto = true; }, false, paused), this.props.dataProvider.raFlyToMinVs.sub(() => { this.needUpdateFlyto = true; }, false, paused), this.smoothedTas.sub(checkNeedUpdateCues, false, paused), this.smoothedPitch.sub(checkNeedUpdateCues, false, paused), this.conformalBounds.sub(() => {
                this.needUpdateNoflyAbove = true;
                this.needUpdateNoflyBelow = true;
            }, false, paused));
            this.showSub = this.props.show.sub(this.setVisible.bind(this), true);
            this.needUpdateClipBounds = true;
            this.needUpdateNoflyAbove = true;
            this.needUpdateNoflyBelow = true;
            this.needUpdateFlyto = true;
        }
        /**
         * Checks whether any of this layer's cues are visible, and if so, marks them to be updated.
         */
        checkNeedUpdateCues() {
            this.needUpdateNoflyAbove || (this.needUpdateNoflyAbove = this.props.dataProvider.raMaxVs.get() !== null);
            this.needUpdateNoflyBelow || (this.needUpdateNoflyBelow = this.props.dataProvider.raMinVs.get() !== null);
            this.needUpdateFlyto || (this.needUpdateFlyto = this.props.dataProvider.raFlyToMaxVs.get() !== null && this.props.dataProvider.raFlyToMinVs.get() !== null);
        }
        /** @inheritdoc */
        onProjectionChanged(projection, changeFlags) {
            this.needUpdateClipBounds || (this.needUpdateClipBounds = msfsSdk.BitFlags.isAny(changeFlags, msfsSdk.HorizonProjectionChangeType.OffsetCenterProjected));
            if (msfsSdk.BitFlags.isAny(changeFlags, ~msfsSdk.HorizonProjectionChangeType.Position)) {
                this.checkNeedUpdateCues();
            }
        }
        /** @inheritdoc */
        onSleep() {
            this.tasSmoother.reset();
            this.pitchSmoother.reset();
        }
        /** @inheritdoc */
        onUpdated(time, elapsed) {
            if (!this.isVisible()) {
                return;
            }
            this.updateSmoothedValues(elapsed);
            if (this.needUpdateClipBounds) {
                this.updateClipBounds();
                this.needUpdateClipBounds = false;
            }
            if (this.needUpdateNoflyAbove) {
                this.updateNoflyCue(1, this.props.dataProvider.raMaxVs.get(), this.noflyAboveDisplay, this.noflyAboveTransform);
                this.needUpdateNoflyAbove = false;
            }
            if (this.needUpdateNoflyBelow) {
                this.updateNoflyCue(-1, this.props.dataProvider.raMinVs.get(), this.noflyBelowDisplay, this.noflyBelowTransform);
                this.needUpdateNoflyBelow = false;
            }
            if (this.needUpdateFlyto) {
                this.updateFlytoCue();
                this.needUpdateFlyto = false;
            }
        }
        /**
         * Updates this layer's smoothed true airspeed and pitch values.
         * @param elapsed The elapsed time since the last update, in milliseconds.
         */
        updateSmoothedValues(elapsed) {
            const tas = this.props.tas.get();
            const dt = elapsed * this.props.simRate.get();
            if (dt > 0) {
                this.smoothedTas.set(this.tasSmoother.next(tas, dt));
                this.smoothedPitch.set(this.pitchSmoother.next(this.props.projection.getPitch(), dt));
            }
            else {
                this.smoothedTas.set(this.tasSmoother.reset(tas));
                this.smoothedPitch.set(this.pitchSmoother.reset(this.props.projection.getPitch()));
            }
        }
        /**
         * Updates this layer's clipping bounds.
         */
        updateClipBounds() {
            const center = this.props.projection.getOffsetCenterProjected();
            const bounds = this.clipBounds.get();
            const width = bounds[2] - bounds[0];
            const height = bounds[3] - bounds[1];
            this.rootStyle.set('left', `${center[0] + bounds[0]}px`);
            this.rootStyle.set('top', `${center[1] + bounds[1]}px`);
            this.rootStyle.set('width', `${width}px`);
            this.rootStyle.set('height', `${height}px`);
            this.clipCenterX.set(`${width / 2}px`);
            this.clipCenterY.set(`${height / 2}px`);
            this.checkNeedUpdateCues();
        }
        /**
         * Updates one of this layer's no-fly pitch cues.
         * @param direction The direction of the no-fly cue: +1 for above and -1 for below.
         * @param vsLimit The vertical speed limit, in feet per minute, commanded by the currently active resolution
         * advisory, or `null` if there is no such limit.
         * @param display The display style of the no-fly cue.
         * @param transform The CSS transform of the no-fly cue.
         */
        updateNoflyCue(direction, vsLimit, display, transform) {
            const currentTas = this.smoothedTas.get();
            if (currentTas < 1 || vsLimit === null) {
                display.set('none');
                return;
            }
            const currentVs = this.props.verticalSpeed.get();
            const projection = this.props.projection;
            const angularResolution = projection.getScaleFactor() / projection.getFov();
            const roll = projection.getRoll();
            const rotation = (-roll + 540) % 360 - 180; // -180 to 180
            const conformalBounds = this.conformalBounds.get();
            let minPitchOffsetPx;
            let maxPitchOffsetPx;
            if (direction === 1) {
                minPitchOffsetPx = -Infinity;
                if (rotation >= 90) {
                    maxPitchOffsetPx = msfsSdk.MathUtils.lerp(rotation, 90, 180, -conformalBounds[0], -conformalBounds[1]);
                }
                else if (rotation >= 0) {
                    maxPitchOffsetPx = msfsSdk.MathUtils.lerp(rotation, 0, 90, conformalBounds[3], -conformalBounds[0]);
                }
                else if (rotation >= -90) {
                    maxPitchOffsetPx = msfsSdk.MathUtils.lerp(rotation, -90, 0, conformalBounds[2], conformalBounds[3]);
                }
                else {
                    maxPitchOffsetPx = msfsSdk.MathUtils.lerp(rotation, -180, -90, -conformalBounds[1], conformalBounds[2]);
                }
            }
            else {
                maxPitchOffsetPx = Infinity;
                if (rotation >= 90) {
                    minPitchOffsetPx = msfsSdk.MathUtils.lerp(rotation, 90, 180, -conformalBounds[2], -conformalBounds[3]);
                }
                else if (rotation >= 0) {
                    minPitchOffsetPx = msfsSdk.MathUtils.lerp(rotation, 0, 90, conformalBounds[1], -conformalBounds[2]);
                }
                else if (rotation >= -90) {
                    minPitchOffsetPx = msfsSdk.MathUtils.lerp(rotation, -90, 0, conformalBounds[0], conformalBounds[1]);
                }
                else {
                    minPitchOffsetPx = msfsSdk.MathUtils.lerp(rotation, -180, -90, -conformalBounds[3], conformalBounds[0]);
                }
            }
            const deltaVs = vsLimit - currentVs;
            const deltaPitch = Math.asin(msfsSdk.MathUtils.clamp(deltaVs / msfsSdk.UnitType.KNOT.convertTo(currentTas, msfsSdk.UnitType.FPM), -1, 1)) * Avionics.Utils.RAD2DEG;
            const pitchOffsetPx = msfsSdk.MathUtils.clamp((this.smoothedPitch.get() - projection.getPitch() + deltaPitch) * -angularResolution, minPitchOffsetPx, maxPitchOffsetPx);
            transform.transform.getChild(0).set(rotation, 0.1);
            transform.transform.getChild(1).set(0, pitchOffsetPx, 0, 0.1, 0.1);
            transform.resolve();
            display.set('');
        }
        /**
         * Updates this layer's fly-to pitch cue.
         */
        updateFlytoCue() {
            const currentTas = this.smoothedTas.get();
            const minVs = this.props.dataProvider.raFlyToMinVs.get();
            const maxVs = this.props.dataProvider.raFlyToMaxVs.get();
            if (currentTas < 1 || minVs === null || maxVs === null) {
                this.flytoDisplay.set('none');
                return;
            }
            const currentVs = this.props.verticalSpeed.get();
            const projection = this.props.projection;
            const angularResolution = projection.getScaleFactor() / projection.getFov();
            const tasFpm = msfsSdk.UnitType.KNOT.convertTo(currentTas, msfsSdk.UnitType.FPM);
            const deltaMinVs = minVs - currentVs;
            const deltaMaxVs = maxVs - currentVs;
            const deltaMinPitch = Math.asin(msfsSdk.MathUtils.clamp(deltaMinVs / tasFpm, -1, 1)) * Avionics.Utils.RAD2DEG;
            const deltaMaxPitch = Math.asin(msfsSdk.MathUtils.clamp(deltaMaxVs / tasFpm, -1, 1)) * Avionics.Utils.RAD2DEG;
            const pitchOffsetPx = (this.smoothedPitch.get() - projection.getPitch() + deltaMaxPitch) * -angularResolution;
            const pitchHeightPx = (deltaMaxPitch - deltaMinPitch) * angularResolution;
            this.flytoHeight.set(Math.round(pitchHeightPx));
            this.flyToTransform.transform.getChild(0).set(-projection.getRoll(), 0.1);
            this.flyToTransform.transform.getChild(1).set(0, pitchOffsetPx, 0, 0.1, 0.1);
            this.flyToTransform.resolve();
            this.flytoDisplay.set('');
        }
        /** @inheritdoc */
        onDetached() {
            super.onDetached();
            this.destroy();
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: 'tcas-ra-pitch', style: this.rootStyle },
                msfsSdk.FSComponent.buildComponent("svg", { viewBox: '-191 -140 382 140', class: 'tcas-ra-pitch-nofly tcas-ra-pitch-nofly-above', style: {
                        'display': this.noflyAboveDisplay,
                        'position': 'absolute',
                        'left': this.clipCenterX,
                        'top': this.clipCenterY,
                        'transform': this.noflyAboveTransform,
                        'transform-origin': '0px 0px',
                        'overflow': 'visible'
                    } },
                    msfsSdk.FSComponent.buildComponent("path", { d: 'M -185.17 -139.83 l -5.66 0 l 0 5.66 z', class: 'tcas-ra-pitch-nofly-end' }),
                    msfsSdk.FSComponent.buildComponent("path", { d: 'M 185.17 -139.83 l 5.66 0 l 0 5.66 z', class: 'tcas-ra-pitch-nofly-end' }),
                    msfsSdk.FSComponent.buildComponent("path", { d: 'M -185.17 -139.83 l -5.66 5.66 l 137.83 134.17 l 106 0 l 137.83 -134.17 l -5.66 -5.66 l -132.17 135.83 l -106 0 z', class: 'tcas-ra-pitch-nofly-main' })),
                msfsSdk.FSComponent.buildComponent("svg", { viewBox: '-191 0 382 140', class: 'tcas-ra-pitch-nofly tcas-ra-pitch-nofly-below', style: {
                        'display': this.noflyBelowDisplay,
                        'position': 'absolute',
                        'left': this.clipCenterX,
                        'top': this.clipCenterY,
                        'transform': this.noflyBelowTransform,
                        'transform-origin': '0px 0px',
                        'overflow': 'visible'
                    } },
                    msfsSdk.FSComponent.buildComponent("path", { d: 'M -185.17 139.83 l -5.66 0 l 0 -5.66 z', class: 'tcas-ra-pitch-nofly-end' }),
                    msfsSdk.FSComponent.buildComponent("path", { d: 'M 185.17 139.83 l 5.66 0 l 0 -5.66 z', class: 'tcas-ra-pitch-nofly-end' }),
                    msfsSdk.FSComponent.buildComponent("path", { d: 'M -185.17 139.83 l -5.66 -5.66 l 137.83 -134.17 l 106 0 l 137.83 134.17 l -5.66 5.66 l -132.17 -135.83 l -106 0 z', class: 'tcas-ra-pitch-nofly-main' })),
                msfsSdk.FSComponent.buildComponent("div", { class: 'tcas-ra-pitch-flyto', style: {
                        'display': this.flytoDisplay,
                        'position': 'absolute',
                        'left': this.clipCenterX,
                        'top': this.clipCenterY,
                        'height': this.flytoHeight.map(height => `${height}px`),
                        'transform': this.flyToTransform,
                        'transform-origin': '0px 0px'
                    } })));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b;
            for (const pauseable of this.pauseable) {
                pauseable.destroy();
            }
            (_a = this.showSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.clipBoundsSub) === null || _b === void 0 ? void 0 : _b.destroy();
            super.destroy();
        }
    }

    /* eslint-disable @typescript-eslint/no-non-null-assertion */
    /**
     * A next-generation (NXi, G3000, etc) Garmin PFD horizon display. Includes an artificial horizon, attitude indicator,
     * aircraft symbol, flight director, and synthetic vision technology (SVT) display.
     */
    class HorizonDisplay extends msfsSdk.DisplayComponent {
        constructor() {
            var _a, _b, _c, _d, _e, _f, _g;
            super(...arguments);
            this.horizonRef = msfsSdk.FSComponent.createRef();
            this.projectionParams = {
                position: new msfsSdk.GeoPoint(0, 0),
                altitude: 0,
                heading: 0,
                pitch: 0,
                roll: 0
            };
            this.adcIndex = msfsSdk.SubscribableUtils.toSubscribable(this.props.adcIndex, true);
            this.ahrsIndex = msfsSdk.SubscribableUtils.toSubscribable(this.props.ahrsIndex, true);
            this.fmsPosIndex = msfsSdk.SubscribableUtils.toSubscribable(this.props.fmsPosIndex, true);
            this.position = msfsSdk.ConsumerSubject.create(null, new LatLongAlt(0, 0, 0));
            this.heading = msfsSdk.ConsumerSubject.create(null, 0);
            this.pitch = msfsSdk.ConsumerSubject.create(null, 0);
            this.roll = msfsSdk.ConsumerSubject.create(null, 0);
            this.paramSubjects = [
                this.position,
                this.heading,
                this.pitch,
                this.roll
            ];
            this.simRate = msfsSdk.ConsumerSubject.create(null, 1);
            this.adcState = msfsSdk.ConsumerSubject.create(null, undefined);
            this.ahrsState = msfsSdk.ConsumerSubject.create(null, undefined);
            this.isHeadingDataValid = msfsSdk.ConsumerSubject.create(null, true);
            this.isAttitudeDataValid = msfsSdk.ConsumerSubject.create(null, true);
            this.fmsPosMode = msfsSdk.ConsumerSubject.create(null, exports.FmsPositionMode.None);
            this.verticalSpeed = this.props.tcasRaPitchCueLayerOptions && this.props.tcasRaCommandDataProvider
                ? msfsSdk.ConsumerSubject.create(null, 0)
                : undefined;
            this.tas = this.props.tcasRaPitchCueLayerOptions && this.props.tcasRaCommandDataProvider
                ? msfsSdk.ConsumerSubject.create(null, 0)
                : undefined;
            this.turnCoordinatorBall = msfsSdk.ConsumerSubject.create(null, 0);
            this.fdDataProvider = new DefaultFlightDirectorDataProvider(this.props.bus, (_b = (_a = this.props.flightDirectorOptions) === null || _a === void 0 ? void 0 : _a.pitchSmoothingTau) !== null && _b !== void 0 ? _b : 500 / Math.LN2, (_d = (_c = this.props.flightDirectorOptions) === null || _c === void 0 ? void 0 : _c.bankSmoothingTau) !== null && _d !== void 0 ? _d : 500 / Math.LN2);
            this.isSvtEnabled = msfsSdk.MappedSubject.create(([isHeadingDataValid, isAttitudeDataValid, fmsPosMode, svtEnabledSetting]) => {
                return svtEnabledSetting && isHeadingDataValid && isAttitudeDataValid && HorizonDisplay.SVT_SUPPORTED_FMS_POS_MODES.includes(fmsPosMode);
            }, this.isHeadingDataValid, this.isAttitudeDataValid, this.fmsPosMode, this.props.svtSettingManager.getSetting('svtEnabled'));
            this.horizonLineShowHeadingLabels = msfsSdk.MappedSubject.create(msfsSdk.SubscribableMapFunctions.and(), this.isHeadingDataValid, this.props.svtSettingManager.getSetting('svtHeadingLabelShow'), this.props.supportAdvancedSvt ? msfsSdk.Subject.create(true) : this.isSvtEnabled);
            this.apMaxBankId = msfsSdk.ConsumerSubject.create(null, 0);
            this.showLowBankArc = this.apMaxBankId.map(id => id === 1);
            this.showTcasRaPitchCueLayer = this.props.tcasRaPitchCueLayerOptions && this.props.tcasRaCommandDataProvider
                ? msfsSdk.MappedSubject.create(([isAttitudeDataValid, adcState]) => isAttitudeDataValid && adcState !== undefined && (adcState.current === undefined || adcState.current === msfsSdk.AvionicsSystemState.On), this.isAttitudeDataValid, this.adcState)
                : undefined;
            this.showFpm = this.props.supportAdvancedSvt
                ? msfsSdk.MappedSubject.create(([isHeadingDataValid, isAttitudeDataValid, isSvtEnabled, svtDisabledFpmShowSetting]) => {
                    return isHeadingDataValid && isAttitudeDataValid && (isSvtEnabled || svtDisabledFpmShowSetting);
                }, this.isHeadingDataValid, this.isAttitudeDataValid, this.isSvtEnabled, this.props.svtSettingManager.getSetting('svtDisabledFpmShow'))
                : undefined;
            this.showFlightDirector = msfsSdk.MappedSubject.create(([declutter, isAttitudeDataValid, isFdActive]) => !declutter && isAttitudeDataValid && isFdActive, this.props.declutter, this.isAttitudeDataValid, this.fdDataProvider.isFdActive);
            this.showFlightDirectorSingleCue = msfsSdk.SubscribableUtils.isSubscribable(this.props.flightDirectorFormat)
                ? msfsSdk.MappedSubject.create(([show, format]) => show && format === exports.FlightDirectorFormat.SingleCue, this.showFlightDirector, this.props.flightDirectorFormat)
                : this.props.flightDirectorFormat === exports.FlightDirectorFormat.SingleCue;
            this.showFlightDirectorDualCue = msfsSdk.SubscribableUtils.isSubscribable(this.props.flightDirectorFormat)
                ? msfsSdk.MappedSubject.create(([show, format]) => show && format === exports.FlightDirectorFormat.DualCue, this.showFlightDirector, this.props.flightDirectorFormat)
                : this.props.flightDirectorFormat === exports.FlightDirectorFormat.DualCue;
            this.aircraftSymbolFormat = msfsSdk.SubscribableUtils.isSubscribable(this.props.flightDirectorFormat)
                ? this.props.flightDirectorFormat.map(format => format === exports.FlightDirectorFormat.DualCue ? exports.AttitudeAircraftSymbolFormat.DualCue : exports.AttitudeAircraftSymbolFormat.SingleCue)
                : this.props.flightDirectorFormat === exports.FlightDirectorFormat.DualCue ? exports.AttitudeAircraftSymbolFormat.DualCue : exports.AttitudeAircraftSymbolFormat.SingleCue;
            this.normalFov = (_e = this.props.normalFov) !== null && _e !== void 0 ? _e : HorizonDisplay.DEFAULT_NORMAL_FOV;
            this.extendedFov = (_f = this.props.extendedFov) !== null && _f !== void 0 ? _f : HorizonDisplay.DEFAULT_EXTENDED_FOV;
            this.fov = this.props.supportAdvancedSvt
                ? this.isSvtEnabled.map(isEnabled => isEnabled ? HorizonDisplay.BING_FOV : this.normalFov)
                : this.isSvtEnabled.map(isEnabled => isEnabled ? HorizonDisplay.BING_FOV : this.extendedFov);
            this.nonSvtFovEndpoints = msfsSdk.VecNMath.create(4, 0.5, 0, 0.5, 1);
            this.svtFovEndpoints = msfsSdk.VecNSubject.create(msfsSdk.VecNMath.create(4, 0.5, 0, 0.5, 1));
            this.fovEndpoints = msfsSdk.VecNSubject.create(msfsSdk.VecNMath.create(4, 0.5, 0, 0.5, 1));
            this.occlusions = (_g = this.props.occlusions) !== null && _g !== void 0 ? _g : msfsSdk.ArraySubject.create();
            this.ahrsAlignState = msfsSdk.MappedSubject.create(([ahrsState, isAttitudeDataValid]) => {
                const isAhrsOk = ahrsState === undefined || ahrsState.current === undefined || ahrsState.current === msfsSdk.AvionicsSystemState.On;
                if (isAhrsOk) {
                    return isAttitudeDataValid ? 'ok' : 'failed';
                }
                else {
                    return ahrsState.current === msfsSdk.AvionicsSystemState.Initializing ? 'aligning' : 'failed';
                }
            }, this.ahrsState, this.isAttitudeDataValid);
            this.ahrsAlignDisplay = this.ahrsAlignState.map(state => state === 'aligning' ? '' : 'none');
            this.showFailureBox = this.ahrsAlignState.map(state => state === 'failed');
            this.isAlive = true;
            this.isAwake = true;
            this.updateFreq = msfsSdk.SubscribableUtils.toSubscribable(this.props.updateFreq, true);
            this.updateCycleHandler = this.onUpdated.bind(this);
        }
        /** @inheritdoc */
        onAfterRender() {
            var _a;
            this.horizonRef.instance.projection.onChange(this.onProjectionChanged.bind(this));
            if (!this.isAwake) {
                this.horizonRef.instance.sleep();
            }
            const sub = this.props.bus.getSubscriber();
            this.simRate.setConsumer(sub.on('simRate'));
            this.position.sub(pos => {
                this.projectionParams.position.set(pos.lat, pos.long);
                this.projectionParams.altitude = pos.alt;
            }, true);
            this.headingSub = this.heading.sub(heading => {
                this.projectionParams.heading = heading;
            }, true);
            this.pitchSub = this.pitch.sub(pitch => {
                this.projectionParams.pitch = -pitch;
            }, true);
            this.rollSub = this.roll.sub(roll => {
                this.projectionParams.roll = -roll;
            }, true);
            this.isAttitudeDataValid.sub(isValid => {
                var _a, _b, _c, _d, _e, _f;
                if (isValid) {
                    (_a = this.headingSub) === null || _a === void 0 ? void 0 : _a.resume(true);
                    (_b = this.pitchSub) === null || _b === void 0 ? void 0 : _b.resume(true);
                    (_c = this.rollSub) === null || _c === void 0 ? void 0 : _c.resume(true);
                }
                else {
                    (_d = this.headingSub) === null || _d === void 0 ? void 0 : _d.pause();
                    (_e = this.pitchSub) === null || _e === void 0 ? void 0 : _e.pause();
                    (_f = this.rollSub) === null || _f === void 0 ? void 0 : _f.pause();
                    this.projectionParams.heading = 0;
                    this.projectionParams.pitch = 0;
                    this.projectionParams.roll = 0;
                }
            }, true);
            this.adcIndexSub = this.adcIndex.sub(index => {
                var _a, _b;
                (_a = this.verticalSpeed) === null || _a === void 0 ? void 0 : _a.setConsumer(sub.on(`adc_vertical_speed_${index}`));
                (_b = this.tas) === null || _b === void 0 ? void 0 : _b.setConsumer(sub.on(`adc_tas_${index}`));
                this.adcState.setConsumer(sub.on(`adc_state_${index}`));
            }, true);
            this.ahrsIndexSub = this.ahrsIndex.sub(index => {
                this.heading.setConsumer(sub.on(`ahrs_hdg_deg_true_${index}`));
                this.pitch.setConsumer(sub.on(`ahrs_pitch_deg_${index}`));
                this.roll.setConsumer(sub.on(`ahrs_roll_deg_${index}`));
                this.turnCoordinatorBall.setConsumer(sub.on(`ahrs_turn_coordinator_ball_${index}`));
                this.ahrsState.setConsumer(sub.on(`ahrs_state_${index}`));
                this.isHeadingDataValid.setConsumer(sub.on(`ahrs_heading_data_valid_${index}`));
                this.isAttitudeDataValid.setConsumer(sub.on(`ahrs_attitude_data_valid_${index}`));
            }, true);
            this.fmsPosIndexSub = this.fmsPosIndex.sub(index => {
                this.position.setConsumer(sub.on(`fms_pos_gps-position_${index}`));
                this.fmsPosMode.setConsumer(sub.on(`fms_pos_mode_${index}`));
            }, true);
            this.apMaxBankId.setConsumer(sub.on('ap_max_bank_id'));
            const svtEndpointsPipe = this.svtFovEndpoints.pipe(this.fovEndpoints, true);
            this.isSvtEnabled.sub(isEnabled => {
                if (isEnabled) {
                    svtEndpointsPipe.resume(true);
                }
                else {
                    svtEndpointsPipe.pause();
                    this.fovEndpoints.set(this.nonSvtFovEndpoints);
                }
            }, true);
            this.fdDataProvider.init(!this.isAwake);
            if (this.props.isSvtEnabled) {
                this.isSvtEnabledPipe = this.isSvtEnabled.pipe(this.props.isSvtEnabled);
            }
            this.recomputeSvtFovEndpoints(this.horizonRef.instance.projection);
            this.updateFreqSub = (_a = this.updateFreq) === null || _a === void 0 ? void 0 : _a.sub(freq => {
                var _a;
                (_a = this.updateCycleSub) === null || _a === void 0 ? void 0 : _a.destroy();
                this.updateCycleSub = this.props.bus.getSubscriber()
                    .on('realTime')
                    .atFrequency(freq)
                    .handle(this.updateCycleHandler, !this.isAwake);
            }, true);
        }
        /**
         * Wakes this horizon display. While awake, this display will be updated.
         * @throws Error if this horizon display is dead.
         */
        wake() {
            var _a, _b;
            if (!this.isAlive) {
                throw new Error('HorizonDisplay: cannot wake a dead display');
            }
            if (this.isAwake) {
                return;
            }
            this.isAwake = true;
            for (const subject of this.paramSubjects) {
                subject.resume();
            }
            this.fdDataProvider.resume();
            (_a = this.horizonRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.wake();
            (_b = this.updateCycleSub) === null || _b === void 0 ? void 0 : _b.resume(true);
        }
        /**
         * Puts this horizon display to sleep. While asleep, this display will not be updated.
         * @throws Error if this horizon display is dead.
         */
        sleep() {
            var _a, _b;
            if (!this.isAlive) {
                throw new Error('HorizonDisplay: cannot sleep a dead display');
            }
            if (!this.isAwake) {
                return;
            }
            this.isAwake = false;
            for (const subject of this.paramSubjects) {
                subject.pause();
            }
            this.fdDataProvider.pause();
            (_a = this.horizonRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.sleep();
            (_b = this.updateCycleSub) === null || _b === void 0 ? void 0 : _b.pause();
        }
        /**
         * Responds to changes in this horizon display's projection.
         * @param projection This display's horizon projection.
         * @param changeFlags The types of changes made to the projection.
         */
        onProjectionChanged(projection, changeFlags) {
            if (msfsSdk.BitFlags.isAny(changeFlags, msfsSdk.HorizonProjectionChangeType.ProjectedOffset
                | msfsSdk.HorizonProjectionChangeType.ProjectedSize)) {
                this.recomputeSvtFovEndpoints(projection);
            }
        }
        /**
         * Recomputes the endpoints at which the field of view of this display's projection is measured when synthetic
         * vision is enabled.
         * @param projection This display's horizon projection.
         */
        recomputeSvtFovEndpoints(projection) {
            const projectedSize = projection.getProjectedSize();
            const projectedOffset = projection.getProjectedOffset();
            const offsetCenterProjected = projection.getOffsetCenterProjected();
            // If there is a projected offset, then the Bing texture for synthetic vision needs to be overdrawn. This reduces
            // the effective FOV of the Bing texture if it is overdrawn vertically. In order to match this reduced FOV with the
            // horizon projection, we need to adjust the FOV endpoints so that they span the height of the entire Bing texture.
            const yOverdraw = Math.abs(projectedOffset[1]);
            const bingHeight = projectedSize[1] + yOverdraw * 2;
            const top = offsetCenterProjected[1] - bingHeight / 2;
            const bottom = top + bingHeight;
            this.svtFovEndpoints.set(0.5, top / projectedSize[1], 0.5, bottom / projectedSize[1]);
        }
        /**
         * This method is called every update cycle.
         * @param time The current time, as a UNIX timestamp in milliseconds.
         */
        onUpdated(time) {
            this.fdDataProvider.update(time);
            this.horizonRef.instance.projection.set(this.projectionParams);
            this.horizonRef.instance.update(time);
        }
        /** @inheritdoc */
        render() {
            var _a;
            const projection = new msfsSdk.HorizonProjection(100, 100, 60);
            return (msfsSdk.FSComponent.buildComponent(msfsSdk.HorizonComponent, { ref: this.horizonRef, projection: projection, projectedSize: this.props.projectedSize, fov: this.fov, fovEndpoints: this.fovEndpoints, projectedOffset: this.props.projectedOffset, class: this.props.class },
                msfsSdk.FSComponent.buildComponent(SyntheticVision, { projection: projection, bingId: this.props.bingId, bingDelay: this.props.bingDelay, isEnabled: this.isSvtEnabled }),
                msfsSdk.FSComponent.buildComponent(msfsSdk.HorizonSharedCanvasLayer, { projection: projection },
                    msfsSdk.FSComponent.buildComponent(ArtificialHorizon, { show: msfsSdk.MappedSubject.create(([isAttitudeDataValid, isSvtEnabled]) => isAttitudeDataValid && !isSvtEnabled, this.isAttitudeDataValid, this.isSvtEnabled), options: this.props.artificialHorizonOptions }),
                    msfsSdk.FSComponent.buildComponent(HorizonLine, { show: this.isAttitudeDataValid, showHeadingLabels: this.horizonLineShowHeadingLabels, useMagneticHeading: this.props.useMagneticHeading, approximate: this.isSvtEnabled.map(msfsSdk.SubscribableMapFunctions.not()), occlusions: this.occlusions, options: this.props.horizonLineOptions })),
                msfsSdk.FSComponent.buildComponent(PitchLadder, { projection: projection, show: this.isAttitudeDataValid, isSVTEnabled: this.isSvtEnabled, clipBounds: this.props.pitchLadderOptions.clipBounds, options: this.props.pitchLadderOptions.options }),
                msfsSdk.FSComponent.buildComponent("div", { class: 'ahrs-align-msg', style: { 'display': this.ahrsAlignDisplay } }, "AHRS ALIGN: Keep Wings Level"),
                this.props.tcasRaPitchCueLayerOptions !== undefined && this.props.tcasRaCommandDataProvider !== undefined && (msfsSdk.FSComponent.buildComponent(TcasRaPitchCueLayer, Object.assign({ projection: projection, show: this.showTcasRaPitchCueLayer, dataProvider: this.props.tcasRaCommandDataProvider, verticalSpeed: this.verticalSpeed, tas: this.tas, simRate: this.simRate }, this.props.tcasRaPitchCueLayerOptions))),
                msfsSdk.FSComponent.buildComponent(RollIndicator, { projection: projection, show: msfsSdk.Subject.create(true), showSlipSkid: this.isAttitudeDataValid, showLowBankArc: this.showLowBankArc, turnCoordinatorBall: this.turnCoordinatorBall, options: this.props.rollIndicatorOptions }),
                msfsSdk.FSComponent.buildComponent(FlightPathMarker, { projection: projection, bus: this.props.bus, show: (_a = this.showFpm) !== null && _a !== void 0 ? _a : this.isSvtEnabled }),
                this.showFlightDirectorSingleCue !== false && this.props.flightDirectorSingleCueOptions !== undefined && (msfsSdk.FSComponent.buildComponent(FlightDirectorSingleCue, Object.assign({ projection: projection, show: this.showFlightDirectorSingleCue === true ? this.showFlightDirector : this.showFlightDirectorSingleCue, fdPitch: this.fdDataProvider.fdPitch, fdBank: this.fdDataProvider.fdBank }, this.props.flightDirectorSingleCueOptions))),
                msfsSdk.FSComponent.buildComponent(AttitudeAircraftSymbol, { projection: projection, show: msfsSdk.Subject.create(true), format: this.aircraftSymbolFormat, color: this.props.aircraftSymbolOptions.color }),
                this.showFlightDirectorDualCue !== false && this.props.flightDirectorDualCueOptions !== undefined && (msfsSdk.FSComponent.buildComponent(FlightDirectorDualCue, Object.assign({ projection: projection, show: this.showFlightDirectorDualCue === true ? this.showFlightDirector : this.showFlightDirectorDualCue, fdPitch: this.fdDataProvider.fdPitch, fdBank: this.fdDataProvider.fdBank }, this.props.flightDirectorDualCueOptions))),
                msfsSdk.FSComponent.buildComponent(FailureBox, { show: this.showFailureBox, class: 'attitude-failure-box' })));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c, _d, _e, _f, _g, _h;
            this.isAlive = false;
            for (const subject of this.paramSubjects) {
                subject.destroy();
            }
            this.fdDataProvider.destroy();
            this.adcState.destroy();
            this.ahrsState.destroy();
            this.isHeadingDataValid.destroy();
            this.isAttitudeDataValid.destroy();
            this.fmsPosMode.destroy();
            this.isSvtEnabled.destroy();
            this.apMaxBankId.destroy();
            this.horizonLineShowHeadingLabels.destroy();
            (_a = this.showFpm) === null || _a === void 0 ? void 0 : _a.destroy();
            this.showFlightDirector.destroy();
            if (msfsSdk.SubscribableUtils.isSubscribable(this.aircraftSymbolFormat)) {
                this.aircraftSymbolFormat.destroy();
            }
            if (msfsSdk.SubscribableUtils.isSubscribable(this.showFlightDirectorSingleCue)) {
                this.showFlightDirectorSingleCue.destroy();
            }
            if (msfsSdk.SubscribableUtils.isSubscribable(this.showFlightDirectorDualCue)) {
                this.showFlightDirectorDualCue.destroy();
            }
            (_b = this.updateFreqSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.updateCycleSub) === null || _c === void 0 ? void 0 : _c.destroy();
            (_d = this.adcIndexSub) === null || _d === void 0 ? void 0 : _d.destroy();
            (_e = this.ahrsIndexSub) === null || _e === void 0 ? void 0 : _e.destroy();
            (_f = this.fmsPosIndexSub) === null || _f === void 0 ? void 0 : _f.destroy();
            (_g = this.isSvtEnabledPipe) === null || _g === void 0 ? void 0 : _g.destroy();
            (_h = this.horizonRef.getOrDefault()) === null || _h === void 0 ? void 0 : _h.destroy();
            super.destroy();
        }
    }
    HorizonDisplay.BING_FOV = 50; // degrees
    HorizonDisplay.DEFAULT_NORMAL_FOV = 55; // degrees
    HorizonDisplay.DEFAULT_EXTENDED_FOV = 110; // degrees
    HorizonDisplay.SVT_SUPPORTED_FMS_POS_MODES = [
        exports.FmsPositionMode.Gps,
        exports.FmsPositionMode.Hns,
        exports.FmsPositionMode.Dme
    ];

    /**
     * Utility class for creating CDI scaling mode text formatters.
     */
    class CdiScaleFormatter {
        /**
         * Creates a function which formats CDI scaling modes to text strings.
         * @param useRnp Whether the formatter should output explicit RNP values for certain scaling modes (e.g. `'RNP2.00'`
         * instead of `'ENR'`).
         * @returns A function which formats CDI scaling modes to text strings.
         */
        static create(useRnp) {
            const textMap = useRnp ? CdiScaleFormatter.RNP_TEXT_MAP : CdiScaleFormatter.TEXT_MAP;
            return (mode) => {
                return textMap[mode];
            };
        }
    }
    CdiScaleFormatter.TEXT_MAP = {
        [exports.CDIScaleLabel.Approach]: 'APPR',
        [exports.CDIScaleLabel.Departure]: 'DPRT',
        [exports.CDIScaleLabel.Enroute]: 'ENR',
        [exports.CDIScaleLabel.LNav]: 'LNAV',
        [exports.CDIScaleLabel.LNavPlusV]: 'LNAV+V',
        [exports.CDIScaleLabel.LNavVNav]: 'L/VNAV',
        [exports.CDIScaleLabel.LP]: 'LP',
        [exports.CDIScaleLabel.LPPlusV]: 'LP+V',
        [exports.CDIScaleLabel.LPV]: 'LPV',
        [exports.CDIScaleLabel.RNP]: 'RNP',
        [exports.CDIScaleLabel.MissedApproach]: 'MAPR',
        [exports.CDIScaleLabel.Oceanic]: 'OCN',
        [exports.CDIScaleLabel.Terminal]: 'TERM',
        [exports.CDIScaleLabel.TerminalArrival]: 'TERM',
        [exports.CDIScaleLabel.TerminalDeparture]: 'TERM',
        [exports.CDIScaleLabel.Visual]: 'VISUAL',
        [exports.CDIScaleLabel.VfrEnroute]: 'VFR',
        [exports.CDIScaleLabel.VfrTerminal]: 'VFR',
        [exports.CDIScaleLabel.VfrApproach]: 'VFR'
    };
    CdiScaleFormatter.RNP_TEXT_MAP = {
        [exports.CDIScaleLabel.Approach]: 'APPR',
        [exports.CDIScaleLabel.Departure]: 'RNP0.30',
        [exports.CDIScaleLabel.Enroute]: 'RNP2.00',
        [exports.CDIScaleLabel.LNav]: 'LNAV',
        [exports.CDIScaleLabel.LNavPlusV]: 'LNAV+V',
        [exports.CDIScaleLabel.LNavVNav]: 'L/VNAV',
        [exports.CDIScaleLabel.LP]: 'LP',
        [exports.CDIScaleLabel.LPPlusV]: 'LP+V',
        [exports.CDIScaleLabel.LPV]: 'LPV',
        [exports.CDIScaleLabel.RNP]: 'RNP',
        [exports.CDIScaleLabel.MissedApproach]: 'RNP0.30',
        [exports.CDIScaleLabel.Oceanic]: 'RNP4.00',
        [exports.CDIScaleLabel.Terminal]: 'RNP1.00',
        [exports.CDIScaleLabel.TerminalArrival]: 'RNP1.00',
        [exports.CDIScaleLabel.TerminalDeparture]: 'RNP1.00',
        [exports.CDIScaleLabel.Visual]: 'VISUAL',
        [exports.CDIScaleLabel.VfrEnroute]: 'VFR',
        [exports.CDIScaleLabel.VfrTerminal]: 'VFR',
        [exports.CDIScaleLabel.VfrApproach]: 'VFR'
    };

    /**
     * Display modes for {@link HsiGpsIntegrityAnnunciation}.
     */
    exports.HsiGpsIntegrityAnnunciationMode = void 0;
    (function (HsiGpsIntegrityAnnunciationMode) {
        HsiGpsIntegrityAnnunciationMode["Ok"] = "Ok";
        HsiGpsIntegrityAnnunciationMode["GpsNotUsed"] = "GpsNotUsed";
        HsiGpsIntegrityAnnunciationMode["UnableRnp"] = "UnableRnp";
        HsiGpsIntegrityAnnunciationMode["GpsLoi"] = "GpsLoi";
    })(exports.HsiGpsIntegrityAnnunciationMode || (exports.HsiGpsIntegrityAnnunciationMode = {}));
    /**
     * A next-generation (NXi, G3000, etc) Garmin HSI GPS integrity annunciation.
     */
    class HsiGpsIntegrityAnnunciation extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.rootStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.rootCssClass = msfsSdk.SetSubject.create(['hsi-gps-annunc']);
            this.text = this.props.mode.map(mode => HsiGpsIntegrityAnnunciation.MODE_TEXT[mode]);
            this.okShowTimer = new msfsSdk.DebounceTimer();
        }
        /** @inheritdoc */
        onAfterRender() {
            this.modeSub = this.props.mode.sub(mode => {
                if (mode === exports.HsiGpsIntegrityAnnunciationMode.Ok) {
                    this.rootCssClass.add('hsi-gps-annunc-ok');
                    this.rootStyle.set('display', '');
                    this.okShowTimer.schedule(() => { this.rootStyle.set('display', 'none'); }, HsiGpsIntegrityAnnunciation.OK_SHOW_DURATION);
                }
                else {
                    this.rootCssClass.delete('hsi-gps-annunc-ok');
                    this.okShowTimer.clear();
                    this.rootStyle.set('display', '');
                }
            }, true);
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: this.rootCssClass, style: this.rootStyle }, this.text));
        }
        /** @inheritdoc */
        destroy() {
            var _a;
            this.okShowTimer.clear();
            this.text.destroy();
            (_a = this.modeSub) === null || _a === void 0 ? void 0 : _a.destroy();
            super.destroy();
        }
    }
    HsiGpsIntegrityAnnunciation.OK_SHOW_DURATION = 5000; // milliseconds
    HsiGpsIntegrityAnnunciation.MODE_TEXT = {
        [exports.HsiGpsIntegrityAnnunciationMode.Ok]: 'GPS\nINTEG\nOK',
        [exports.HsiGpsIntegrityAnnunciationMode.GpsNotUsed]: 'GPS\nNOT\nUSED',
        [exports.HsiGpsIntegrityAnnunciationMode.UnableRnp]: 'UNABLE\nRNP',
        [exports.HsiGpsIntegrityAnnunciationMode.GpsLoi]: 'GPS LOI'
    };

    /**
     * A compass display for a next-generation (NXi, G3000, etc) HSI.
     *
     * The compass display depicts a circular compass rose with minor tick marks every 5 degrees and major tick marks every
     * 10 degrees. The cardinal directions are labeled N/E/S/W, with numeric labels (3, 6, 12, ...) at every hour position
     * that does not represent a cardinal direction.
     *
     * The compass also optionally displays a single continuous circular line of arbitrary radius within the rose.
     */
    class HsiCompass extends msfsSdk.DisplayComponent {
        /** @inheritdoc */
        render() {
            const halfSize = this.props.size / 2;
            return (msfsSdk.FSComponent.buildComponent("svg", { viewBox: `${-halfSize} ${-halfSize} ${this.props.size} ${this.props.size}`, class: "hsi-compass" },
                msfsSdk.FSComponent.buildComponent("circle", { cx: 0, cy: 0, r: halfSize, class: "hsi-compass-background" }),
                msfsSdk.FSComponent.buildComponent("g", { class: "hsi-compass-ticks" }, this.renderTicks()),
                msfsSdk.FSComponent.buildComponent("g", { class: "hsi-compass-labels" }, this.renderLabels())));
        }
        /**
         * Renders this compass's tick marks.
         * @returns This compass's tick marks, as an array of VNodes.
         */
        renderTicks() {
            const lines = [];
            for (let i = 0; i < 360; i += 5) {
                const isMajor = i % 10 === 0;
                lines.push(msfsSdk.FSComponent.buildComponent("line", { x1: 0, y1: 0, x2: 0, y2: isMajor ? this.props.majorTickLength : this.props.minorTickLength, transform: `rotate(${i}) translate(0, ${-this.props.size / 2})`, class: `hsi-compass-tick hsi-compass-tick-${isMajor ? 'major' : 'minor'}` }));
            }
            return lines;
        }
        /**
         * Renders this compass's labels.
         * @returns This compass's labels, as an array of VNodes.
         */
        renderLabels() {
            const texts = ['N', '3', '6', 'E', '12', '15', 'S', '21', '24', 'W', '30', '33'];
            const letters = [];
            const increment = 360 / texts.length;
            let angle = 0;
            for (let i = 0; i < texts.length; i++) {
                letters.push(msfsSdk.FSComponent.buildComponent("text", { x: 0, y: 0, "text-anchor": "middle", "dominant-baseline": "hanging", transform: `rotate(${angle}) translate(0, ${-this.props.size / 2 + this.props.labelOffset})`, class: `hsi-compass-label hsi-compass-label-${i % 3 === 0 ? 'major' : 'minor'}` }, texts[i]));
                angle += increment;
            }
            return letters;
        }
    }

    /**
     * An animator for HSI needle rotations.
     */
    class NeedleAnimator {
        /**
         * Constructor.
         * @param turnRate The turn rate, in degrees per second, used by this animator.
         */
        constructor(turnRate) {
            this.turnRate = turnRate;
            /** The minimum change in rotation, in degrees, required to trigger an animation. */
            this.animationThreshold = this.turnRate * (1 / 15);
            this.animator = new msfsSdk.Animator();
            /** This animator's current rotation, in degrees. */
            this.rotation = this.animator.value;
        }
        /**
         * Animates a rotation. This animator's rotation will be animated to rotate toward a target rotation with easing at
         * the start and end. If another animation is currently active, it will be immediately stopped and replaced by the
         * new animation, and the ease in will be skipped.
         * @param target The target rotation, in degrees.
         */
        animateRotation(target) {
            const currentRotation = msfsSdk.NavMath.normalizeHeading(this.animator.value.get());
            const diff = Math.abs(msfsSdk.NavMath.diffAngle(currentRotation, target));
            if (diff >= this.animationThreshold) {
                // Make sure the current rotation is normalized before starting the animation.
                this.setRotation(currentRotation);
                const sign = msfsSdk.NavMath.getTurnDirection(currentRotation, target) === 'left' ? -1 : 1;
                this.animator.start(currentRotation + diff * sign, diff / this.turnRate * 1000, this.animator.isAnimating() ? NeedleAnimator.easeOut : NeedleAnimator.easeInOut);
            }
            else {
                this.setRotation(target);
            }
        }
        /**
         * Immediately sets this animator's rotation to a given value and cancels any animation in progress.
         * @param rotation The rotation to set, in degrees.
         */
        setRotation(rotation) {
            this.animator.set(msfsSdk.NavMath.normalizeHeading(rotation));
        }
        /**
         * Stops the current animation in progress, if any, and optionally sets this animator's rotation to the animation
         * target.
         * @param setAnimationTarget Whether to set this animator's rotation to the animation target. Defaults to `false`.
         */
        stopAnimation(setAnimationTarget = false) {
            this.animator.stop(setAnimationTarget);
        }
    }
    NeedleAnimator.easeInOut = msfsSdk.Easing.withEndpointParams(msfsSdk.Easing.bezier(0.5, 0.1, 0, 1, true));
    NeedleAnimator.easeOut = msfsSdk.Easing.withEndpointParams(msfsSdk.Easing.bezier(0.5, 0.5, 0, 1, true));

    /**
     * A turn rate indicator for a next-generation (NXi, G3000, etc) HSI.
     *
     * The turn rate indicator is rendered such that it sits on the top edge of an HSI compass. The indicator spans 24
     * degrees of arc on either side of the lubber line. Markings are present at 9 and 18 degrees on either side of the
     * lubber line. A turn rate vector is extended along an arc parallel to the compass edge subtending an angle equal to
     * the estimated change in heading over the next 6 seconds. If turn rate exceeds 4 degrees per second, the vector is
     * clamped to 24 degrees of arc and an arrowhead is added to the end.
     *
     * The turn rate indicator's root element should be positioned such that its bottom edge intersects the center of the
     * HSI compass, and its left edge sits flush with the left edge of the HSI compass (the width of the root element is
     * equal to the diameter of the compass).
     */
    class TurnRateIndicator extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.clipStyle = msfsSdk.ObjectSubject.create({
                position: 'absolute',
                left: '50%',
                top: '0px',
                width: '50%',
                height: '100%',
                overflow: 'hidden',
                transform: 'scaleX(1) rotateX(0deg)',
                'transform-origin': '0% 50%'
            });
            this.rotateStyle = msfsSdk.ObjectSubject.create({
                position: 'absolute',
                right: '0px',
                top: '0px',
                width: '200%',
                height: '100%',
                transform: 'rotate3d(0, 0, 1, 0deg)',
                'transform-origin': '50% 100%'
            });
            this.noArrowVectorPath = '';
            this.arrowVectorPath = '';
            this.vectorPath = msfsSdk.Subject.create('');
            this.vectorRotate = msfsSdk.Subject.create(0);
            this.vectorRotateSign = this.vectorRotate.map(rotate => rotate < 0 ? -1 : 1);
            this.vectorRotateMag = this.vectorRotate.map(Math.abs);
        }
        /** @inheritDoc */
        onAfterRender() {
            this.vectorRotateSign.sub(sign => {
                this.clipStyle.set('transform', `scaleX(${sign}) rotateX(0deg)`);
            }, true);
            this.vectorRotateMag.sub(rotate => {
                this.rotateStyle.set('transform', `rotate3d(0, 0, 1, ${rotate}deg)`);
            }, true);
        }
        /**
         * Sets this indicator's displayed turn rate.
         * @param turnRate The turn rate, in degrees per second. Positive values indicate a right-hand turn.
         */
        setTurnRate(turnRate) {
            this.vectorPath.set(Math.abs(turnRate) > 4 ? this.arrowVectorPath : this.noArrowVectorPath);
            this.vectorRotate.set(msfsSdk.MathUtils.clamp(msfsSdk.MathUtils.round(turnRate * 6, 0.1), -24, 24));
        }
        /** @inheritDoc */
        render() {
            var _a, _b, _c, _d, _e, _f, _g;
            const svgPathStream = new msfsSdk.SvgPathStream(0.1);
            const transformStream = new msfsSdk.AffineTransformPathStream(svgPathStream);
            const compassRadius = this.props.compassRadius;
            const height = (_a = this.props.height) !== null && _a !== void 0 ? _a : TurnRateIndicator.DEFAULT_HEIGHT;
            const tickOffset = (_b = this.props.tickOffset) !== null && _b !== void 0 ? _b : TurnRateIndicator.DEFAULT_TICK_OFFSET;
            const tickLength = (_c = this.props.tickLength) !== null && _c !== void 0 ? _c : TurnRateIndicator.DEFAULT_TICK_LENGTH;
            const vectorOffset = (_d = this.props.vectorOffset) !== null && _d !== void 0 ? _d : TurnRateIndicator.DEFAULT_VECTOR_OFFSET;
            const vectorWidth = (_e = this.props.vectorWidth) !== null && _e !== void 0 ? _e : TurnRateIndicator.DEFAULT_VECTOR_WIDTH;
            const vectorArrowWidth = (_f = this.props.vectorArrowWidth) !== null && _f !== void 0 ? _f : TurnRateIndicator.DEFAULT_VECTOR_ARROW_WIDTH;
            const vectorArrowLength = (_g = this.props.vectorArrowLength) !== null && _g !== void 0 ? _g : TurnRateIndicator.DEFAULT_VECTOR_ARROW_LENGTH;
            // ---- Build border ----
            const innerRadius = compassRadius;
            const outerRadius = compassRadius + height;
            transformStream.beginPath();
            transformStream.resetTransform();
            transformStream.addRotation(-msfsSdk.MathUtils.HALF_PI - TurnRateIndicator.HALF_ANGULAR_WIDTH * Avionics.Utils.DEG2RAD);
            transformStream.moveTo(innerRadius, 0);
            transformStream.lineTo(outerRadius, 0);
            transformStream.arc(0, 0, outerRadius, 0, 2 * TurnRateIndicator.HALF_ANGULAR_WIDTH * Avionics.Utils.DEG2RAD);
            transformStream.addRotation(2 * TurnRateIndicator.HALF_ANGULAR_WIDTH * Avionics.Utils.DEG2RAD);
            transformStream.lineTo(innerRadius, 0);
            transformStream.arc(0, 0, innerRadius, 0, -2 * TurnRateIndicator.HALF_ANGULAR_WIDTH * Avionics.Utils.DEG2RAD, true);
            const borderPath = svgPathStream.getSvgPath();
            // ---- Build ticks ----
            const tickStart = compassRadius + tickOffset;
            const tickEnd = tickStart + tickLength;
            transformStream.beginPath();
            transformStream.resetTransform();
            // -18 degrees
            transformStream.addRotation(-msfsSdk.MathUtils.HALF_PI - 18 * Avionics.Utils.DEG2RAD);
            transformStream.moveTo(tickStart, 0);
            transformStream.lineTo(tickEnd, 0);
            // -9 degrees
            transformStream.addRotation(9 * Avionics.Utils.DEG2RAD);
            transformStream.moveTo(tickStart, 0);
            transformStream.lineTo(tickEnd, 0);
            // +9 degrees
            transformStream.addRotation(18 * Avionics.Utils.DEG2RAD);
            transformStream.moveTo(tickStart, 0);
            transformStream.lineTo(tickEnd, 0);
            // +18 degrees
            transformStream.addRotation(9 * Avionics.Utils.DEG2RAD);
            transformStream.moveTo(tickStart, 0);
            transformStream.lineTo(tickEnd, 0);
            const ticksPath = svgPathStream.getSvgPath();
            // ---- Build no-arrow vector ----
            const vectorInnerRadius = compassRadius + vectorOffset;
            const vectorOuterRadius = innerRadius + vectorWidth;
            transformStream.beginPath();
            transformStream.resetTransform();
            transformStream.addRotation(-msfsSdk.MathUtils.HALF_PI - TurnRateIndicator.HALF_ANGULAR_WIDTH * Avionics.Utils.DEG2RAD);
            transformStream.moveTo(vectorInnerRadius, 0);
            transformStream.lineTo(vectorOuterRadius, 0);
            transformStream.arc(0, 0, vectorOuterRadius, 0, TurnRateIndicator.HALF_ANGULAR_WIDTH * Avionics.Utils.DEG2RAD);
            transformStream.addRotation(TurnRateIndicator.HALF_ANGULAR_WIDTH * Avionics.Utils.DEG2RAD);
            transformStream.lineTo(vectorInnerRadius, 0);
            transformStream.arc(0, 0, vectorInnerRadius, 0, -TurnRateIndicator.HALF_ANGULAR_WIDTH * Avionics.Utils.DEG2RAD, true);
            this.noArrowVectorPath = svgPathStream.getSvgPath();
            // ---- Build arrow vector ----
            const vectorCenter = (vectorInnerRadius + vectorOuterRadius) / 2;
            transformStream.beginPath();
            transformStream.resetTransform();
            transformStream.addRotation(-msfsSdk.MathUtils.HALF_PI - TurnRateIndicator.HALF_ANGULAR_WIDTH * Avionics.Utils.DEG2RAD);
            transformStream.moveTo(vectorInnerRadius, 0);
            transformStream.lineTo(vectorOuterRadius, 0);
            transformStream.arc(0, 0, vectorOuterRadius, 0, TurnRateIndicator.HALF_ANGULAR_WIDTH * Avionics.Utils.DEG2RAD);
            transformStream.addRotation(TurnRateIndicator.HALF_ANGULAR_WIDTH * Avionics.Utils.DEG2RAD);
            transformStream.lineTo(vectorCenter + vectorArrowWidth / 2, 0);
            transformStream.addRotation(vectorArrowLength / vectorCenter);
            transformStream.lineTo(vectorCenter, 0);
            transformStream.addRotation(-vectorArrowLength / vectorCenter);
            transformStream.lineTo(vectorCenter - vectorArrowWidth / 2, 0);
            transformStream.lineTo(vectorInnerRadius, 0);
            transformStream.arc(0, 0, vectorInnerRadius, 0, -TurnRateIndicator.HALF_ANGULAR_WIDTH * Avionics.Utils.DEG2RAD, true);
            this.arrowVectorPath = svgPathStream.getSvgPath();
            const viewBox = `${-compassRadius} ${-outerRadius} ${2 * compassRadius} ${outerRadius}`;
            return (msfsSdk.FSComponent.buildComponent("div", { class: "hsi-turn-rate-indicator", style: `width: ${compassRadius * 2}px; height: ${outerRadius}px` },
                msfsSdk.FSComponent.buildComponent("svg", { viewBox: viewBox, class: "hsi-turn-rate-indicator-background", style: "position: absolute; left: 0px; top: 0px; width: 100%; height: 100%;" },
                    msfsSdk.FSComponent.buildComponent("path", { d: borderPath, class: "hsi-turn-rate-indicator-border" }),
                    msfsSdk.FSComponent.buildComponent("path", { d: ticksPath, class: "hsi-turn-rate-indicator-ticks" })),
                msfsSdk.FSComponent.buildComponent("div", { class: "hsi-turn-rate-indicator-dynamic-clip", style: this.clipStyle },
                    msfsSdk.FSComponent.buildComponent("svg", { viewBox: viewBox, class: "hsi-turn-rate-indicator-dynamic", style: this.rotateStyle },
                        msfsSdk.FSComponent.buildComponent("path", { d: this.vectorPath, class: "hsi-turn-rate-indicator-vector" })))));
        }
    }
    TurnRateIndicator.HALF_ANGULAR_WIDTH = 24; // degrees
    TurnRateIndicator.DEFAULT_HEIGHT = 24; // px
    TurnRateIndicator.DEFAULT_TICK_OFFSET = 3; // px
    TurnRateIndicator.DEFAULT_TICK_LENGTH = 15; // px
    TurnRateIndicator.DEFAULT_VECTOR_OFFSET = 1; // px
    TurnRateIndicator.DEFAULT_VECTOR_WIDTH = 6; // px
    TurnRateIndicator.DEFAULT_VECTOR_ARROW_WIDTH = 12; // px
    TurnRateIndicator.DEFAULT_VECTOR_ARROW_LENGTH = 12; // px

    /**
     * A default implementation of {@link MarkerBeaconDataProvider}.
     */
    class DefaultMarkerBeaconDataProvider {
        /**
         * Constructor.
         * @param bus The event bus.
         * @param markerBeaconIndex The index of the AoA computer that is the source of this provider's data.
         */
        constructor(bus, markerBeaconIndex) {
            this.bus = bus;
            this._markerBeaconState = msfsSdk.ConsumerSubject.create(null, 0);
            /** @inheritdoc */
            this.markerBeaconState = this._markerBeaconState;
            this._isDataFailed = msfsSdk.Subject.create(false);
            /** @inheritdoc */
            this.isDataFailed = this._isDataFailed;
            this.markerSystemState = msfsSdk.ConsumerSubject.create(null, { previous: undefined, current: undefined });
            this.isInit = false;
            this.isAlive = true;
            this.isPaused = false;
            this.markerBeaconIndex = msfsSdk.SubscribableUtils.toSubscribable(markerBeaconIndex, true);
        }
        /**
         * Initializes this data provider. Once initialized, this data provider will continuously update its data until
         * paused or destroyed.
         * @param paused Whether to initialize this data provider as paused. If `true`, this data provider will provide an
         * initial set of data but will not update the provided data until it is resumed. Defaults to `false`.
         * @throws Error if this data provider is dead.
         */
        init(paused = false) {
            if (!this.isAlive) {
                throw new Error('DefaultMarkerBeaconDataProvider: cannot initialize a dead provider');
            }
            if (this.isInit) {
                return;
            }
            this.isInit = true;
            this.isPaused = paused;
            const sub = this.bus.getSubscriber();
            this.markerBeaconIndexSub = this.markerBeaconIndex.sub(index => {
                this._markerBeaconState.setConsumer(sub.on(`marker_mkr_bcn_state_${index}`));
                this.markerSystemState.setConsumer(sub.on(`marker_state_${index}`));
            }, true);
            this.markerSystemState.sub(state => {
                if (state.current === undefined || state.current === msfsSdk.AvionicsSystemState.On) {
                    this._isDataFailed.set(false);
                }
                else {
                    this._isDataFailed.set(true);
                }
            }, true);
            if (paused) {
                this.pause();
            }
        }
        /**
         * Resumes this data provider. Once resumed, this data provider will continuously update its data until paused or
         * destroyed.
         * @throws Error if this data provider is dead.
         */
        resume() {
            if (!this.isAlive) {
                throw new Error('DefaultMarkerBeaconDataProvider: cannot resume a dead provider');
            }
            if (!this.isInit || !this.isPaused) {
                return;
            }
            this.isPaused = false;
            this._markerBeaconState.resume();
            this.markerSystemState.resume();
        }
        /**
         * Pauses this data provider. Once paused, this data provider will not update its data until it is resumed.
         * @throws Error if this data provider is dead.
         */
        pause() {
            if (!this.isAlive) {
                throw new Error('DefaultMarkerBeaconDataProvider: cannot pause a dead provider');
            }
            if (!this.isInit || this.isPaused) {
                return;
            }
            this.isPaused = true;
            this._markerBeaconState.pause();
            this.markerSystemState.pause();
        }
        /**
         * Destroys this data provider. Once destroyed, this data provider will no longer update its provided data, and can
         * no longer be paused or resumed.
         */
        destroy() {
            var _a;
            this.isAlive = false;
            this._markerBeaconState.destroy();
            this.markerSystemState.destroy();
            (_a = this.markerBeaconIndexSub) === null || _a === void 0 ? void 0 : _a.destroy();
        }
    }

    /**
     * The PFD marker beacon annunciation display.
     */
    class MarkerBeaconDisplay extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.rootStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.rootCssClass = msfsSdk.SetSubject.create(['marker-beacon']);
            this.text = msfsSdk.Subject.create('');
        }
        /** @inheritdoc */
        onAfterRender() {
            const stateSub = this.stateSub = this.props.dataProvider.markerBeaconState.sub(state => {
                if (state === msfsSdk.MarkerBeaconState.Inactive) {
                    this.rootStyle.set('display', 'none');
                }
                else {
                    this.rootStyle.set('display', '');
                }
                this.rootCssClass.toggle('marker-beacon-outer', state === msfsSdk.MarkerBeaconState.Outer);
                this.rootCssClass.toggle('marker-beacon-middle', state === msfsSdk.MarkerBeaconState.Middle);
                this.rootCssClass.toggle('marker-beacon-inner', state === msfsSdk.MarkerBeaconState.Inner);
                this.text.set(MarkerBeaconDisplay.TEXT[state]);
            }, false, true);
            const isDataFailedSub = this.isDataFailedSub = this.props.dataProvider.isDataFailed.sub(isFailed => {
                if (isFailed) {
                    stateSub.pause();
                    this.rootStyle.set('display', 'none');
                }
                else {
                    stateSub.resume(true);
                }
            }, false, true);
            this.declutterSub = this.props.declutter.sub(declutter => {
                if (declutter) {
                    isDataFailedSub.pause();
                    this.rootStyle.set('display', 'none');
                }
                else {
                    isDataFailedSub.resume(true);
                }
            }, true);
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: this.rootCssClass, style: this.rootStyle }, this.text));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c;
            (_a = this.stateSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.isDataFailedSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.declutterSub) === null || _c === void 0 ? void 0 : _c.destroy();
            super.destroy();
        }
    }
    MarkerBeaconDisplay.TEXT = {
        [msfsSdk.MarkerBeaconState.Inactive]: '',
        [msfsSdk.MarkerBeaconState.Outer]: 'O',
        [msfsSdk.MarkerBeaconState.Middle]: 'M',
        [msfsSdk.MarkerBeaconState.Inner]: 'I',
    };

    /**
     * A next-generation (NXi, G3000, etc) Garmin PFD minimums display.
     */
    class MinimumsDisplay extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.valueRef = msfsSdk.FSComponent.createRef();
            this.rootStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.rootCssClass = msfsSdk.SetSubject.create(['minimums']);
            this.modeText = this.props.dataProvider.mode.map(mode => {
                switch (mode) {
                    case msfsSdk.MinimumsMode.BARO:
                        return 'BARO';
                    case msfsSdk.MinimumsMode.RA:
                        return 'RA';
                    default:
                        return '';
                }
            });
            this.minimumsValue = msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.FOOT.createNumber(0));
        }
        /** @inheritdoc */
        onAfterRender() {
            const minimumsAlertSub = this.minimumsAlertSub = this.props.minimumsAlertState.sub(state => {
                switch (state) {
                    case exports.MinimumsAlertState.Within100:
                        this.rootCssClass.delete('minimums-alert-atorbelow');
                        this.rootCssClass.add('minimums-alert-within100');
                        break;
                    case exports.MinimumsAlertState.AtOrBelow:
                        this.rootCssClass.delete('minimums-alert-within100');
                        this.rootCssClass.add('minimums-alert-atorbelow');
                        break;
                    default:
                        this.rootCssClass.delete('minimums-alert-within100');
                        this.rootCssClass.delete('minimums-alert-atorbelow');
                }
            }, false, true);
            const minimumsPipe = this.minimumsPipe = this.props.dataProvider.minimums.pipe(this.minimumsValue, minimums => minimums !== null && minimums !== void 0 ? minimums : 0, true);
            const modeSub = this.modeSub = this.props.dataProvider.mode.sub(mode => {
                if (mode === msfsSdk.MinimumsMode.OFF) {
                    this.modeText.pause();
                    minimumsPipe.pause();
                    minimumsAlertSub.pause();
                    this.rootStyle.set('display', 'none');
                }
                else {
                    this.rootStyle.set('display', '');
                    this.modeText.resume();
                    minimumsPipe.resume(true);
                    minimumsAlertSub.resume(true);
                }
            }, false, true);
            this.declutterSub = this.props.declutter.sub(declutter => {
                if (declutter) {
                    modeSub.pause();
                    this.modeText.pause();
                    minimumsPipe.pause();
                    minimumsAlertSub.pause();
                    this.rootStyle.set('display', 'none');
                }
                else {
                    modeSub.resume(true);
                }
            }, true);
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: this.rootCssClass, style: this.rootStyle },
                msfsSdk.FSComponent.buildComponent("div", { class: 'minimums-title' },
                    msfsSdk.FSComponent.buildComponent("div", { class: 'minimums-mode' }, this.modeText),
                    msfsSdk.FSComponent.buildComponent("div", { class: 'minimums-min' }, "MIN")),
                msfsSdk.FSComponent.buildComponent(NumberUnitDisplay, { ref: this.valueRef, value: this.minimumsValue, displayUnit: this.props.unitsSettingManager.altitudeUnits, formatter: msfsSdk.NumberFormatter.create({ precision: 1 }), class: 'minimums-value' })));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c, _d, _e;
            (_a = this.valueRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.modeSub) === null || _b === void 0 ? void 0 : _b.destroy();
            this.modeText.destroy();
            (_c = this.minimumsPipe) === null || _c === void 0 ? void 0 : _c.destroy();
            (_d = this.minimumsAlertSub) === null || _d === void 0 ? void 0 : _d.destroy();
            (_e = this.declutterSub) === null || _e === void 0 ? void 0 : _e.destroy();
            super.destroy();
        }
    }

    /**
     * A factory for navigation status box field data models.
     */
    class NavStatusBoxFieldModelFactory {
        /**
         * Constructor.
         * @param bus The event bus.
         * @param gpsValidity The subscribable that provides the validity of the GPS data for the models.
         * @param lnavIndex The index of the LNAV from which to source data. Defaults to `0`.
         */
        constructor(bus, gpsValidity, lnavIndex) {
            this.gpsValidity = gpsValidity;
            this.factories = new Map();
            this.factories.set(exports.NavDataFieldType.BearingToWaypoint, new NavStatusBoxFieldBrgModelFactory(bus, lnavIndex));
            this.factories.set(exports.NavDataFieldType.DistanceToWaypoint, new NavStatusBoxFieldDisModelFactory(bus, lnavIndex));
            this.factories.set(exports.NavDataFieldType.TimeToWaypoint, new NavStatusBoxFieldEteModelFactory(bus, lnavIndex));
        }
        /**
         * Creates a navigation status box field data model for a given type of field.
         * @param type A navigation status box field type.
         * @returns A navigation status box field data model for the given field type.
         * @throws Error if an unsupported field type is specified.
         */
        create(type) {
            var _a;
            const model = (_a = this.factories.get(type)) === null || _a === void 0 ? void 0 : _a.create(this.gpsValidity);
            if (!model) {
                throw new Error(`NavStatusBoxFieldModelFactory: no single-type model factory of data field type [${type}] is registered`);
            }
            return model;
        }
    }
    /**
     * Creates data models for Bearing to Waypoint navigation status box fields.
     */
    class NavStatusBoxFieldBrgModelFactory {
        /**
         * Constructor.
         * @param bus The event bus.
         * @param lnavIndex The index of the LNAV from which to source data. Defaults to `0`.
         */
        constructor(bus, lnavIndex) {
            this.bus = bus;
            this.lnavIndex = lnavIndex;
        }
        /** @inheritDoc */
        create(gpsValidity) {
            return new NavStatusBoxFieldBrgModel(this.bus, gpsValidity, this.lnavIndex);
        }
    }
    /**
     * Creates data models for Distance to Waypoint navigation status box fields.
     */
    class NavStatusBoxFieldDisModelFactory {
        /**
         * Constructor.
         * @param bus The event bus.
         * @param lnavIndex The index of the LNAV from which to source data. Defaults to `0`.
         */
        constructor(bus, lnavIndex) {
            this.bus = bus;
            this.lnavIndex = lnavIndex;
        }
        /** @inheritDoc */
        create(gpsValidity) {
            return new NavStatusBoxFieldDisModel(this.bus, gpsValidity, this.lnavIndex);
        }
    }
    /**
     * Creates data models for Time To Waypoint navigation status box fields.
     */
    class NavStatusBoxFieldEteModelFactory {
        /**
         * Constructor.
         * @param bus The event bus.
         * @param lnavIndex The index of the LNAV from which to source data. Defaults to `0`.
         */
        constructor(bus, lnavIndex) {
            this.bus = bus;
            this.lnavIndex = lnavIndex;
        }
        /** @inheritDoc */
        create(gpsValidity) {
            return new NavStatusBoxFieldEteModel(this.bus, gpsValidity, this.lnavIndex);
        }
    }
    /**
     * A data model for Bearing to Waypoint navigation status box fields.
     */
    class NavStatusBoxFieldBrgModel {
        /**
         * Constructor.
         * @param bus The event bus.
         * @param gpsValidity The current validity state of the GPS data for this model.
         * @param lnavIndex The index of the LNAV from which to source data. Defaults to `0`.
         */
        constructor(bus, gpsValidity, lnavIndex) {
            this.value = msfsSdk.BasicNavAngleSubject.create(msfsSdk.BasicNavAngleUnit.create(true).createNumber(NaN));
            this.isLNavIndexValid = msfsSdk.Subject.create(false);
            const sub = bus.getSubscriber();
            this.lnavIsTracking = msfsSdk.ConsumerSubject.create(null, false);
            this.bearing = msfsSdk.ConsumerSubject.create(null, 0);
            this.magVar = msfsSdk.ConsumerSubject.create(null, 0);
            lnavIndex !== null && lnavIndex !== void 0 ? lnavIndex : (lnavIndex = 0);
            const resolveLNavConsumers = (index) => {
                if (msfsSdk.LNavUtils.isValidLNavIndex(index)) {
                    const suffix = msfsSdk.LNavUtils.getEventBusTopicSuffix(index);
                    this.isLNavIndexValid.set(true);
                    this.lnavIsTracking.setConsumer(sub.on(`lnav_is_tracking${suffix}`));
                    this.bearing.setConsumer(sub.on(`lnavdata_waypoint_bearing_mag${suffix}`));
                }
                else {
                    this.isLNavIndexValid.set(false);
                    this.lnavIsTracking.setConsumer(null);
                    this.bearing.setConsumer(null);
                }
            };
            if (msfsSdk.SubscribableUtils.isSubscribable(lnavIndex)) {
                this.lnavIndexSub = lnavIndex.sub(resolveLNavConsumers, true);
            }
            else {
                resolveLNavConsumers(lnavIndex);
            }
            this.magVar.setConsumer(sub.on('magvar'));
            this.state = msfsSdk.MappedSubject.create(gpsValidity, this.isLNavIndexValid, this.lnavIsTracking, this.bearing, this.magVar);
            this.state.sub(([gpsValidityState, isLNavIndexValid, isTracking, bearing, magVar]) => {
                this.value.set(gpsValidityState !== exports.NavDataFieldGpsValidity.Invalid && isLNavIndexValid && isTracking ? msfsSdk.MathUtils.round(bearing, 0.5) : NaN, msfsSdk.MathUtils.round(magVar, 0.5));
            }, true);
        }
        /** @inheritDoc */
        destroy() {
            var _a;
            (_a = this.lnavIndexSub) === null || _a === void 0 ? void 0 : _a.destroy();
            this.lnavIsTracking.destroy();
            this.bearing.destroy();
            this.magVar.destroy();
            this.state.destroy();
        }
    }
    /**
     * A data model for Distance to Waypoint navigation status box fields.
     */
    class NavStatusBoxFieldDisModel {
        /**
         * Constructor.
         * @param bus The event bus.
         * @param gpsValidity The current validity state of the GPS data for this model.
         * @param lnavIndex The index of the LNAV from which to source data. Defaults to `0`.
         */
        constructor(bus, gpsValidity, lnavIndex) {
            this.value = msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.NMILE.createNumber(NaN));
            this.isLNavIndexValid = msfsSdk.Subject.create(false);
            const sub = bus.getSubscriber();
            this.lnavIsTracking = msfsSdk.ConsumerSubject.create(null, false);
            this.distance = msfsSdk.ConsumerSubject.create(null, 0);
            lnavIndex !== null && lnavIndex !== void 0 ? lnavIndex : (lnavIndex = 0);
            const resolveLNavConsumers = (index) => {
                if (msfsSdk.LNavUtils.isValidLNavIndex(index)) {
                    const suffix = msfsSdk.LNavUtils.getEventBusTopicSuffix(index);
                    this.isLNavIndexValid.set(true);
                    this.lnavIsTracking.setConsumer(sub.on(`lnav_is_tracking${suffix}`));
                    this.distance.setConsumer(sub.on(`lnavdata_waypoint_distance${suffix}`));
                }
                else {
                    this.isLNavIndexValid.set(false);
                    this.lnavIsTracking.setConsumer(null);
                    this.distance.setConsumer(null);
                }
            };
            if (msfsSdk.SubscribableUtils.isSubscribable(lnavIndex)) {
                this.lnavIndexSub = lnavIndex.sub(resolveLNavConsumers, true);
            }
            else {
                resolveLNavConsumers(lnavIndex);
            }
            this.state = msfsSdk.MappedSubject.create(gpsValidity, this.isLNavIndexValid, this.lnavIsTracking, this.distance);
            this.state.sub(([gpsValidityState, isLNavIndexValid, isTracking, distance]) => {
                this.value.set(gpsValidityState !== exports.NavDataFieldGpsValidity.Invalid && isLNavIndexValid && isTracking
                    ? msfsSdk.MathUtils.round(distance, NavStatusBoxFieldDisModel.PRECISION)
                    : NaN);
            }, true);
        }
        /** @inheritDoc */
        destroy() {
            var _a;
            (_a = this.lnavIndexSub) === null || _a === void 0 ? void 0 : _a.destroy();
            this.lnavIsTracking.destroy();
            this.distance.destroy();
            this.state.destroy();
        }
    }
    NavStatusBoxFieldDisModel.PRECISION = msfsSdk.UnitType.KILOMETER.convertTo(0.1, msfsSdk.UnitType.NMILE);
    /**
     * A data model for Time to Waypoint navigation status box fields.
     */
    class NavStatusBoxFieldEteModel {
        /**
         * Constructor.
         * @param bus The event bus.
         * @param gpsValidity The current validity state of the GPS data for this model.
         * @param lnavIndex The index of the LNAV from which to source data. Defaults to `0`.
         */
        constructor(bus, gpsValidity, lnavIndex) {
            this.value = msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.HOUR.createNumber(NaN));
            this.isLNavIndexValid = msfsSdk.Subject.create(false);
            const sub = bus.getSubscriber();
            this.lnavIsTracking = msfsSdk.ConsumerSubject.create(null, false);
            this.distance = msfsSdk.ConsumerSubject.create(null, 0);
            this.groundSpeed = msfsSdk.ConsumerSubject.create(null, 0);
            lnavIndex !== null && lnavIndex !== void 0 ? lnavIndex : (lnavIndex = 0);
            const resolveLNavConsumers = (index) => {
                if (msfsSdk.LNavUtils.isValidLNavIndex(index)) {
                    const suffix = msfsSdk.LNavUtils.getEventBusTopicSuffix(index);
                    this.isLNavIndexValid.set(true);
                    this.lnavIsTracking.setConsumer(sub.on(`lnav_is_tracking${suffix}`));
                    this.distance.setConsumer(sub.on(`lnavdata_waypoint_distance${suffix}`));
                }
                else {
                    this.isLNavIndexValid.set(false);
                    this.lnavIsTracking.setConsumer(null);
                    this.distance.setConsumer(null);
                }
            };
            if (msfsSdk.SubscribableUtils.isSubscribable(lnavIndex)) {
                this.lnavIndexSub = lnavIndex.sub(resolveLNavConsumers, true);
            }
            else {
                resolveLNavConsumers(lnavIndex);
            }
            this.groundSpeed.setConsumer(sub.on('ground_speed'));
            this.state = msfsSdk.MappedSubject.create(gpsValidity, this.isLNavIndexValid, this.lnavIsTracking, this.distance, this.groundSpeed);
            this.state.sub(([gpsValidityState, isLNavIndexValid, isTracking, distance, gs]) => {
                let time = NaN;
                if (gpsValidityState !== exports.NavDataFieldGpsValidity.Invalid && isLNavIndexValid && isTracking && gs > 30) {
                    time = distance / gs;
                }
                this.value.set(msfsSdk.MathUtils.round(time, NavStatusBoxFieldEteModel.PRECISION));
            }, true);
        }
        /** @inheritDoc */
        destroy() {
            var _a;
            (_a = this.lnavIndexSub) === null || _a === void 0 ? void 0 : _a.destroy();
            this.lnavIsTracking.destroy();
            this.distance.destroy();
            this.groundSpeed.destroy();
            this.state.destroy();
        }
    }
    NavStatusBoxFieldEteModel.PRECISION = msfsSdk.UnitType.SECOND.convertTo(1, msfsSdk.UnitType.HOUR);

    /**
     * A navigation status box field renderer.
     */
    class NavStatusBoxFieldRenderer {
        /**
         * Constructor.
         * @param unitsSettingManager A display units user setting manager.
         */
        constructor(unitsSettingManager) {
            this.renderer = new GenericNavDataFieldRenderer();
            this.renderer.register(exports.NavDataFieldType.BearingToWaypoint, new NextGenNavDataFieldBrgRenderer(unitsSettingManager));
            this.renderer.register(exports.NavDataFieldType.DistanceToWaypoint, new NextGenNavDataFieldDisRenderer(unitsSettingManager));
            this.renderer.register(exports.NavDataFieldType.TimeToWaypoint, new NextGenNavDataFieldEteRenderer(unitsSettingManager));
        }
        /** @inheritdoc */
        render(type, model) {
            return this.renderer.render(type, model);
        }
    }

    /**
     * Setting modes for COM radio channel spacing.
     */
    exports.ComRadioSpacingSettingMode = void 0;
    (function (ComRadioSpacingSettingMode) {
        ComRadioSpacingSettingMode["Spacing25Khz"] = "25Khz";
        ComRadioSpacingSettingMode["Spacing8_33Khz"] = "8.33Khz";
    })(exports.ComRadioSpacingSettingMode || (exports.ComRadioSpacingSettingMode = {}));
    /**
     * Utility class for retrieving COM radio user setting managers.
     */
    class ComRadioUserSettings {
        /**
         * Retrieves a manager for COM radio user settings.
         * @param bus The event bus.
         * @returns A manager for COM radio user settings.
         */
        static getManager(bus) {
            var _a;
            return (_a = ComRadioUserSettings.INSTANCE) !== null && _a !== void 0 ? _a : (ComRadioUserSettings.INSTANCE = new msfsSdk.DefaultUserSettingManager(bus, [
                {
                    name: 'comRadioSpacing',
                    defaultValue: exports.ComRadioSpacingSettingMode.Spacing25Khz
                }
            ]));
        }
    }

    /**
     * Setting modes for DME tuning.
     */
    exports.DmeTuneSettingMode = void 0;
    (function (DmeTuneSettingMode) {
        DmeTuneSettingMode["Nav1"] = "Nav1";
        DmeTuneSettingMode["Nav2"] = "Nav2";
        DmeTuneSettingMode["Hold"] = "Hold";
    })(exports.DmeTuneSettingMode || (exports.DmeTuneSettingMode = {}));
    /**
     * Utility class for retrieving DME user setting managers.
     */
    class DmeUserSettings {
        /**
         * Retrieves a manager for DME user settings.
         * @param bus The event bus.
         * @returns A manager for DME user settings.
         */
        static getManager(bus) {
            var _a;
            return (_a = DmeUserSettings.INSTANCE) !== null && _a !== void 0 ? _a : (DmeUserSettings.INSTANCE = new msfsSdk.DefaultUserSettingManager(bus, [
                {
                    name: 'dme1TuneMode',
                    defaultValue: exports.DmeTuneSettingMode.Nav1
                },
                {
                    name: 'dme2TuneMode',
                    defaultValue: exports.DmeTuneSettingMode.Nav2
                }
            ]));
        }
    }

    /**
     * Utility class for retrieving navigation data bar user setting managers.
     */
    class NavDataBarUserSettings {
        /**
         * Creates a manager for navigation data bar user settings.
         * @param bus The event bus.
         * @param defaultValues The default values for the settings. One indexed setting will be created for each default
         * value, in order.
         * @returns A new manager for navigation data bar user settings.
         */
        static createManager(bus, defaultValues) {
            return new msfsSdk.DefaultUserSettingManager(bus, defaultValues.map((defaultValue, index) => {
                return { name: `navDataBarField${index}`, defaultValue };
            }));
        }
    }

    /**
     * Utility class for retrieving nearest airport user setting managers.
     */
    class NearestAirportUserSettings {
        /**
         * Retrieves a manager for airport search settings.
         * @param bus The event bus.
         * @returns a manager for airport search settings.
         */
        static getManager(bus) {
            var _a;
            return (_a = NearestAirportUserSettings.INSTANCE) !== null && _a !== void 0 ? _a : (NearestAirportUserSettings.INSTANCE = new msfsSdk.DefaultUserSettingManager(bus, [
                {
                    name: 'nearestAptRunwayLength',
                    defaultValue: 3000
                },
                {
                    name: 'nearestAptRunwaySurfaceTypes',
                    defaultValue: msfsSdk.RunwaySurfaceCategory.Hard
                }
            ]));
        }
    }

    /**
     * A component that alerts when the waypoint is about to change.
     */
    class NavStatusBoxDtkAlert extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.rootStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.rootCssClass = msfsSdk.SetSubject.create(['nav-status-alert']);
            this.beforeCourseText = msfsSdk.Subject.create('');
            this.afterCourseText = msfsSdk.Subject.create('');
            this.course = msfsSdk.BasicNavAngleSubject.create(msfsSdk.BasicNavAngleUnit.create(false).createNumber(NaN));
            this.courseUnit = msfsSdk.Subject.create(NavStatusBoxDtkAlert.MAG_COURSE);
            this.seconds = msfsSdk.Subject.create(0, msfsSdk.SubscribableUtils.NUMERIC_NAN_EQUALITY);
            this.isPaused = true;
        }
        /** @inheritdoc */
        onAfterRender() {
            this.courseSub = this.props.useMagnetic.sub(this.onUseMagneticChanged.bind(this), false, true);
            this.timePipe = this.props.dataProvider.waypointAlertTime.pipe(this.seconds, time => Math.floor(time.asUnit(msfsSdk.UnitType.SECOND)), true);
            this.secondsPipe = this.seconds.pipe(this.afterCourseText, seconds => ` in ${isNaN(seconds) ? '_' : seconds} ${seconds === 1 ? 'second' : 'seconds'}`, true);
            this.alertStateSub = this.props.dataProvider.waypointAlertState.sub(this.onAlertStateChanged.bind(this), !this.isPaused, this.isPaused);
        }
        /**
         * Resumes this component. Once resumed, this component will automatically update.
         */
        resume() {
            var _a;
            if (!this.isPaused) {
                return;
            }
            (_a = this.alertStateSub) === null || _a === void 0 ? void 0 : _a.resume(true);
        }
        /**
         * Pauses this component. This component will not update while it is paused.
         */
        pause() {
            var _a, _b, _c, _d;
            if (this.isPaused) {
                return;
            }
            (_a = this.alertStateSub) === null || _a === void 0 ? void 0 : _a.pause();
            (_b = this.courseSub) === null || _b === void 0 ? void 0 : _b.pause();
            (_c = this.timePipe) === null || _c === void 0 ? void 0 : _c.pause();
            (_d = this.secondsPipe) === null || _d === void 0 ? void 0 : _d.pause();
        }
        /**
         * Responds to when the waypoint alert state changes.
         * @param stateEvent The alert state change event.
         */
        onAlertStateChanged(stateEvent) {
            var _a, _b, _c, _d, _e, _f, _g;
            const state = stateEvent.newState;
            (_a = this.courseSub) === null || _a === void 0 ? void 0 : _a.pause();
            (_b = this.timePipe) === null || _b === void 0 ? void 0 : _b.pause();
            (_c = this.secondsPipe) === null || _c === void 0 ? void 0 : _c.pause();
            if (NavStatusBoxDtkAlert.SHOW_COURSE_STATES.includes(state)) {
                (_d = this.courseSub) === null || _d === void 0 ? void 0 : _d.resume(true);
            }
            else {
                this.course.set(NaN);
            }
            if (NavStatusBoxDtkAlert.SHOW_TIME_STATES.includes(state)) {
                (_e = this.timePipe) === null || _e === void 0 ? void 0 : _e.resume(true);
                (_f = this.secondsPipe) === null || _f === void 0 ? void 0 : _f.resume(true);
            }
            else if (NavStatusBoxDtkAlert.SHOW_NOW_STATES.includes(state)) {
                this.afterCourseText.set(' now');
            }
            else {
                this.afterCourseText.set('');
            }
            switch (state) {
                case exports.WaypointAlertingState.CourseInSeconds:
                case exports.WaypointAlertingState.CourseNow:
                    this.beforeCourseText.set('Next DTK ');
                    break;
                case exports.WaypointAlertingState.LeftTurnInSeconds:
                case exports.WaypointAlertingState.LeftTurnNow:
                    this.beforeCourseText.set('Turn left to ');
                    break;
                case exports.WaypointAlertingState.RightTurnInSeconds:
                case exports.WaypointAlertingState.RightTurnNow:
                    this.beforeCourseText.set('Turn right to ');
                    break;
                case exports.WaypointAlertingState.ArrivingAtWaypoint:
                    this.beforeCourseText.set('Arriving at waypoint');
                    break;
                case exports.WaypointAlertingState.HoldDirect:
                    this.beforeCourseText.set('Hold direct');
                    break;
                case exports.WaypointAlertingState.HoldTeardrop:
                    this.beforeCourseText.set('Hold teardrop');
                    break;
                case exports.WaypointAlertingState.HoldParallel:
                    this.beforeCourseText.set('Hold parallel');
                    break;
            }
            const isActive = state !== exports.WaypointAlertingState.None;
            this.rootStyle.set('display', isActive ? '' : 'none');
            this.rootCssClass.toggle('nav-status-alert-flash', isActive);
            (_g = this.props.isActive) === null || _g === void 0 ? void 0 : _g.set(isActive);
        }
        /**
         * Responds to when whether to display magnetic course changes.
         * @param useMagnetic Whether to display magnetic course.
         */
        onUseMagneticChanged(useMagnetic) {
            var _a, _b;
            if (useMagnetic) {
                this.course.set((_a = this.props.dataProvider.waypointAlertState.get().courseMag) !== null && _a !== void 0 ? _a : NaN);
                this.courseUnit.set(NavStatusBoxDtkAlert.MAG_COURSE);
            }
            else {
                this.course.set((_b = this.props.dataProvider.waypointAlertState.get().course) !== null && _b !== void 0 ? _b : NaN);
                this.courseUnit.set(NavStatusBoxDtkAlert.TRUE_COURSE);
            }
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: this.rootCssClass, style: this.rootStyle },
                msfsSdk.FSComponent.buildComponent("div", { class: 'nav-status-alert-text' },
                    msfsSdk.FSComponent.buildComponent("span", null, this.beforeCourseText),
                    msfsSdk.FSComponent.buildComponent(BearingDisplay, { value: this.course, displayUnit: this.courseUnit, formatter: NavStatusBoxDtkAlert.COURSE_FORMATTER, hideDegreeSymbolWhenNan: true, class: 'nav-status-alert-course' }),
                    msfsSdk.FSComponent.buildComponent("span", null, this.afterCourseText))));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c;
            (_a = this.alertStateSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.courseSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.timePipe) === null || _c === void 0 ? void 0 : _c.destroy();
            super.destroy();
        }
    }
    NavStatusBoxDtkAlert.TRUE_COURSE = msfsSdk.BasicNavAngleUnit.create(false);
    NavStatusBoxDtkAlert.MAG_COURSE = msfsSdk.BasicNavAngleUnit.create(true);
    NavStatusBoxDtkAlert.COURSE_FORMATTER = msfsSdk.NumberFormatter.create({ precision: 1, pad: 3, nanString: '' });
    NavStatusBoxDtkAlert.SHOW_COURSE_STATES = [
        exports.WaypointAlertingState.CourseInSeconds,
        exports.WaypointAlertingState.CourseNow,
        exports.WaypointAlertingState.LeftTurnInSeconds,
        exports.WaypointAlertingState.LeftTurnNow,
        exports.WaypointAlertingState.RightTurnInSeconds,
        exports.WaypointAlertingState.RightTurnNow
    ];
    NavStatusBoxDtkAlert.SHOW_TIME_STATES = [
        exports.WaypointAlertingState.CourseInSeconds,
        exports.WaypointAlertingState.LeftTurnInSeconds,
        exports.WaypointAlertingState.RightTurnInSeconds
    ];
    NavStatusBoxDtkAlert.SHOW_NOW_STATES = [
        exports.WaypointAlertingState.CourseNow,
        exports.WaypointAlertingState.LeftTurnNow,
        exports.WaypointAlertingState.RightTurnNow
    ];

    /**
     * A next-generation (NXi, G3000, etc) navigation status box leg arrow display.
     */
    class NavStatusBoxLegArrow extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.rootStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.straightStyle = msfsSdk.ObjectSubject.create({
                display: 'none',
                width: '25px',
                height: '15px'
            });
            this.leftStyle = msfsSdk.ObjectSubject.create({
                display: 'none',
                width: '25px',
                height: '13px'
            });
            this.rightStyle = msfsSdk.ObjectSubject.create({
                display: 'none',
                width: '25px',
                height: '13px'
            });
            this.holdLeftStyle = msfsSdk.ObjectSubject.create({
                display: 'none',
                width: '33px',
                height: '15px'
            });
            this.holdRightStyle = msfsSdk.ObjectSubject.create({
                display: 'none',
                width: '33px',
                height: '15px'
            });
            this.directStyle = msfsSdk.ObjectSubject.create({
                display: 'none',
                width: '25px',
                height: '15px'
            });
            this.arrowMode = msfsSdk.Subject.create('straight');
        }
        /** @inheritdoc */
        onAfterRender() {
            this.legSub = this.props.toLeg.sub(leg => {
                if (leg === null) {
                    this.arrowMode.set('none');
                }
                else {
                    if (msfsSdk.BitFlags.isAny(leg.flags, msfsSdk.LegDefinitionFlags.DirectTo)) {
                        this.arrowMode.set('direct');
                    }
                    else {
                        switch (leg.leg.type) {
                            case msfsSdk.LegType.HF:
                            case msfsSdk.LegType.HA:
                            case msfsSdk.LegType.HM:
                                this.arrowMode.set(leg.leg.turnDirection === msfsSdk.LegTurnDirection.Left ? 'hold-left' : 'hold-right');
                                break;
                            case msfsSdk.LegType.AF:
                            case msfsSdk.LegType.RF:
                                this.arrowMode.set(leg.leg.turnDirection === msfsSdk.LegTurnDirection.Left ? 'left' : 'right');
                                break;
                            default:
                                this.arrowMode.set('straight');
                        }
                    }
                }
            }, true);
            this.arrowMode.sub(mode => {
                if (mode === 'none') {
                    this.rootStyle.set('display', 'none');
                }
                else {
                    this.rootStyle.set('display', '');
                    this.straightStyle.set('display', mode === 'straight' ? '' : 'none');
                    this.leftStyle.set('display', mode === 'left' ? '' : 'none');
                    this.rightStyle.set('display', mode === 'right' ? '' : 'none');
                    this.holdLeftStyle.set('display', mode === 'hold-left' ? '' : 'none');
                    this.holdRightStyle.set('display', mode === 'hold-right' ? '' : 'none');
                    this.directStyle.set('display', mode === 'direct' ? '' : 'none');
                }
            });
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: 'nav-status-leg-arrow', style: this.rootStyle },
                msfsSdk.FSComponent.buildComponent("svg", { viewBox: '0 0 25 14.5', style: this.straightStyle },
                    msfsSdk.FSComponent.buildComponent("path", { d: 'M 25 7.25 L 18 0 L 18 4.75 L 0 4.75 L 0 9.75 L 18 9.75 L 18 14.5 Z', fill: 'var(--nav-status-icon-color)', stroke: 'none' })),
                msfsSdk.FSComponent.buildComponent("svg", { viewBox: '0 0 25 13', style: this.leftStyle },
                    msfsSdk.FSComponent.buildComponent("path", { d: 'm 0 6.6 c 0 4.4 1.5 6.4 5.1 6.4 l 11.8 0 c 3.7 0 5.1 -2 5.1 -6.4 l 2.9 0 l -5.5 -6.6 l -5.5 6.6 l 2.9 0 c 0 1.5 -0.7 2.9 -2.9 2.9 l -5.9 0 c -2.2 0 -2.9 -1.5 -2.9 -2.9 l -5.1 0', fill: 'var(--nav-status-icon-color)', stroke: 'none' })),
                msfsSdk.FSComponent.buildComponent("svg", { viewBox: '0 0 25 13', style: this.rightStyle },
                    msfsSdk.FSComponent.buildComponent("path", { d: 'm 0 6.4 c 0 -4.4 1.5 -6.4 5.1 -6.4 l 11.8 0 c 3.7 0 5.1 2 5.1 6.4 l 2.9 0 l -5.5 6.6 l -5.5 -6.6 l 2.9 0 c 0 -1.5 -0.7 -2.9 -2.9 -2.9 l -5.9 0 c -2.2 0 -2.9 1.5 -2.9 2.9 l -5.1 0', fill: 'var(--nav-status-icon-color)', stroke: 'none' })),
                msfsSdk.FSComponent.buildComponent("svg", { viewBox: '0 0 33 15', style: this.holdLeftStyle },
                    msfsSdk.FSComponent.buildComponent("path", { d: 'm 0 13 l 22 0 m 0 -2 l 3 0 m 0 2 l 8 0 m -8 -1 l 1 0 c 7 0 7 -11 0 -11 l -19 0 c -7 0 -7 11 0 11 l 15 0 m 0 2 l 3 0', fill: 'none', stroke: 'var(--nav-status-icon-color)', "stroke-width": '2' })),
                msfsSdk.FSComponent.buildComponent("svg", { viewBox: '0 0 33 15', style: this.holdRightStyle },
                    msfsSdk.FSComponent.buildComponent("path", { d: 'm 0 2 l 22 0 m 0 2 l 3 0 m 0 -2 l 8 0 m -8 1 l 1 0 c 7 0 7 11 0 11 l -19 0 c -7 0 -7 -11 0 -11 l 15 0 m 0 -2 l 3 0', fill: 'none', stroke: 'var(--nav-status-icon-color)', "stroke-width": '2' })),
                msfsSdk.FSComponent.buildComponent("svg", { viewBox: '0 0 25 16.5', style: this.directStyle },
                    msfsSdk.FSComponent.buildComponent("path", { d: 'M 25 8.25 l -7 -7.25 l 0 4.75 l -18 0 l 0 5 l 18 0 l 0 4.75 z', fill: 'var(--nav-status-icon-color)', stroke: 'none' }),
                    msfsSdk.FSComponent.buildComponent("path", { d: 'M 4 1 l 4 0 a 6.25 7.25 0 0 1 0 14.5 l -4 0 z', fill: 'none', stroke: 'var(--nav-status-icon-color)', "stroke-width": '2' }))));
        }
        /** @inheritdoc */
        destroy() {
            var _a;
            (_a = this.legSub) === null || _a === void 0 ? void 0 : _a.destroy();
            super.destroy();
        }
    }

    /**
     * A next-generation (NXi, G3000, etc) navigation status box flight plan leg display.
     */
    class NavStatusBoxLegDisplay extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.legArrowRef = msfsSdk.FSComponent.createRef();
            this.fromLegDistanceRef = msfsSdk.FSComponent.createRef();
            this.alertRef = msfsSdk.FSComponent.createRef();
            this.baseStyle = msfsSdk.ObjectSubject.create({
                display: ''
            });
            this.fromLegStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.fromLegTextStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.fromLegAltitudeStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.fromLegDistanceStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.fromLegProcLeftStyle = msfsSdk.ObjectSubject.create({
                display: 'none',
                width: '30px',
                height: '12px'
            });
            this.fromLegProcRightStyle = msfsSdk.ObjectSubject.create({
                display: 'none',
                width: '30px',
                height: '12px'
            });
            this.toLegStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.toLegTextStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.toLegAltitudeStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.toLegProcLeftStyle = msfsSdk.ObjectSubject.create({
                display: 'none',
                width: '30px',
                height: '12px'
            });
            this.toLegProcRightStyle = msfsSdk.ObjectSubject.create({
                display: 'none',
                width: '30px',
                height: '12px'
            });
            this.toLeg = this.props.dataProvider.trackedLegs.map(legs => legs.toLeg);
            this.fromLegState = msfsSdk.MappedSubject.create(this.props.dataProvider.trackedLegs, this.props.dataProvider.obsCourse);
            this.fromLegText = msfsSdk.Subject.create('');
            this.toLegText = msfsSdk.Subject.create('');
            this.fromLegAltitude = msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.FOOT.createNumber(0));
            this.toLegAltitude = msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.FOOT.createNumber(0));
            this.fromLegDistance = msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.NMILE.createNumber(0));
            this.useMagnetic = this.props.unitsSettingManager.getSetting('unitsNavAngle').map(mode => mode !== exports.UnitsNavAngleSettingMode.True);
            this.isAlertActive = msfsSdk.Subject.create(false);
            this.isPaused = true;
        }
        /** @inheritdoc */
        onAfterRender() {
            this.toLeg.sub(toLeg => {
                this.updateToLeg(toLeg);
            }, true);
            this.fromLegState.sub(([trackedLegs, obsCourse]) => {
                this.updateFromLeg(trackedLegs, obsCourse);
            }, true);
            this.isAlertActive.sub(isActive => {
                this.baseStyle.set('display', isActive ? 'none' : '');
            }, true);
            this.useMagnetic.sub(() => {
                this.updateFromLeg(this.props.dataProvider.trackedLegs.get(), this.props.dataProvider.obsCourse.get());
                this.updateToLeg(this.toLeg.get());
            });
            if (!this.isPaused) {
                this.alertRef.instance.resume();
            }
        }
        /**
         * Resumes this display. Once resumed, this display will automatically update.
         */
        resume() {
            var _a;
            if (!this.isPaused) {
                return;
            }
            this.toLeg.resume();
            this.fromLegState.resume();
            this.useMagnetic.resume();
            (_a = this.alertRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.resume();
        }
        /**
         * Pauses this display. This display will not update while it is paused.
         */
        pause() {
            var _a;
            if (this.isPaused) {
                return;
            }
            this.toLeg.pause();
            this.fromLegState.pause();
            this.useMagnetic.pause();
            (_a = this.alertRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.pause();
        }
        /**
         * Updates the to leg display.
         * @param toLeg The current to leg.
         */
        updateToLeg(toLeg) {
            var _a;
            if (toLeg === null) {
                this.toLegStyle.set('display', 'none');
            }
            else {
                this.toLegStyle.set('display', '');
                if (toLeg.leg.type === msfsSdk.LegType.PI) {
                    this.toLegTextStyle.set('display', 'none');
                    this.toLegAltitudeStyle.set('display', 'none');
                    if (toLeg.leg.turnDirection === msfsSdk.LegTurnDirection.Left) {
                        this.toLegProcRightStyle.set('display', 'none');
                        this.toLegProcLeftStyle.set('display', '');
                    }
                    else {
                        this.toLegProcLeftStyle.set('display', 'none');
                        this.toLegProcRightStyle.set('display', '');
                    }
                }
                else if (toLeg.leg.type === msfsSdk.LegType.CA || toLeg.leg.type === msfsSdk.LegType.VA) {
                    this.toLegProcLeftStyle.set('display', 'none');
                    this.toLegProcRightStyle.set('display', 'none');
                    this.toLegTextStyle.set('display', 'none');
                    this.toLegAltitudeStyle.set('display', '');
                    this.toLegAltitude.set(toLeg.leg.altitude1, msfsSdk.UnitType.METER);
                }
                else {
                    this.toLegProcLeftStyle.set('display', 'none');
                    this.toLegProcRightStyle.set('display', 'none');
                    this.toLegAltitudeStyle.set('display', 'none');
                    this.toLegTextStyle.set('display', '');
                    let name;
                    switch (toLeg.leg.type) {
                        case msfsSdk.LegType.CI:
                        case msfsSdk.LegType.VI:
                            name = 'intrcpt';
                            break;
                        case msfsSdk.LegType.FM:
                        case msfsSdk.LegType.VM:
                            name = 'man seq';
                            break;
                        default:
                            name = `${(_a = toLeg.name) !== null && _a !== void 0 ? _a : ''}${this.getFixTypeSuffix(toLeg)}`;
                    }
                    this.toLegText.set(name);
                }
            }
        }
        /**
         * Updates the from leg display.
         * @param trackedLegs The legs currently tracked by LNAV.
         * @param obsCourse The active OBS course, or `null` if OBS is inactive.
         */
        updateFromLeg(trackedLegs, obsCourse) {
            var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
            if (trackedLegs.toLeg === null) {
                this.fromLegStyle.set('display', 'none');
            }
            else {
                if (obsCourse === null) {
                    if (msfsSdk.BitFlags.isAny(trackedLegs.toLeg.flags, msfsSdk.LegDefinitionFlags.VectorsToFinalFaf)) {
                        this.fromLegStyle.set('display', '');
                        let course = trackedLegs.toLeg.leg.course;
                        const useMagnetic = this.useMagnetic.get();
                        if (!useMagnetic && ((_a = trackedLegs.toLeg.calculated) === null || _a === void 0 ? void 0 : _a.endLat) !== undefined && ((_b = trackedLegs.toLeg.calculated) === null || _b === void 0 ? void 0 : _b.endLon) !== undefined) {
                            course = msfsSdk.MagVar.magneticToTrue(course, trackedLegs.toLeg.calculated.endLat, trackedLegs.toLeg.calculated.endLon);
                        }
                        this.setFromLegText(`vtf ${NavStatusBoxLegDisplay.HEADING_FORMATTER(course, useMagnetic)}`);
                    }
                    else {
                        switch (trackedLegs.toLeg.leg.type) {
                            case msfsSdk.LegType.CF: {
                                const fromLegType = (_c = trackedLegs.fromLeg) === null || _c === void 0 ? void 0 : _c.leg.type;
                                if (fromLegType !== undefined
                                    && !msfsSdk.FlightPlanUtils.isDiscontinuityLeg(fromLegType)
                                    && !msfsSdk.FlightPlanUtils.isManualDiscontinuityLeg(fromLegType)) {
                                    break;
                                }
                            }
                            // eslint-disable-next-line no-fallthrough
                            case msfsSdk.LegType.CD:
                            case msfsSdk.LegType.CR:
                            case msfsSdk.LegType.CI:
                            case msfsSdk.LegType.CA: {
                                this.fromLegStyle.set('display', '');
                                let course = trackedLegs.toLeg.leg.course;
                                const isCourseMagnetic = !trackedLegs.toLeg.leg.trueDegrees;
                                const useMagnetic = this.useMagnetic.get();
                                if (isCourseMagnetic !== useMagnetic && ((_d = trackedLegs.toLeg.calculated) === null || _d === void 0 ? void 0 : _d.endLat) !== undefined && ((_e = trackedLegs.toLeg.calculated) === null || _e === void 0 ? void 0 : _e.endLon) !== undefined) {
                                    course = useMagnetic
                                        ? msfsSdk.MagVar.trueToMagnetic(course, trackedLegs.toLeg.calculated.endLat, trackedLegs.toLeg.calculated.endLon)
                                        : msfsSdk.MagVar.magneticToTrue(course, trackedLegs.toLeg.calculated.endLat, trackedLegs.toLeg.calculated.endLon);
                                }
                                this.setFromLegText(`crs ${NavStatusBoxLegDisplay.HEADING_FORMATTER(course, useMagnetic)}`);
                                return;
                            }
                            case msfsSdk.LegType.VR:
                            case msfsSdk.LegType.VD:
                            case msfsSdk.LegType.VI:
                            case msfsSdk.LegType.VA:
                            case msfsSdk.LegType.VM: {
                                this.fromLegStyle.set('display', '');
                                let course = trackedLegs.toLeg.leg.course;
                                const isCourseMagnetic = !trackedLegs.toLeg.leg.trueDegrees;
                                const useMagnetic = this.useMagnetic.get();
                                if (isCourseMagnetic !== useMagnetic && ((_f = trackedLegs.toLeg.calculated) === null || _f === void 0 ? void 0 : _f.endLat) !== undefined && ((_g = trackedLegs.toLeg.calculated) === null || _g === void 0 ? void 0 : _g.endLon) !== undefined) {
                                    course = useMagnetic
                                        ? msfsSdk.MagVar.trueToMagnetic(course, trackedLegs.toLeg.calculated.endLat, trackedLegs.toLeg.calculated.endLon)
                                        : msfsSdk.MagVar.magneticToTrue(course, trackedLegs.toLeg.calculated.endLat, trackedLegs.toLeg.calculated.endLon);
                                }
                                this.setFromLegText(`hdg ${NavStatusBoxLegDisplay.HEADING_FORMATTER(course, useMagnetic)}`);
                                return;
                            }
                            case msfsSdk.LegType.HF:
                            case msfsSdk.LegType.HA:
                            case msfsSdk.LegType.HM:
                                this.fromLegStyle.set('display', '');
                                if (trackedLegs.toLeg.leg.distanceMinutes) {
                                    const seconds = Math.round(trackedLegs.toLeg.leg.distance * 60);
                                    const minutesPart = Math.floor(seconds / 60);
                                    const secondsPart = seconds - minutesPart * 60;
                                    this.setFromLegText(`${minutesPart.toString().padStart(2, '0')}:${secondsPart.toString().padStart(2, '0')}`);
                                }
                                else {
                                    this.fromLegTextStyle.set('display', 'none');
                                    this.fromLegProcLeftStyle.set('display', 'none');
                                    this.fromLegProcRightStyle.set('display', 'none');
                                    this.fromLegAltitudeStyle.set('display', 'none');
                                    this.fromLegDistanceStyle.set('display', '');
                                    this.fromLegDistance.set(trackedLegs.toLeg.leg.distance, msfsSdk.UnitType.METER);
                                }
                                return;
                        }
                        if (trackedLegs.fromLeg === null) {
                            this.fromLegStyle.set('display', 'none');
                        }
                        else {
                            this.fromLegStyle.set('display', '');
                            switch (trackedLegs.fromLeg.leg.type) {
                                case msfsSdk.LegType.PI:
                                    this.fromLegTextStyle.set('display', 'none');
                                    this.fromLegAltitudeStyle.set('display', 'none');
                                    this.fromLegDistanceStyle.set('display', 'none');
                                    if (trackedLegs.fromLeg.leg.turnDirection === msfsSdk.LegTurnDirection.Left) {
                                        this.fromLegProcRightStyle.set('display', 'none');
                                        this.fromLegProcLeftStyle.set('display', '');
                                    }
                                    else {
                                        this.fromLegProcLeftStyle.set('display', 'none');
                                        this.fromLegProcRightStyle.set('display', '');
                                    }
                                    break;
                                case msfsSdk.LegType.CA:
                                case msfsSdk.LegType.VA:
                                    this.fromLegTextStyle.set('display', 'none');
                                    this.fromLegProcLeftStyle.set('display', 'none');
                                    this.fromLegProcRightStyle.set('display', 'none');
                                    this.fromLegDistanceStyle.set('display', 'none');
                                    this.fromLegAltitudeStyle.set('display', '');
                                    this.fromLegAltitude.set(trackedLegs.fromLeg.leg.altitude1, msfsSdk.UnitType.METER);
                                    break;
                                default:
                                    this.setFromLegText(`${(_h = trackedLegs.fromLeg.name) !== null && _h !== void 0 ? _h : ''}${this.getFixTypeSuffix(trackedLegs.fromLeg)}`);
                            }
                        }
                    }
                }
                else {
                    this.fromLegStyle.set('display', '');
                    let course = obsCourse;
                    const useMagnetic = this.useMagnetic.get();
                    if (!useMagnetic && ((_j = trackedLegs.toLeg.calculated) === null || _j === void 0 ? void 0 : _j.endLat) !== undefined && ((_k = trackedLegs.toLeg.calculated) === null || _k === void 0 ? void 0 : _k.endLon) !== undefined) {
                        course = msfsSdk.MagVar.magneticToTrue(course, trackedLegs.toLeg.calculated.endLat, trackedLegs.toLeg.calculated.endLon);
                    }
                    this.setFromLegText(`obs ${NavStatusBoxLegDisplay.HEADING_FORMATTER(course, useMagnetic)}`);
                }
            }
        }
        /**
         * Sets and displays the text of the from leg display.
         * @param text The text to set.
         */
        setFromLegText(text) {
            this.fromLegProcLeftStyle.set('display', 'none');
            this.fromLegProcRightStyle.set('display', 'none');
            this.fromLegAltitudeStyle.set('display', 'none');
            this.fromLegDistanceStyle.set('display', 'none');
            this.fromLegTextStyle.set('display', '');
            this.fromLegText.set(text);
        }
        /**
         * Gets the fix type suffix for a flight plan leg.
         * @param leg A flight plan leg.
         * @returns The fix type suffix for the specified flight plan leg.
         */
        getFixTypeSuffix(leg) {
            switch (leg.leg.fixTypeFlags) {
                case msfsSdk.FixTypeFlags.FAF:
                    return ' faf';
                case msfsSdk.FixTypeFlags.IAF:
                    return ' iaf';
                case msfsSdk.FixTypeFlags.MAP:
                    return ' map';
                case msfsSdk.FixTypeFlags.MAHP:
                    return ' mahp';
                default:
                    return '';
            }
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: 'nav-status-leg' },
                msfsSdk.FSComponent.buildComponent("div", { class: 'nav-status-leg-base', style: this.baseStyle },
                    msfsSdk.FSComponent.buildComponent("div", { class: 'nav-status-leg-from', style: this.fromLegStyle },
                        msfsSdk.FSComponent.buildComponent("div", { class: 'nav-status-leg-text', style: this.fromLegTextStyle }, this.fromLegText),
                        msfsSdk.FSComponent.buildComponent("div", { class: 'nav-status-leg-altitude', style: this.fromLegAltitudeStyle },
                            msfsSdk.FSComponent.buildComponent(NumberUnitDisplay, { value: this.fromLegAltitude, displayUnit: null, formatter: NavStatusBoxLegDisplay.ALTITUDE_FORMATTER })),
                        msfsSdk.FSComponent.buildComponent("div", { class: 'nav-status-leg-distance', style: this.fromLegDistanceStyle },
                            msfsSdk.FSComponent.buildComponent(NumberUnitDisplay, { value: this.fromLegDistance, displayUnit: this.props.unitsSettingManager.distanceUnitsLarge, formatter: NavStatusBoxLegDisplay.DISTANCE_FORMATTER })),
                        msfsSdk.FSComponent.buildComponent("svg", { viewBox: '0 0 30 12', style: this.fromLegProcLeftStyle },
                            msfsSdk.FSComponent.buildComponent("path", { d: 'M 0 2 l 3 0 m 0 -1 l 2 0 m 0 1 l 25 0 M 0 3 l 3 0 m 0 1 l 2 0 m 0 -1 l 10 0 l 7 7 a 1.41 1.41 0 0 0 4 -4 l -3 -3 l -8 0', fill: 'none', stroke: 'var(--nav-status-icon-color)', "stroke-width": '2' })),
                        msfsSdk.FSComponent.buildComponent("svg", { viewBox: '0 0 30 12', style: this.fromLegProcRightStyle },
                            msfsSdk.FSComponent.buildComponent("path", { d: 'M 0 10 l 3 0 m 0 1 l 2 0 m 0 -1 l 25 0 M 0 9 l 3 0 m 0 -1 l 2 0 m 0 1 l 10 0 l 7 -7 a 1.41 1.41 90 0 1 4 4 l -3 3 l -8 0', fill: 'none', stroke: 'var(--nav-status-icon-color)', "stroke-width": '2' }))),
                    msfsSdk.FSComponent.buildComponent(NavStatusBoxLegArrow, { ref: this.legArrowRef, toLeg: this.toLeg }),
                    msfsSdk.FSComponent.buildComponent("div", { class: 'nav-status-leg-to', style: this.toLegStyle },
                        msfsSdk.FSComponent.buildComponent("div", { class: 'nav-status-leg-text', style: this.toLegTextStyle }, this.toLegText),
                        msfsSdk.FSComponent.buildComponent("div", { class: 'nav-status-leg-altitude', style: this.toLegAltitudeStyle },
                            msfsSdk.FSComponent.buildComponent(NumberUnitDisplay, { value: this.toLegAltitude, displayUnit: null, formatter: NavStatusBoxLegDisplay.ALTITUDE_FORMATTER })),
                        msfsSdk.FSComponent.buildComponent("svg", { viewBox: '0 0 30 12', style: this.toLegProcLeftStyle },
                            msfsSdk.FSComponent.buildComponent("path", { d: 'M 0 2 l 3 0 m 0 -1 l 2 0 m 0 1 l 25 0 M 0 3 l 3 0 m 0 1 l 2 0 m 0 -1 l 10 0 l 7 7 a 1.41 1.41 0 0 0 4 -4 l -3 -3 l -8 0', fill: 'none', stroke: 'var(--nav-status-icon-color)', "stroke-width": '2' })),
                        msfsSdk.FSComponent.buildComponent("svg", { viewBox: '0 0 30 12', style: this.toLegProcRightStyle },
                            msfsSdk.FSComponent.buildComponent("path", { d: 'M 0 10 l 3 0 m 0 1 l 2 0 m 0 -1 l 25 0 M 0 9 l 3 0 m 0 -1 l 2 0 m 0 1 l 10 0 l 7 -7 a 1.41 1.41 90 0 1 4 4 l -3 3 l -8 0', fill: 'none', stroke: 'var(--nav-status-icon-color)', "stroke-width": '2' })))),
                msfsSdk.FSComponent.buildComponent(NavStatusBoxDtkAlert, { ref: this.alertRef, dataProvider: this.props.dataProvider, useMagnetic: this.useMagnetic, isActive: this.isAlertActive })));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c;
            (_a = this.fromLegDistanceRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.legArrowRef.getOrDefault()) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.alertRef.getOrDefault()) === null || _c === void 0 ? void 0 : _c.destroy();
            this.toLeg.destroy();
            this.useMagnetic.destroy();
            super.destroy();
        }
    }
    NavStatusBoxLegDisplay.HEADING_FORMATTER = (heading, isMagnetic) => {
        const rounded = Math.round(heading);
        return `${rounded === 0 ? '360' : rounded.toString().padStart(3, '0')}°${isMagnetic ? '' : 'ᴛ'}`;
    };
    NavStatusBoxLegDisplay.ALTITUDE_FORMATTER = msfsSdk.NumberFormatter.create({ precision: 1 });
    NavStatusBoxLegDisplay.DISTANCE_FORMATTER = msfsSdk.NumberFormatter.create({ precision: 0.1, maxDigits: 3 });

    /**
     * A next-generation (NXi, G3000, etc) Garmin PFD navigation status box.
     */
    class NavStatusBox extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.legRef = msfsSdk.FSComponent.createRef();
            this.rootCssClass = msfsSdk.SetSubject.create(['nav-status']);
            this.rootStyle = msfsSdk.ObjectSubject.create({
                'display': 'none'
            });
            this.modelFactory = new NavStatusBoxFieldModelFactory(this.props.bus, this.props.gpsValidity);
            this.fieldRenderer = new NavStatusBoxFieldRenderer(this.props.unitsSettingManager);
            this.fieldModels = [];
            this.fieldInstances = [];
        }
        /** @inheritdoc */
        onAfterRender() {
            const gpsValiditySub = this.gpsValiditySub = this.props.gpsValidity.sub(validity => {
                this.rootCssClass.toggle('nav-status-gps-loi', validity !== exports.NavDataFieldGpsValidity.Valid);
            }, false, true);
            this.declutterSub = this.props.declutter.sub(declutter => {
                if (declutter) {
                    this.rootStyle.set('display', 'none');
                    this.legRef.instance.pause();
                    gpsValiditySub.pause();
                }
                else {
                    this.rootStyle.set('display', '');
                    this.legRef.instance.resume();
                    gpsValiditySub.resume(true);
                }
            }, true);
        }
        /** @inheritdoc */
        render() {
            if (typeof this.props.class === 'object') {
                this.cssClassSub = msfsSdk.FSComponent.bindCssClassSet(this.rootCssClass, this.props.class, NavStatusBox.RESERVED_CSS_CLASSES);
            }
            else {
                if (this.props.class !== undefined && this.props.class.length > 0) {
                    const classesToAdd = msfsSdk.FSComponent.parseCssClassesFromString(this.props.class, classToAdd => !NavStatusBox.RESERVED_CSS_CLASSES.includes(classToAdd));
                    for (const classToAdd of classesToAdd) {
                        this.rootCssClass.add(classToAdd);
                    }
                }
            }
            return (msfsSdk.FSComponent.buildComponent("div", { class: this.rootCssClass, style: this.rootStyle },
                msfsSdk.FSComponent.buildComponent(NavStatusBoxLegDisplay, { ref: this.legRef, dataProvider: this.props.dataProvider, unitsSettingManager: this.props.unitsSettingManager }),
                msfsSdk.FSComponent.buildComponent("div", { class: 'nav-status-fields' }, this.props.fieldTypes.map((type, index) => {
                    const model = this.modelFactory.create(type);
                    const field = this.fieldRenderer.render(type, model);
                    this.fieldModels.push(model);
                    this.fieldInstances.push(field.instance);
                    return (msfsSdk.FSComponent.buildComponent("div", { class: `nav-status-field-slot nav-status-field-slot-${index + 1}` }, field));
                }))));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c, _d;
            (_a = this.legRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
            this.fieldInstances.forEach(field => { field.destroy(); });
            this.fieldModels.forEach(model => { model.destroy(); });
            (_b = this.declutterSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.cssClassSub) === null || _c === void 0 ? void 0 : _c.destroy();
            (_d = this.gpsValiditySub) === null || _d === void 0 ? void 0 : _d.destroy();
            super.destroy();
        }
    }
    NavStatusBox.RESERVED_CSS_CLASSES = ['nav-status', 'nav-status-gps-loi'];

    /**
     * A default implementation of {@link NavStatusBoxDataProvider}.
     */
    class DefaultNavStatusBoxDataProvider {
        /**
         * Constructor.
         * @param bus The event bus.
         * @param fms The FMS.
         * @param waypointAlertComputer The waypoint alert computer from which to source this provider's waypoint alert data.
         */
        constructor(bus, fms, waypointAlertComputer) {
            this.bus = bus;
            this.fms = fms;
            this.waypointAlertComputer = waypointAlertComputer;
            this.trackedLegsBuffer = [
                { fromLeg: null, toLeg: null, nextLeg: null },
                { fromLeg: null, toLeg: null, nextLeg: null }
            ];
            this._trackedLegs = msfsSdk.Subject.create(this.trackedLegsBuffer[0], (a, b) => a.fromLeg === b.fromLeg && a.toLeg === b.toLeg && a.nextLeg === b.nextLeg);
            /** @inheritdoc */
            this.trackedLegs = this._trackedLegs;
            this.isObsActive = msfsSdk.ConsumerSubject.create(null, false);
            this.obsCourseSource = msfsSdk.ConsumerSubject.create(null, 0);
            this._obsCourse = msfsSdk.MappedSubject.create(([isObsActive, obsCourseSource]) => isObsActive ? obsCourseSource : null, this.isObsActive, this.obsCourseSource);
            this.obsCourse = this._obsCourse;
            /** @inheritdoc */
            this.waypointAlertState = this.waypointAlertComputer.state;
            /** @inheritdoc */
            this.waypointAlertTime = this.waypointAlertComputer.timeRemaining;
            this.isInit = false;
            this.isAlive = true;
            this.isPaused = false;
            this.needUpdateTrackedLegs = false;
            this.fplSubs = [];
        }
        /**
         * Initializes this data provider. Once initialized, this data provider will continuously update its data until
         * paused or destroyed.
         * @param paused Whether to initialize this data provider as paused. If `true`, this data provider will provide an
         * initial set of data but will not update the provided data until it is resumed. Defaults to `false`.
         * @throws Error if this data provider is dead.
         */
        init(paused = false) {
            if (!this.isAlive) {
                throw new Error('DefaultNavStatusBoxDataProvider: cannot initialize a dead provider');
            }
            if (this.isInit) {
                return;
            }
            this.isInit = true;
            this.isPaused = paused;
            const sub = this.bus.getSubscriber();
            this.isObsActive.setConsumer(sub.on('gps_obs_active'));
            this.obsCourseSource.setConsumer(sub.on('gps_obs_value'));
            this.fplSubs.push(this.fms.flightPlanner.onEvent('fplCreated').handle(evt => {
                if (evt.planIndex === this.fms.flightPlanner.activePlanIndex) {
                    this.needUpdateTrackedLegs = true;
                }
            }));
            this.fplSubs.push(this.fms.flightPlanner.onEvent('fplDeleted').handle(evt => {
                if (evt.planIndex === this.fms.flightPlanner.activePlanIndex) {
                    this.needUpdateTrackedLegs = true;
                }
            }));
            this.fplSubs.push(this.fms.flightPlanner.onEvent('fplLoaded').handle(evt => {
                if (evt.planIndex === this.fms.flightPlanner.activePlanIndex) {
                    this.needUpdateTrackedLegs = true;
                }
            }));
            this.fplSubs.push(this.fms.flightPlanner.onEvent('fplCopied').handle(evt => {
                if (evt.planIndex === this.fms.flightPlanner.activePlanIndex) {
                    this.needUpdateTrackedLegs = true;
                }
            }));
            this.fplSubs.push(this.fms.flightPlanner.onEvent('fplIndexChanged').handle(() => {
                this.needUpdateTrackedLegs = true;
            }));
            this.fplSubs.push(this.fms.flightPlanner.onEvent('fplSegmentChange').handle((evt) => {
                if (evt.planIndex === this.fms.flightPlanner.activePlanIndex) {
                    this.needUpdateTrackedLegs = true;
                }
            }));
            this.fplSubs.push(this.fms.flightPlanner.onEvent('fplLegChange').handle((evt) => {
                if (evt.planIndex === this.fms.flightPlanner.activePlanIndex) {
                    this.needUpdateTrackedLegs = true;
                }
            }));
            this.fplSubs.push(this.fms.flightPlanner.onEvent('fplActiveLegChange').handle((evt) => {
                if (evt.planIndex === this.fms.flightPlanner.activePlanIndex && evt.type === msfsSdk.ActiveLegType.Lateral) {
                    this.needUpdateTrackedLegs = true;
                }
            }));
            this.needUpdateTrackedLegs = true;
            this.clockSub = sub.on('realTime').handle(this.update.bind(this));
            if (paused) {
                this.pause();
            }
        }
        /**
         * Updates this data provider.
         */
        update() {
            if (this.needUpdateTrackedLegs) {
                this.updateTrackedLegs();
                this.needUpdateTrackedLegs = false;
            }
        }
        /**
         * Updates this provider's tracked leg information.
         */
        updateTrackedLegs() {
            var _a;
            let toLeg = null;
            let fromLeg = null;
            let nextLeg = null;
            if (this.fms.flightPlanner.hasActiveFlightPlan()) {
                const plan = this.fms.flightPlanner.getActiveFlightPlan();
                const activeLegIndex = plan.activeLateralLeg;
                const isDirectTo = this.fms.getDirectToState() !== exports.DirectToState.NONE;
                if (plan.length > 0 && activeLegIndex < plan.length) {
                    toLeg = plan.getLeg(activeLegIndex);
                    if (activeLegIndex > 0 && !isDirectTo && !msfsSdk.BitFlags.isAny(toLeg.flags, msfsSdk.LegDefinitionFlags.VectorsToFinalFaf)) {
                        fromLeg = (_a = FmsUtils.getFromLegForArrowDisplay(plan, activeLegIndex)) !== null && _a !== void 0 ? _a : null;
                    }
                    if (activeLegIndex < plan.length - 1) {
                        nextLeg = plan.getLeg(activeLegIndex + 1);
                    }
                }
            }
            const trackedLegs = this.trackedLegsBuffer[this._trackedLegs.get() === this.trackedLegsBuffer[0] ? 1 : 0];
            trackedLegs.fromLeg = fromLeg;
            trackedLegs.toLeg = toLeg;
            trackedLegs.nextLeg = nextLeg;
            this._trackedLegs.set(trackedLegs);
        }
        /**
         * Resumes this data provider. Once resumed, this data provider will continuously update its data until paused or
         * destroyed.
         * @throws Error if this data provider is dead.
         */
        resume() {
            var _a;
            if (!this.isAlive) {
                throw new Error('DefaultNavStatusBoxDataProvider: cannot resume a dead provider');
            }
            if (!this.isPaused) {
                return;
            }
            this.isObsActive.resume();
            this.obsCourseSource.resume();
            this.fplSubs.forEach(sub => { sub.resume(); });
            this.needUpdateTrackedLegs = true;
            (_a = this.clockSub) === null || _a === void 0 ? void 0 : _a.resume(true);
            this.isPaused = false;
        }
        /**
         * Pauses this data provider. Once paused, this data provider will not update its data until it is resumed.
         * @throws Error if this data provider is dead.
         */
        pause() {
            var _a;
            if (!this.isAlive) {
                throw new Error('DefaultNavStatusBoxDataProvider: cannot pause a dead provider');
            }
            if (this.isPaused) {
                return;
            }
            this.isObsActive.pause();
            this.obsCourseSource.pause();
            this.fplSubs.forEach(sub => { sub.pause(); });
            (_a = this.clockSub) === null || _a === void 0 ? void 0 : _a.pause();
            this.isPaused = true;
        }
        /**
         * Destroys this data provider. Once destroyed, this data provider will no longer update its provided data, and can
         * no longer be paused or resumed.
         */
        destroy() {
            var _a;
            this.isAlive = false;
            this.isObsActive.destroy();
            this.obsCourseSource.destroy();
            this.fplSubs.forEach(sub => { sub.destroy(); });
            (_a = this.clockSub) === null || _a === void 0 ? void 0 : _a.destroy();
        }
    }

    /**
     * A next-generation (NXi, G3000, etc) Garmin PFD traffic annunciation.
     */
    class PfdTrafficAnnunciation extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.rootStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.rootCssClass = msfsSdk.SetSubject.create(['traffic-annunc']);
            this.isTcasII = this.props.trafficSystem.type === exports.TrafficSystemType.TcasII;
            this.flashTimer = new msfsSdk.DebounceTimer();
            this.removeFlashFunc = () => { this.rootCssClass.delete('traffic-annunc-flash'); };
            this.text = msfsSdk.Subject.create('');
            this.activeAlertType = 'none';
        }
        /** @inheritdoc */
        onAfterRender() {
            const taSub = this.taSub = this.props.advisoryDataProvider.taIntruders.sub((set, type) => {
                this.onAdvisoryChanged(msfsSdk.TcasAlertLevel.TrafficAdvisory, type);
            }, false, true);
            const raSub = this.raSub = this.props.advisoryDataProvider.raIntruders.sub((set, type) => {
                this.onAdvisoryChanged(msfsSdk.TcasAlertLevel.ResolutionAdvisory, type);
            }, false, true);
            if (this.isTcasII) {
                this.operatingModeSub = this.props.trafficSystem.getEventSubscriber().on('tcas_operating_mode').whenChanged().handle(() => {
                    this.onAdvisoryChanged();
                }, true);
            }
            this.declutterSub = this.props.declutter.sub(declutter => {
                var _a, _b;
                if (declutter) {
                    taSub.pause();
                    raSub.pause();
                    (_a = this.operatingModeSub) === null || _a === void 0 ? void 0 : _a.pause();
                    this.rootStyle.set('display', 'none');
                    this.flashTimer.clear();
                    this.rootCssClass.delete('traffic-annunc-flash');
                }
                else {
                    this.onAdvisoryChanged();
                    taSub.resume();
                    raSub.resume();
                    (_b = this.operatingModeSub) === null || _b === void 0 ? void 0 : _b.resume();
                }
            }, true);
        }
        /**
         * Responds to when an advisory is issued or cancelled.
         * @param alertLevel The level of the changed advisory.
         * @param type The type of change: whether the advisory was added or removed.
         */
        onAdvisoryChanged(alertLevel, type) {
            const raCount = this.props.advisoryDataProvider.raIntruders.size;
            const taCount = this.props.advisoryDataProvider.taIntruders.size;
            let alertType;
            let shouldFlash = false;
            if (raCount > 0) {
                alertType = 'RA';
                shouldFlash = alertLevel === msfsSdk.TcasAlertLevel.ResolutionAdvisory && type === msfsSdk.SubscribableSetEventType.Added;
            }
            else if (taCount > 0) {
                alertType = 'TA';
                shouldFlash = alertLevel === msfsSdk.TcasAlertLevel.TrafficAdvisory && type === msfsSdk.SubscribableSetEventType.Added;
            }
            else if (this.isTcasII && this.props.trafficSystem.getOperatingMode() === msfsSdk.TcasOperatingMode.TAOnly) {
                alertType = 'TAOnly';
            }
            else {
                alertType = 'none';
            }
            this.text.set(alertType === 'TAOnly' ? 'TA ONLY' : 'TRAFFIC');
            this.rootCssClass.toggle('traffic-annunc-taonly', alertType === 'TAOnly');
            this.rootCssClass.toggle('traffic-annunc-ta', alertType === 'TA');
            this.rootCssClass.toggle('traffic-annunc-ra', alertType === 'RA');
            if (shouldFlash) {
                this.rootCssClass.add('traffic-annunc-flash');
                this.flashTimer.schedule(this.removeFlashFunc, PfdTrafficAnnunciation.FLASH_DURATION);
            }
            else if (this.activeAlertType !== alertType) {
                this.flashTimer.clear();
                this.rootCssClass.delete('traffic-annunc-flash');
            }
            this.activeAlertType = alertType;
            this.rootStyle.set('display', alertType === 'none' ? 'none' : '');
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: this.rootCssClass, style: this.rootStyle }, this.text));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c, _d;
            (_a = this.declutterSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.taSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.raSub) === null || _c === void 0 ? void 0 : _c.destroy();
            (_d = this.operatingModeSub) === null || _d === void 0 ? void 0 : _d.destroy();
            super.destroy();
        }
    }
    PfdTrafficAnnunciation.FLASH_DURATION = 5000; // milliseconds

    /* eslint-disable @typescript-eslint/no-non-null-assertion */
    /**
     * A next-generation (NXi, G3000, etc) Garmin PFD vertical speed indicator.
     */
    class VerticalSpeedIndicator extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.minorTickContainerRef = msfsSdk.FSComponent.createRef();
            this.majorTickContainerRef = msfsSdk.FSComponent.createRef();
            this.labelContainerRef = msfsSdk.FSComponent.createRef();
            this.vsPointerBugRef = msfsSdk.FSComponent.createRef();
            this.selectedVsBugRef = msfsSdk.FSComponent.createRef();
            this.vsRequiredBugRef = msfsSdk.FSComponent.createRef();
            this.vnavAltDisplayRef = msfsSdk.FSComponent.createRef();
            this.selectedVsDisplayRef = msfsSdk.FSComponent.createRef();
            this.raColorRangeMaxStyle = msfsSdk.ObjectSubject.create({
                position: 'absolute',
                top: '0px',
                height: '100%',
                transform: 'scaleY(0)',
                'transform-origin': 'top center'
            });
            this.raColorRangeMinStyle = msfsSdk.ObjectSubject.create({
                position: 'absolute',
                top: '0px',
                height: '100%',
                transform: 'scaleY(0)',
                'transform-origin': 'bottom center'
            });
            this.raColorRangeFlyToStyle = msfsSdk.ObjectSubject.create({
                position: 'absolute',
                top: '0px',
                height: '100%',
                transform: 'translate(0px, 0px) scaleY(0)',
                'transform-origin': 'top center'
            });
            this.rootCssClass = msfsSdk.SetSubject.create(['vsi']);
            this.maximum = msfsSdk.SubscribableUtils.toSubscribable(this.props.scaleOptions.maximum, true);
            this.majorTickInterval = msfsSdk.SubscribableUtils.toSubscribable(this.props.scaleOptions.majorTickInterval, true);
            this.minorTickFactor = msfsSdk.SubscribableUtils.toSubscribable(this.props.scaleOptions.minorTickFactor, true);
            this.options = msfsSdk.MappedSubject.create(this.maximum, this.majorTickInterval, this.minorTickFactor);
            this.updateTapeEvent = new msfsSdk.SubEvent();
            this.raFlyToState = this.props.tcasRaCommandDataProvider
                ? msfsSdk.MappedSubject.create(this.props.tcasRaCommandDataProvider.raFlyToMinVs, this.props.tcasRaCommandDataProvider.raFlyToMaxVs)
                : undefined;
            this.raVsAvoidanceState = this.props.tcasRaCommandDataProvider
                ? msfsSdk.MappedSubject.create(this.props.dataProvider.verticalSpeed, this.props.tcasRaCommandDataProvider.raMaxVs, this.props.tcasRaCommandDataProvider.raMinVs).pause()
                : undefined;
            this.showBugs = this.props.dataProvider.isDataFailed.map(msfsSdk.SubscribableMapFunctions.not());
            this.showDisplays = msfsSdk.MappedSubject.create(([declutter, isDataFailed]) => !declutter && !isDataFailed, this.props.declutter, this.props.dataProvider.isDataFailed);
        }
        /** @inheritdoc */
        onAfterRender() {
            this.options.sub(this.rebuildScale.bind(this), true);
            if (this.props.tcasRaCommandDataProvider) {
                this.raFlyToState.sub(([minVs, maxVs]) => {
                    this.updateRaFlyToVsColorRange(minVs, maxVs);
                }, true);
                this.raMaxVsSub = this.props.tcasRaCommandDataProvider.raMaxVs.sub(this.updateRaMaxVsColorRange.bind(this), true);
                this.raMinVsSub = this.props.tcasRaCommandDataProvider.raMinVs.sub(this.updateRaMinVsColorRange.bind(this), true);
                const raVsAvoidanceStateSub = this.raVsAvoidanceState.sub(([vs, maxVs, minVs]) => {
                    maxVs !== null && maxVs !== void 0 ? maxVs : (maxVs = Infinity);
                    minVs !== null && minVs !== void 0 ? minVs : (minVs = -Infinity);
                    this.rootCssClass.toggle('vsi-ra-warn', vs > maxVs || vs < minVs);
                }, false, true);
                this.isDataFailedSub = this.props.dataProvider.isDataFailed.sub(isDataFailed => {
                    if (isDataFailed) {
                        this.rootCssClass.add('data-failed');
                        this.rootCssClass.delete('vsi-ra-warn');
                        raVsAvoidanceStateSub.pause();
                        this.raVsAvoidanceState.pause();
                    }
                    else {
                        this.rootCssClass.delete('data-failed');
                        this.raVsAvoidanceState.resume();
                        raVsAvoidanceStateSub.resume(true);
                    }
                }, true);
            }
            this.rootCssClass.toggle('advanced-vnav', this.props.isAdvancedVnav);
        }
        /**
         * Rebuilds this tape's ticks and labels.
         * @param options Scale options for this tape, as `[minimum, maximum, window, majorTickInterval, minorTickFactor]`.
         */
        rebuildScale(options) {
            const [maximum, majorTickInterval, minorTickFactor] = options;
            this.minorTickContainerRef.instance.innerHTML = '';
            this.majorTickContainerRef.instance.innerHTML = '';
            this.labelContainerRef.instance.innerHTML = '';
            const majorTickCount = Math.floor(maximum / majorTickInterval);
            const halfMaxScaleLength = (1 - VerticalSpeedIndicator.SCALE_OVERDRAW) * 50;
            const len = majorTickCount * minorTickFactor;
            const minorTickInterval = majorTickInterval / minorTickFactor;
            for (let i = 1; i <= len; i++) {
                const yOffset = i / len * halfMaxScaleLength;
                if (i % minorTickFactor === 0) {
                    // major tick
                    msfsSdk.FSComponent.render(msfsSdk.FSComponent.buildComponent("path", { d: `M 0 ${50 - yOffset} L 100 ${50 - yOffset}`, "vector-effect": 'non-scaling-stroke', class: 'vsi-scale-tick vsi-scale-tick-major' }, "."), this.majorTickContainerRef.instance);
                    msfsSdk.FSComponent.render(msfsSdk.FSComponent.buildComponent("path", { d: `M 0 ${50 + yOffset} L 100 ${50 + yOffset}`, "vector-effect": 'non-scaling-stroke', class: 'vsi-scale-tick vsi-scale-tick-major' }, "."), this.majorTickContainerRef.instance);
                    const vs = i * minorTickInterval;
                    msfsSdk.FSComponent.render(msfsSdk.FSComponent.buildComponent("div", { class: 'vsi-scale-label', style: `position: absolute; left: 0%; top: ${50 - yOffset}%; transform: translateY(-50%)` }, (vs / 1000).toFixed(0)), this.labelContainerRef.instance);
                    msfsSdk.FSComponent.render(msfsSdk.FSComponent.buildComponent("div", { class: 'vsi-scale-label', style: `position: absolute; left: 0%; top: ${50 + yOffset}%; transform: translateY(-50%)` }, (vs / 1000).toFixed(0)), this.labelContainerRef.instance);
                }
                else {
                    // minor tick
                    msfsSdk.FSComponent.render(msfsSdk.FSComponent.buildComponent("path", { d: `M 0 ${50 - yOffset} L 100 ${50 - yOffset}`, "vector-effect": 'non-scaling-stroke', class: 'vsi-scale-tick vsi-scale-tick-minor' }, "."), this.minorTickContainerRef.instance);
                    msfsSdk.FSComponent.render(msfsSdk.FSComponent.buildComponent("path", { d: `M 0 ${50 + yOffset} L 100 ${50 + yOffset}`, "vector-effect": 'non-scaling-stroke', class: 'vsi-scale-tick vsi-scale-tick-minor' }, "."), this.minorTickContainerRef.instance);
                }
            }
            this.updateTapeEvent.notify(this);
            if (this.props.tcasRaCommandDataProvider) {
                this.updateRaMaxVsColorRange(this.props.tcasRaCommandDataProvider.raMaxVs.get());
                this.updateRaMinVsColorRange(this.props.tcasRaCommandDataProvider.raMinVs.get());
                this.updateRaFlyToVsColorRange(this.props.tcasRaCommandDataProvider.raFlyToMinVs.get(), this.props.tcasRaCommandDataProvider.raFlyToMaxVs.get());
            }
        }
        /**
         * Updates the color range depicting the maximum allowed vertical speed commanded by the current resolution
         * advisory.
         * @param maxVs The maximum allowed vertical speed, in feet per minute, commanded by the current resolution
         * advisory, or `null` if there is no such value.
         */
        updateRaMaxVsColorRange(maxVs) {
            if (maxVs === null) {
                this.raColorRangeMaxStyle.set('transform', 'scaleY(0)');
            }
            else {
                const vsPosition = this.calculateScalePosition(maxVs);
                this.raColorRangeMaxStyle.set('transform', `scaleY(${vsPosition})`);
            }
        }
        /**
         * Updates the color range depicting the minimum allowed vertical speed commanded by the current resolution
         * advisory.
         * @param minVs The minimum allowed vertical speed, in feet per minute, commanded by the current resolution
         * advisory, or `null` if there is no such value.
         */
        updateRaMinVsColorRange(minVs) {
            if (minVs === null) {
                this.raColorRangeMinStyle.set('transform', 'scaleY(0)');
            }
            else {
                const vsPosition = this.calculateScalePosition(minVs);
                this.raColorRangeMinStyle.set('transform', `scaleY(${1 - vsPosition})`);
            }
        }
        /**
         * Updates the color range depicting the fly-to command issued by the current resolution advisory.
         * @param minVs The lower bound, in feet per minute, of the current fly-to command, or `null` if there is no such
         * value.
         * @param maxVs The upper bound, in feet per minute, of the current fly-to command, or `null` if there is no such
         * value.
         */
        updateRaFlyToVsColorRange(minVs, maxVs) {
            if (minVs === null || maxVs === null) {
                this.raColorRangeFlyToStyle.set('transform', 'translate(0px, 0px) scaleY(0)');
            }
            else {
                const maxVsPosition = this.calculateScalePosition(maxVs);
                const minVsPosition = this.calculateScalePosition(minVs);
                const height = minVsPosition - maxVsPosition;
                this.raColorRangeFlyToStyle.set('transform', `translate(0px, ${maxVsPosition * 100}%) scaleY(${height})`);
            }
        }
        /**
         * Calculates the vertical position on this indicator's scale at which a particular vertical speed is located, with
         * `0` at the top of the scale and `1` at the bottom.
         * @param verticalSpeed A vertical speed, in feet per minute.
         * @returns The vertical position on this indicator's scale at which the specified vertical speed is located, with
         * `0` at the top of the scale and `1` at the bottom.
         */
        calculateScalePosition(verticalSpeed) {
            const max = this.maximum.get() * (1 + VerticalSpeedIndicator.SCALE_OVERDRAW);
            return 1 - (verticalSpeed + max) / (2 * max);
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: this.rootCssClass },
                msfsSdk.FSComponent.buildComponent("div", { class: 'vsi-scale', "data-checklist": "checklist-vsi" },
                    msfsSdk.FSComponent.buildComponent("div", { class: 'vsi-scale-ra-color-range-container', style: 'position: absolute; top: 0; height: 100%;' },
                        msfsSdk.FSComponent.buildComponent("div", { class: 'vsi-scale-ra-color-range vsi-scale-ra-color-range-max', style: this.raColorRangeMaxStyle }),
                        msfsSdk.FSComponent.buildComponent("div", { class: 'vsi-scale-ra-color-range vsi-scale-ra-color-range-min', style: this.raColorRangeMinStyle }),
                        msfsSdk.FSComponent.buildComponent("div", { class: 'vsi-scale-ra-color-range vsi-scale-ra-color-range-flyto', style: this.raColorRangeFlyToStyle })),
                    msfsSdk.FSComponent.buildComponent("svg", { ref: this.minorTickContainerRef, class: 'vsi-scale-tick-minor-container', viewBox: '0 0 100 100', preserveAspectRatio: 'none', style: 'position: absolute; top: 0; height: 100%;' }),
                    msfsSdk.FSComponent.buildComponent("svg", { ref: this.majorTickContainerRef, class: 'vsi-scale-tick-major-container', viewBox: '0 0 100 100', preserveAspectRatio: 'none', style: 'position: absolute; top: 0; height: 100%;' }),
                    msfsSdk.FSComponent.buildComponent("div", { ref: this.labelContainerRef, class: 'vsi-scale-label-container', style: 'position: absolute; top: 0; height: 100%;' }),
                    msfsSdk.FSComponent.buildComponent("div", { class: 'vsi-bug-container', style: 'position: absolute; left: 0; top: 0; width: 100%; height: 100%;' },
                        msfsSdk.FSComponent.buildComponent("div", { class: 'vsi-bug-container-clip', style: 'position: absolute; left: 0; top: 0; width: 100%; height: 100%; overflow: hidden;' },
                            msfsSdk.FSComponent.buildComponent(SelectedVsBug, { ref: this.selectedVsBugRef, show: this.showBugs, selectedVs: this.props.dataProvider.selectedVs, updateEvent: this.updateTapeEvent, getPosition: this.calculateScalePosition.bind(this) }),
                            msfsSdk.FSComponent.buildComponent(VsRequiredBug, { ref: this.vsRequiredBugRef, show: this.showBugs, vsRequired: this.props.dataProvider.vsRequired, updateEvent: this.updateTapeEvent, getPosition: this.calculateScalePosition.bind(this) })),
                        msfsSdk.FSComponent.buildComponent(VsPointerBug, { ref: this.vsPointerBugRef, show: this.showBugs, verticalSpeed: this.props.dataProvider.verticalSpeed, updateEvent: this.updateTapeEvent, getPosition: (verticalSpeed) => {
                                return msfsSdk.MathUtils.clamp(this.calculateScalePosition(verticalSpeed), VerticalSpeedIndicator.POINTER_POS_MIN, VerticalSpeedIndicator.POINTER_POS_MAX);
                            } }))),
                msfsSdk.FSComponent.buildComponent(VNavTargetAltitudeDisplay, { ref: this.vnavAltDisplayRef, show: this.showDisplays, targetRestriction: this.props.dataProvider.targetRestriction }),
                msfsSdk.FSComponent.buildComponent(SelectedVsDisplay, { ref: this.selectedVsDisplayRef, show: this.showDisplays, selectedVs: this.props.dataProvider.selectedVs }),
                msfsSdk.FSComponent.buildComponent("div", { class: 'failed-box' })));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
            (_a = this.vsPointerBugRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.selectedVsBugRef.getOrDefault()) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.vsRequiredBugRef.getOrDefault()) === null || _c === void 0 ? void 0 : _c.destroy();
            (_d = this.vnavAltDisplayRef.getOrDefault()) === null || _d === void 0 ? void 0 : _d.destroy();
            (_e = this.selectedVsDisplayRef.getOrDefault()) === null || _e === void 0 ? void 0 : _e.destroy();
            this.options.destroy();
            (_f = this.raFlyToState) === null || _f === void 0 ? void 0 : _f.destroy();
            (_g = this.raVsAvoidanceState) === null || _g === void 0 ? void 0 : _g.destroy();
            this.showBugs.destroy();
            this.showDisplays.destroy();
            (_h = this.isDataFailedSub) === null || _h === void 0 ? void 0 : _h.destroy();
            (_j = this.raMaxVsSub) === null || _j === void 0 ? void 0 : _j.destroy();
            (_k = this.raMinVsSub) === null || _k === void 0 ? void 0 : _k.destroy();
            super.destroy();
        }
    }
    /** The amount of overdraw on the upper and lower ends of the scale, as a factor of the scale maximum. */
    VerticalSpeedIndicator.SCALE_OVERDRAW = 0.125;
    /**
     * The amount of overdraw available for the vertical speed pointer on the upper and lower ends of the scale, as a
     * factor of the scale maximum.
     */
    VerticalSpeedIndicator.POINTER_OVERDRAW = 0.05;
    VerticalSpeedIndicator.POINTER_POS_MIN = 0.5 - ((1 + VerticalSpeedIndicator.POINTER_OVERDRAW) / (1 + VerticalSpeedIndicator.SCALE_OVERDRAW)) / 2;
    VerticalSpeedIndicator.POINTER_POS_MAX = 0.5 + ((1 + VerticalSpeedIndicator.POINTER_OVERDRAW) / (1 + VerticalSpeedIndicator.SCALE_OVERDRAW)) / 2;
    /**
     * A vertical speed bug for a next-generation (NXi, G3000, etc) Garmin vertical speed indicator.
     */
    class VerticalSpeedBug extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.style = msfsSdk.ObjectSubject.create({
                display: '',
                position: 'absolute',
                top: '50%',
                transform: 'translate3d(0, -50%, 0)'
            });
            this.position = msfsSdk.Subject.create(0);
            this.vsFpmRounded = this.props.verticalSpeedFpm.map(msfsSdk.SubscribableMapFunctions.withPrecision(1)).pause();
        }
        /** @inheritdoc */
        onAfterRender() {
            const updateHandler = this.updatePosition.bind(this);
            this.vsFpmRounded.sub(updateHandler);
            const updateEventSub = this.updateEventSub = this.props.updateEvent.on(updateHandler, true);
            this.position.sub(translate => {
                this.style.set('top', `${translate}%`);
            });
            this.showSub = this.props.show.sub(show => {
                if (show) {
                    this.vsFpmRounded.resume();
                    updateEventSub.resume();
                    this.style.set('display', '');
                }
                else {
                    this.vsFpmRounded.pause();
                    updateEventSub.pause();
                    this.style.set('display', 'none');
                }
            }, true);
        }
        /**
         * Updates this bug's position on its parent vertical speed indicator.
         */
        updatePosition() {
            const pos = this.props.getPosition(this.vsFpmRounded.get());
            this.position.set(msfsSdk.MathUtils.round(pos * 100, 0.1));
        }
        /** @inheritdoc */
        render() {
            let cssClass;
            if (typeof this.props.class === 'object') {
                const baseClass = ['vsi-bug'];
                cssClass = msfsSdk.SetSubject.create(baseClass);
                this.cssClassSub = msfsSdk.FSComponent.bindCssClassSet(cssClass, this.props.class, baseClass);
            }
            else {
                const classesToAdd = this.props.class === undefined
                    ? ''
                    : msfsSdk.FSComponent.parseCssClassesFromString(this.props.class, classToAdd => classToAdd !== 'vsi-bug').join(' ');
                cssClass = `vsi-bug ${classesToAdd}`;
            }
            return (msfsSdk.FSComponent.buildComponent("div", { class: cssClass, style: this.style }, this.props.children));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c, _d;
            super.destroy();
            (_a = this.vsFpmRounded) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.cssClassSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.showSub) === null || _c === void 0 ? void 0 : _c.destroy();
            (_d = this.updateEventSub) === null || _d === void 0 ? void 0 : _d.destroy();
        }
    }
    /**
     * A vertical speed pointer bug.
     */
    class VsPointerBug extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.bugRef = msfsSdk.FSComponent.createRef();
            this.roundedVs = this.props.verticalSpeed.map(msfsSdk.SubscribableMapFunctions.withPrecision(50)).pause();
            this.text = this.roundedVs.map(vs => {
                const abs = Math.abs(vs);
                if (abs < 100) {
                    return '';
                }
                else {
                    if (abs < 10000) {
                        return VsPointerBug.FORMATTER_NORMAL(vs);
                    }
                    else {
                        return `${VsPointerBug.FORMATTER_THOUSANDS(vs / 1000)}K`;
                    }
                }
            });
        }
        /** @inheritdoc */
        onAfterRender() {
            this.showSub = this.props.show.sub(show => {
                if (show) {
                    this.roundedVs.resume();
                }
                else {
                    this.roundedVs.pause();
                }
            }, true);
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent(VerticalSpeedBug, { ref: this.bugRef, verticalSpeedFpm: this.props.verticalSpeed, show: this.props.show, updateEvent: this.props.updateEvent, getPosition: (indicatedAlt) => msfsSdk.MathUtils.clamp(this.props.getPosition(indicatedAlt), 0, 1), class: 'vsi-pointer-bug' },
                msfsSdk.FSComponent.buildComponent("svg", { viewBox: '0 0 68 24', preserveAspectRatio: 'none', class: 'vsi-pointer-bug-background' },
                    msfsSdk.FSComponent.buildComponent("path", { d: 'M 1 12 l 19 -11 l 44 0 c 2 0 3 1 3 3 l 0 16 c 0 2 -1 3 -3 3 l -44 0 z', "vector-effect": 'non-scaling-stroke' })),
                msfsSdk.FSComponent.buildComponent("div", { class: 'vsi-pointer-bug-text' }, this.text)));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b;
            (_a = this.bugRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
            this.roundedVs.destroy();
            (_b = this.showSub) === null || _b === void 0 ? void 0 : _b.destroy();
            super.destroy();
        }
    }
    VsPointerBug.FORMATTER_NORMAL = msfsSdk.NumberFormatter.create({ useMinusSign: true });
    VsPointerBug.FORMATTER_THOUSANDS = msfsSdk.NumberFormatter.create({ precision: 0.1, useMinusSign: true });
    /**
     * A selected vertical speed bug.
     */
    class SelectedVsBug extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.bugRef = msfsSdk.FSComponent.createRef();
            this.visibilityState = msfsSdk.MappedSubject.create(this.props.show, this.props.selectedVs).pause();
            this.show = msfsSdk.Subject.create(false);
            this.verticalSpeedFpm = msfsSdk.Subject.create(0);
        }
        /** @inheritdoc */
        onAfterRender() {
            this.visibilityState.resume();
            this.visibilityState.sub(([show, selectedVs]) => {
                if (show && selectedVs !== null) {
                    this.show.set(true);
                    this.verticalSpeedFpm.set(selectedVs);
                }
                else {
                    this.show.set(false);
                }
            }, true);
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent(VerticalSpeedBug, { ref: this.bugRef, verticalSpeedFpm: this.verticalSpeedFpm, show: this.show, updateEvent: this.props.updateEvent, getPosition: (indicatedAlt) => msfsSdk.MathUtils.clamp(this.props.getPosition(indicatedAlt), 0, 1), class: 'vsi-selectedvs-bug' },
                msfsSdk.FSComponent.buildComponent("svg", { viewBox: '0 0 100 100', preserveAspectRatio: 'none', class: 'vsi-selectedvs-bug-icon' },
                    msfsSdk.FSComponent.buildComponent("path", { d: 'M 95 5 h -90 v 90 h 90 v -30 L 45 50 L 95 30 Z', "vector-effect": 'non-scaling-stroke' }))));
        }
        /** @inheritdoc */
        destroy() {
            var _a;
            (_a = this.bugRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
            this.visibilityState.destroy();
            super.destroy();
        }
    }
    /**
     * A required vertical speed bug.
     */
    class VsRequiredBug extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.bugRef = msfsSdk.FSComponent.createRef();
            this.visibilityState = msfsSdk.MappedSubject.create(this.props.show, this.props.vsRequired).pause();
            this.show = msfsSdk.Subject.create(false);
            this.verticalSpeedFpm = msfsSdk.Subject.create(0);
        }
        /** @inheritdoc */
        onAfterRender() {
            this.visibilityState.resume();
            this.visibilityState.sub(([show, selectedVs]) => {
                if (show && selectedVs !== null) {
                    this.show.set(true);
                    this.verticalSpeedFpm.set(selectedVs);
                }
                else {
                    this.show.set(false);
                }
            }, true);
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent(VerticalSpeedBug, { ref: this.bugRef, verticalSpeedFpm: this.verticalSpeedFpm, show: this.show, updateEvent: this.props.updateEvent, getPosition: (indicatedAlt) => msfsSdk.MathUtils.clamp(this.props.getPosition(indicatedAlt), 0, 1), class: 'vsi-requiredvs-bug' },
                msfsSdk.FSComponent.buildComponent("svg", { viewBox: '0 0 16 28', preserveAspectRatio: 'none', class: 'vsi-requiredvs-bug-icon' },
                    msfsSdk.FSComponent.buildComponent("path", { d: 'M -5.5 14 l 19.5 -12 l 0 4 l -13 8 l 13 8 l 0 4 z', "vector-effect": 'non-scaling-stroke' }))));
        }
        /** @inheritdoc */
        destroy() {
            var _a;
            (_a = this.bugRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
            this.visibilityState.destroy();
            super.destroy();
        }
    }
    /**
     * A selected vertical speed display for a next-generation (NXi, G3000, etc) Garmin vertical speed indicator.
     */
    class SelectedVsDisplay extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.rootStyle = msfsSdk.ObjectSubject.create({
                display: ''
            });
            this.visibilityState = msfsSdk.MappedSubject.create(this.props.show, this.props.selectedVs).pause();
            this.text = msfsSdk.Subject.create('');
        }
        /** @inheritdoc */
        onAfterRender() {
            this.visibilityState.resume();
            this.visibilityState.sub(([show, selectedVs]) => {
                if (show && selectedVs !== null) {
                    this.rootStyle.set('display', '');
                    this.text.set(SelectedVsDisplay.FORMATTER(selectedVs));
                }
                else {
                    this.rootStyle.set('display', 'none');
                }
            }, true);
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: 'vsi-selectedvs-display', style: this.rootStyle },
                msfsSdk.FSComponent.buildComponent("div", { class: 'vsi-selectedvs-display-text' }, this.text)));
        }
        /** @inheritdoc */
        destroy() {
            this.visibilityState.destroy();
            super.destroy();
        }
    }
    SelectedVsDisplay.FORMATTER = msfsSdk.NumberFormatter.create({ useMinusSign: true });
    /**
     * A VNAV target altitude restriction display for a next-generation (NXi, G3000, etc) Garmin vertical speed indicator.
     */
    class VNavTargetAltitudeDisplay extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.rootStyle = msfsSdk.ObjectSubject.create({
                display: ''
            });
            this.rootCssClass = msfsSdk.SetSubject.create(['vsi-vnavalt-display']);
            this.visibilityState = msfsSdk.MappedSubject.create(this.props.show, this.props.targetRestriction).pause();
            this.restrictionType = msfsSdk.Subject.create(msfsSdk.AltitudeRestrictionType.At);
            this.text = msfsSdk.Subject.create('');
        }
        /** @inheritdoc */
        onAfterRender() {
            this.visibilityState.resume();
            this.restrictionType.sub(type => {
                this.rootCssClass.delete('vsi-vnavalt-display-at');
                this.rootCssClass.delete('vsi-vnavalt-display-atorabove');
                this.rootCssClass.delete('vsi-vnavalt-display-atorbelow');
                switch (type) {
                    case msfsSdk.AltitudeRestrictionType.At:
                        this.rootCssClass.add('vsi-vnavalt-display-at');
                        break;
                    case msfsSdk.AltitudeRestrictionType.AtOrAbove:
                        this.rootCssClass.add('vsi-vnavalt-display-atorabove');
                        break;
                    case msfsSdk.AltitudeRestrictionType.AtOrBelow:
                        this.rootCssClass.add('vsi-vnavalt-display-atorbelow');
                        break;
                }
            }, true);
            this.visibilityState.sub(([show, targetRestriction]) => {
                if (show && targetRestriction !== null) {
                    this.rootStyle.set('display', '');
                    this.restrictionType.set(targetRestriction.type);
                    this.text.set(targetRestriction.altitude.toFixed(0));
                }
                else {
                    this.rootStyle.set('display', 'none');
                }
            }, true);
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: this.rootCssClass, style: this.rootStyle },
                msfsSdk.FSComponent.buildComponent("div", { class: 'vsi-vnavalt-display-text-container' },
                    msfsSdk.FSComponent.buildComponent("div", { class: 'vsi-vnavalt-display-bar' }),
                    msfsSdk.FSComponent.buildComponent("div", { class: 'vsi-vnavalt-display-text' }, this.text))));
        }
        /** @inheritdoc */
        destroy() {
            this.visibilityState.destroy();
            super.destroy();
        }
    }

    /**
     * A default implementation of {@link VsiDataProvider}.
     */
    class DefaultVsiDataProvider {
        /**
         * Constructor.
         * @param bus The event bus.
         * @param adcIndex The index of the ADC that is the source of this provider's data.
         * @param vnavDataProvider A provider of VNAV data.
         */
        constructor(bus, adcIndex, vnavDataProvider) {
            this.bus = bus;
            this.vnavDataProvider = vnavDataProvider;
            this._verticalSpeed = msfsSdk.ConsumerSubject.create(null, 0);
            this.verticalSpeed = this._verticalSpeed;
            this.selectedVsSource = msfsSdk.ConsumerSubject.create(null, 0);
            this.isVsHoldActive = msfsSdk.ConsumerSubject.create(null, false);
            this._selectedVs = msfsSdk.MappedSubject.create(([selectedVsSource, isVsHoldActive]) => {
                return isVsHoldActive ? selectedVsSource : null;
            }, this.selectedVsSource, this.isVsHoldActive);
            /** @inheritdoc */
            this.selectedVs = this._selectedVs;
            /** @inheritdoc */
            this.targetRestriction = this.vnavDataProvider.targetRestriction;
            /** @inheritdoc */
            this.vsRequired = this.vnavDataProvider.vsRequired;
            this.isAltitudeDataValid = msfsSdk.ConsumerSubject.create(null, false);
            /** @inheritdoc */
            this.isDataFailed = this.isAltitudeDataValid.map(msfsSdk.SubscribableMapFunctions.not());
            this.isInit = false;
            this.isAlive = true;
            this.isPaused = false;
            this.adcIndex = msfsSdk.SubscribableUtils.toSubscribable(adcIndex, true);
        }
        /**
         * Initializes this data provider. Once initialized, this data provider will continuously update its data until
         * paused or destroyed.
         * @param paused Whether to initialize this data provider as paused. If `true`, this data provider will provide an
         * initial set of data but will not update the provided data until it is resumed. Defaults to `false`.
         * @throws Error if this data provider is dead.
         */
        init(paused = false) {
            if (!this.isAlive) {
                throw new Error('DefaultVsiDataProvider: cannot initialize a dead provider');
            }
            if (this.isInit) {
                return;
            }
            this.isInit = true;
            this.isPaused = paused;
            const sub = this.bus.getSubscriber();
            this.adcIndexSub = this.adcIndex.sub(index => {
                this._verticalSpeed.setConsumer(sub.on(`adc_vertical_speed_${index}`));
                this.isAltitudeDataValid.setConsumer(sub.on(`adc_altitude_data_valid_${index}`));
            }, true);
            this.selectedVsSource.setConsumer(sub.on('ap_vs_selected'));
            this.isVsHoldActive.setConsumer(sub.on('ap_vs_hold'));
            if (paused) {
                this.pause();
            }
        }
        /**
         * Resumes this data provider. Once resumed, this data provider will continuously update its data until paused or
         * destroyed.
         * @throws Error if this data provider is dead.
         */
        resume() {
            if (!this.isAlive) {
                throw new Error('DefaultVsiDataProvider: cannot resume a dead provider');
            }
            if (!this.isPaused) {
                return;
            }
            this.isPaused = false;
            this._verticalSpeed.resume();
            this.selectedVsSource.resume();
            this.isVsHoldActive.resume();
            this.isAltitudeDataValid.resume();
        }
        /**
         * Pauses this data provider. Once paused, this data provider will not update its data until it is resumed.
         * @throws Error if this data provider is dead.
         */
        pause() {
            if (!this.isAlive) {
                throw new Error('DefaultVsiDataProvider: cannot pause a dead provider');
            }
            if (this.isPaused) {
                return;
            }
            this._verticalSpeed.pause();
            this.selectedVsSource.pause();
            this.isVsHoldActive.pause();
            this.isAltitudeDataValid.pause();
            this.isPaused = true;
        }
        /**
         * Destroys this data provider. Once destroyed, this data provider will no longer update its provided data, and can
         * no longer be paused or resumed.
         */
        destroy() {
            var _a;
            this.isAlive = false;
            this._verticalSpeed.destroy();
            this.selectedVsSource.destroy();
            this.isVsHoldActive.destroy();
            this.isAltitudeDataValid.destroy();
            (_a = this.adcIndexSub) === null || _a === void 0 ? void 0 : _a.destroy();
        }
    }

    /**
     * Next-generation (NXi, G3000, etc) Garmin PFD wind display options.
     */
    exports.WindDisplayOption = void 0;
    (function (WindDisplayOption) {
        WindDisplayOption[WindDisplayOption["Option1"] = 1] = "Option1";
        WindDisplayOption[WindDisplayOption["Option2"] = 2] = "Option2";
        WindDisplayOption[WindDisplayOption["Option3"] = 3] = "Option3";
    })(exports.WindDisplayOption || (exports.WindDisplayOption = {}));
    /**
     * A next-generation (NXi, G3000, etc) Garmin PFD wind display.
     */
    class WindDisplay extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.option1Ref = msfsSdk.FSComponent.createRef();
            this.option2Ref = msfsSdk.FSComponent.createRef();
            this.option3Ref = msfsSdk.FSComponent.createRef();
            this.rootStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.noDataStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.rootCssClass = msfsSdk.SetSubject.create(['wind']);
            this.option1Declutter = msfsSdk.Subject.create(true);
            this.option2Declutter = msfsSdk.Subject.create(true);
            this.option3Declutter = msfsSdk.Subject.create(true);
        }
        /** @inheritdoc */
        onAfterRender() {
            const optionSub = this.optionSub = this.props.option.sub(option => {
                this.option1Declutter.set(option !== exports.WindDisplayOption.Option1);
                this.option2Declutter.set(option !== exports.WindDisplayOption.Option2);
                this.option3Declutter.set(option !== exports.WindDisplayOption.Option3);
            }, false, true);
            const isDeadReckoningSub = this.isDeadReckoningSub = this.props.dataProvider.isGpsDeadReckoning.sub(isDr => {
                this.rootCssClass.toggle('dead-reckoning', isDr);
            }, false, true);
            const isDataFailedSub = this.isDataFailedSub = this.props.dataProvider.isDataFailed.sub(isFailed => {
                if (isFailed) {
                    this.noDataStyle.set('display', '');
                    optionSub.pause();
                    this.option1Declutter.set(true);
                    this.option2Declutter.set(true);
                    this.option3Declutter.set(true);
                }
                else {
                    this.noDataStyle.set('display', 'none');
                    optionSub.resume(true);
                }
            }, false, true);
            this.declutterSub = this.props.declutter.sub(declutter => {
                if (declutter) {
                    this.rootStyle.set('display', 'none');
                    isDeadReckoningSub.pause();
                    isDataFailedSub.pause();
                    optionSub.pause();
                }
                else {
                    this.rootStyle.set('display', '');
                    isDeadReckoningSub.resume(true);
                    isDataFailedSub.resume(true);
                }
            }, true);
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: this.rootCssClass, style: this.rootStyle },
                msfsSdk.FSComponent.buildComponent(WindOption1, { ref: this.option1Ref, dataProvider: this.props.dataProvider, unitsSettingManager: this.props.unitsSettingManager, declutter: this.option1Declutter }),
                msfsSdk.FSComponent.buildComponent(WindOption2, { ref: this.option2Ref, dataProvider: this.props.dataProvider, unitsSettingManager: this.props.unitsSettingManager, declutter: this.option2Declutter }),
                msfsSdk.FSComponent.buildComponent(WindOption3, { ref: this.option3Ref, dataProvider: this.props.dataProvider, unitsSettingManager: this.props.unitsSettingManager, declutter: this.option3Declutter }),
                msfsSdk.FSComponent.buildComponent("div", { class: 'wind-no-data', style: this.noDataStyle }, "NO WIND DATA")));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c, _d, _e, _f, _g;
            (_a = this.option1Ref.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.option2Ref.getOrDefault()) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.option3Ref.getOrDefault()) === null || _c === void 0 ? void 0 : _c.destroy();
            (_d = this.declutterSub) === null || _d === void 0 ? void 0 : _d.destroy();
            (_e = this.isDeadReckoningSub) === null || _e === void 0 ? void 0 : _e.destroy();
            (_f = this.isDataFailedSub) === null || _f === void 0 ? void 0 : _f.destroy();
            (_g = this.optionSub) === null || _g === void 0 ? void 0 : _g.destroy();
            super.destroy();
        }
    }
    /**
     * An option 1 display for a next-generation (NXi, G3000, etc) Garmin PFD wind display.
     */
    class WindOption1 extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.rootStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.topArrowStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.bottomArrowStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.leftArrowStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.rightArrowStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.headwindRounded = this.props.dataProvider.headwind.map(msfsSdk.SubscribableMapFunctions.withPrecision(1));
            this.crosswindRounded = this.props.dataProvider.crosswind.map(msfsSdk.SubscribableMapFunctions.withPrecision(1));
            this.headwindSign = this.headwindRounded.map(headwind => Math.sign(headwind));
            this.crosswindSign = this.crosswindRounded.map(crosswind => Math.sign(crosswind));
            this.headwindText = this.headwindRounded.map(msfsSdk.SubscribableMapFunctions.abs());
            this.crosswindText = this.crosswindRounded.map(msfsSdk.SubscribableMapFunctions.abs());
        }
        /** @inheritdoc */
        onAfterRender() {
            this.headwindSign.sub(headwindSign => {
                this.bottomArrowStyle.set('display', headwindSign > 0 ? '' : 'none');
                this.topArrowStyle.set('display', headwindSign < 0 ? '' : 'none');
            }, true);
            this.crosswindSign.sub(crosswindSign => {
                this.leftArrowStyle.set('display', crosswindSign > 0 ? '' : 'none');
                this.rightArrowStyle.set('display', crosswindSign < 0 ? '' : 'none');
            }, true);
            this.declutterSub = this.props.declutter.sub(declutter => {
                if (declutter) {
                    this.rootStyle.set('display', 'none');
                    this.headwindRounded.pause();
                    this.crosswindRounded.pause();
                }
                else {
                    this.rootStyle.set('display', '');
                    this.headwindRounded.resume();
                    this.crosswindRounded.resume();
                }
            }, true);
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: 'wind-option1', style: this.rootStyle },
                msfsSdk.FSComponent.buildComponent("div", { class: 'wind-option1-arrows' },
                    msfsSdk.FSComponent.buildComponent("svg", { viewBox: '-13 -13 26 26', class: 'wind-option1-stem' },
                        msfsSdk.FSComponent.buildComponent("rect", { x: '-10', y: '-1', width: '20', height: '2' }),
                        msfsSdk.FSComponent.buildComponent("rect", { x: '-1', y: '-10', width: '2', height: '20' })),
                    msfsSdk.FSComponent.buildComponent("svg", { viewBox: '-13 -13 26 26', class: 'wind-option1-arrow wind-option1-arrow-left', style: this.leftArrowStyle },
                        msfsSdk.FSComponent.buildComponent("path", { d: 'M -12 0 l 5 -5 l 0 10 z' })),
                    msfsSdk.FSComponent.buildComponent("svg", { viewBox: '-13 -13 26 26', class: 'wind-option1-arrow wind-option1-arrow-top', style: this.topArrowStyle },
                        msfsSdk.FSComponent.buildComponent("path", { d: 'M 0 -12 l -5 5 l 10 0 z' })),
                    msfsSdk.FSComponent.buildComponent("svg", { viewBox: '-13 -13 26 26', class: 'wind-option1-arrow wind-option1-arrow-right', style: this.rightArrowStyle },
                        msfsSdk.FSComponent.buildComponent("path", { d: 'M 12 0 l -5 5 l 0 -10 z' })),
                    msfsSdk.FSComponent.buildComponent("svg", { viewBox: '-13 -13 26 26', class: 'wind-option1-arrow wind-option1-arrow-bottom', style: this.bottomArrowStyle },
                        msfsSdk.FSComponent.buildComponent("path", { d: 'M 0 12 l -5 -5 l 10 0 z' }))),
                msfsSdk.FSComponent.buildComponent("div", { class: 'wind-option1-crosswind' }, this.crosswindText),
                msfsSdk.FSComponent.buildComponent("div", { class: 'wind-option1-headwind' }, this.headwindText)));
        }
        /** @inheritdoc */
        destroy() {
            var _a;
            this.headwindRounded.destroy();
            this.crosswindRounded.destroy();
            (_a = this.declutterSub) === null || _a === void 0 ? void 0 : _a.destroy();
            super.destroy();
        }
    }
    /**
     * An option 2 display for a next-generation (NXi, G3000, etc) Garmin PFD wind display.
     */
    class WindOption2 extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.rootStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.arrowStyle = msfsSdk.ObjectSubject.create({
                transform: 'rotate3d(0, 0, 1, 0deg)'
            });
            this.windDirectionRounded = this.props.dataProvider.windDirectionRelative.map(msfsSdk.SubscribableMapFunctions.withPrecision(0.1));
            this.windSpeedRounded = this.props.dataProvider.windSpeed.map(msfsSdk.SubscribableMapFunctions.withPrecision(1));
        }
        /** @inheritdoc */
        onAfterRender() {
            this.windDirectionRounded.sub(direction => {
                this.arrowStyle.set('transform', `rotate3d(0, 0, 1, ${direction}deg)`);
            }, true);
            this.declutterSub = this.props.declutter.sub(declutter => {
                if (declutter) {
                    this.rootStyle.set('display', 'none');
                    this.windDirectionRounded.pause();
                    this.windSpeedRounded.pause();
                }
                else {
                    this.rootStyle.set('display', '');
                    this.windDirectionRounded.resume();
                    this.windSpeedRounded.resume();
                }
            }, true);
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: 'wind-option2', style: this.rootStyle },
                msfsSdk.FSComponent.buildComponent("svg", { viewBox: '-11.5 -11.5 23 23', class: 'wind-option2-arrow', style: this.arrowStyle },
                    msfsSdk.FSComponent.buildComponent("rect", { x: '-1', y: '-10.5', width: '2', height: '17.5' }),
                    msfsSdk.FSComponent.buildComponent("path", { d: 'M 5 5.5 l -5 5 l -5 -5 z' })),
                msfsSdk.FSComponent.buildComponent("div", { class: 'wind-option2-speed' }, this.windSpeedRounded)));
        }
        /** @inheritdoc */
        destroy() {
            var _a;
            this.windDirectionRounded.destroy();
            this.windSpeedRounded.destroy();
            (_a = this.declutterSub) === null || _a === void 0 ? void 0 : _a.destroy();
            super.destroy();
        }
    }
    /**
     * An option 3 display for a next-generation (NXi, G3000, etc) Garmin PFD wind display.
     */
    class WindOption3 extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.bearingRef = msfsSdk.FSComponent.createRef();
            this.speedRef = msfsSdk.FSComponent.createRef();
            this.rootStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.arrowStyle = msfsSdk.ObjectSubject.create({
                transform: 'rotate3d(0, 0, 1, 0deg)'
            });
            this.windDirectionRelRounded = this.props.dataProvider.windDirectionRelative.map(msfsSdk.SubscribableMapFunctions.withPrecision(0.1));
            this.windBearingState = msfsSdk.CombinedSubject.create(this.props.dataProvider.windDirection, this.props.dataProvider.magVar);
            this.windBearing = msfsSdk.BasicNavAngleSubject.create(msfsSdk.BasicNavAngleUnit.create(false).createNumber(0));
            this.windSpeed = msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.KNOT.createNumber(0));
        }
        /** @inheritdoc */
        onAfterRender() {
            this.windBearingState.sub(([bearing, magVar]) => {
                this.windBearing.set(msfsSdk.MathUtils.round(bearing, 0.5), msfsSdk.MathUtils.round(magVar, 0.5));
            }, true);
            const windSpeedPipe = this.windSpeedPipe = this.props.dataProvider.windSpeed.pipe(this.windSpeed, msfsSdk.SubscribableMapFunctions.withPrecision(1), true);
            this.windDirectionRelRounded.sub(direction => {
                this.arrowStyle.set('transform', `rotate3d(0, 0, 1, ${direction}deg)`);
            }, true);
            this.declutterSub = this.props.declutter.sub(declutter => {
                if (declutter) {
                    this.rootStyle.set('display', 'none');
                    this.windBearingState.pause();
                    this.windDirectionRelRounded.pause();
                    windSpeedPipe.pause();
                }
                else {
                    this.rootStyle.set('display', '');
                    this.windBearingState.resume();
                    this.windDirectionRelRounded.resume();
                    windSpeedPipe.resume(true);
                }
            }, true);
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: 'wind-option3', style: this.rootStyle },
                msfsSdk.FSComponent.buildComponent("svg", { viewBox: '-11.5 -11.5 23 23', class: 'wind-option3-arrow', style: this.arrowStyle },
                    msfsSdk.FSComponent.buildComponent("rect", { x: '-1', y: '-10.5', width: '2', height: '17.5' }),
                    msfsSdk.FSComponent.buildComponent("path", { d: 'M 5 5.5 l -5 5 l -5 -5 z' })),
                msfsSdk.FSComponent.buildComponent(BearingDisplay, { ref: this.bearingRef, value: this.windBearing, displayUnit: this.props.unitsSettingManager.navAngleUnits, formatter: WindOption3.BEARING_FORMATTER, class: 'wind-option3-direction' }),
                msfsSdk.FSComponent.buildComponent(NumberUnitDisplay, { ref: this.speedRef, value: this.windSpeed, displayUnit: null, formatter: WindOption3.SPEED_FORMATTER, class: 'wind-option3-speed' })));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c, _d;
            (_a = this.bearingRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.speedRef.getOrDefault()) === null || _b === void 0 ? void 0 : _b.destroy();
            this.windDirectionRelRounded.destroy();
            this.windBearingState.destroy();
            (_c = this.declutterSub) === null || _c === void 0 ? void 0 : _c.destroy();
            (_d = this.windSpeedPipe) === null || _d === void 0 ? void 0 : _d.destroy();
            super.destroy();
        }
    }
    WindOption3.BEARING_FORMATTER = msfsSdk.NumberFormatter.create({ precision: 1, pad: 3 });
    WindOption3.SPEED_FORMATTER = msfsSdk.NumberFormatter.create({});

    /**
     * A manager for the PFD declutter feature. Keeps track of whether the PFD should be decluttered due to unusual
     * airplane attitudes and exposes that information as a subscribable through the `declutter` property.
     */
    class PfdDeclutterManager {
        /**
         * Constructor.
         * @param bus The event bus.
         * @param ahrsIndex The index of the AHRS that is the source of the attitude data used by this manager.
         * @param pitchUpThreshold The pitch up threshold for declutter, in degrees. Defaults to 30 degrees.
         * @param pitchDownThreshold The pitch down threshold for declutter, in degrees. Defaults to -20 degrees.
         * @param rollThreshold The roll threshold for declutter, in degrees, in either direction. Defaults to 65 degrees.
         * @param pitchUpHysteresis The hysteresis to apply for the pitch up threshold, in degrees. Defaults to 5 degrees.
         * @param pitchDownHysteresis The hysteresis to apply for the pitch down threshold, in degrees. Defaults to 5
         * degrees.
         * @param rollHysteresis The hysteresis to apply for the roll threshold, in degrees. Defaults to 5 degrees.
         */
        constructor(bus, ahrsIndex, pitchUpThreshold = 30, pitchDownThreshold = -20, rollThreshold = 65, pitchUpHysteresis = 5, pitchDownHysteresis = 5, rollHysteresis = 5) {
            this.bus = bus;
            this._declutter = msfsSdk.Subject.create(false);
            /** Whether the PFD should be decluttered. */
            this.declutter = this._declutter;
            this.pitch = msfsSdk.ConsumerSubject.create(null, 0);
            this.roll = msfsSdk.ConsumerSubject.create(null, 0);
            this.isAttitudeDataValid = msfsSdk.ConsumerSubject.create(null, true);
            this.isPitchUpOob = false;
            this.isPitchDownOob = false;
            this.isRollOob = false;
            this.isAlive = true;
            this.isInit = false;
            this.ahrsIndex = msfsSdk.SubscribableUtils.toSubscribable(ahrsIndex, true);
            this.pitchUpThreshold = msfsSdk.SubscribableUtils.toSubscribable(pitchUpThreshold, true);
            this.pitchDownThreshold = msfsSdk.SubscribableUtils.toSubscribable(pitchDownThreshold, true);
            this.rollThreshold = msfsSdk.SubscribableUtils.toSubscribable(rollThreshold, true);
            this.pitchUpHysteresis = msfsSdk.SubscribableUtils.toSubscribable(pitchUpHysteresis, true);
            this.pitchDownHysteresis = msfsSdk.SubscribableUtils.toSubscribable(pitchDownHysteresis, true);
            this.rollHysteresis = msfsSdk.SubscribableUtils.toSubscribable(rollHysteresis, true);
        }
        /**
         * Initializes this manager. Once initialized, this manager will automatically keep track of whether the PFD should
         * be decluttered until it is destroyed.
         * @throws Error if this manager has been destroyed.
         */
        init() {
            if (!this.isAlive) {
                throw new Error('PfdDeclutterManager: cannot initialize a dead manager');
            }
            if (this.isInit) {
                return;
            }
            this.isInit = true;
            const sub = this.bus.getSubscriber();
            this.ahrsIndexSub = this.ahrsIndex.sub(index => {
                this.pitch.setConsumer(sub.on(`ahrs_pitch_deg_${index}`));
                this.roll.setConsumer(sub.on(`ahrs_roll_deg_${index}`));
                this.isAttitudeDataValid.setConsumer(sub.on(`ahrs_attitude_data_valid_${index}`));
            }, true);
            this.clockSub = sub.on('realTime').handle(this.update.bind(this));
        }
        /**
         * Updates this manager.
         */
        update() {
            if (this.isAttitudeDataValid.get()) {
                const pitch = -this.pitch.get();
                const roll = Math.abs(this.roll.get());
                const pitchUpThreshold = this.pitchUpThreshold.get() - (this.isPitchUpOob ? this.pitchUpHysteresis.get() : 0);
                const pitchDownThreshold = this.pitchDownThreshold.get() + (this.isPitchDownOob ? this.pitchDownHysteresis.get() : 0);
                const rollThreshold = this.rollThreshold.get() - (this.isRollOob ? this.rollHysteresis.get() : 0);
                this.isPitchUpOob = pitch > pitchUpThreshold;
                this.isPitchDownOob = pitch < pitchDownThreshold;
                this.isRollOob = roll > rollThreshold;
                this._declutter.set(this.isPitchUpOob || this.isPitchDownOob || this.isRollOob);
            }
            else {
                this.isPitchUpOob = false;
                this.isPitchDownOob = false;
                this.isRollOob = false;
                this._declutter.set(false);
            }
        }
        /**
         * Destroys this manager.
         */
        destroy() {
            var _a, _b;
            this.isAlive = false;
            this.pitch.destroy();
            this.roll.destroy();
            this.isAttitudeDataValid.destroy();
            (_a = this.clockSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.ahrsIndexSub) === null || _b === void 0 ? void 0 : _b.destroy();
        }
    }

    /**
     * Garmin TAWS status flags.
     */
    exports.GarminTawsStatus = void 0;
    (function (GarminTawsStatus) {
        GarminTawsStatus["TawsFailed"] = "TawsFailed";
        GarminTawsStatus["TawsNotAvailable"] = "TawsNotAvailable";
        GarminTawsStatus["GpwsFailed"] = "TawsGpwsFailed";
    })(exports.GarminTawsStatus || (exports.GarminTawsStatus = {}));
    /**
     * Garmin TAWS inhibit flags.
     */
    exports.GarminTawsInhibit = void 0;
    (function (GarminTawsInhibit) {
        /** FLTA and PDA alerts inhibited. */
        GarminTawsInhibit["FltaPda"] = "TawsFltaPda";
        /** GPWS alerts (EDR, ECR, FIT, NCR) inhibited. */
        GarminTawsInhibit["Gpws"] = "TawsGpws";
        /** FIT alerts based on flap position inhibited. */
        GarminTawsInhibit["FitFlaps"] = "TawsFitFlaps";
        /** GSD glideslope alerts inhibited. */
        GarminTawsInhibit["GsdGlideslope"] = "TawsGsdGlideslope";
        /** GSD glidepath alerts inhibited. */
        GarminTawsInhibit["GsdGlidepath"] = "TawsGsdGlidepath";
    })(exports.GarminTawsInhibit || (exports.GarminTawsInhibit = {}));
    /**
     * Garmin TAWS alerts.
     */
    exports.GarminTawsAlert = void 0;
    (function (GarminTawsAlert) {
        GarminTawsAlert["RtcWarning"] = "TawsRtcWarning";
        GarminTawsAlert["RtcCaution"] = "TawsRtcCaution";
        GarminTawsAlert["ItiWarning"] = "TawsItiWarning";
        GarminTawsAlert["ItiCaution"] = "TawsItiCaution";
        GarminTawsAlert["RocWarning"] = "TawsRocWarning";
        GarminTawsAlert["RocCaution"] = "TawsRocCaution";
        GarminTawsAlert["IoiWarning"] = "TawsIoiWarning";
        GarminTawsAlert["IoiCaution"] = "TawsIoiCaution";
        GarminTawsAlert["PdaCaution"] = "TawsPdaCaution";
        GarminTawsAlert["EdrWarning"] = "TawsEdrWarning";
        GarminTawsAlert["EdrCaution"] = "TawsEdrCaution";
        GarminTawsAlert["EcrWarning"] = "TawsEcrWarning";
        GarminTawsAlert["EcrCaution"] = "TawsEcrCaution";
        GarminTawsAlert["FitTerrainCaution"] = "TawsFitTerrainCaution";
        GarminTawsAlert["FitGearCaution"] = "TawsFitGearCaution";
        GarminTawsAlert["FitFlapsCaution"] = "TawsFitFlapsCaution";
        GarminTawsAlert["FitTakeoffCaution"] = "TawsFitTakeoffCaution";
        GarminTawsAlert["NcrCaution"] = "TawsNcrCaution";
        GarminTawsAlert["GsdGlideslopeCaution"] = "TawsGsdGlideslopeCaution";
        GarminTawsAlert["GsdGlidepathCaution"] = "TawsGsdGlidepathCaution";
        GarminTawsAlert["Vco500"] = "TawsVco500";
        GarminTawsAlert["Vco450"] = "TawsVco450";
        GarminTawsAlert["Vco400"] = "TawsVco400";
        GarminTawsAlert["Vco350"] = "TawsVco350";
        GarminTawsAlert["Vco300"] = "TawsVco300";
        GarminTawsAlert["Vco250"] = "TawsVco250";
        GarminTawsAlert["Vco200"] = "TawsVco200";
        GarminTawsAlert["Vco150"] = "TawsVco150";
        GarminTawsAlert["Vco100"] = "TawsVco100";
        GarminTawsAlert["Vco50"] = "TawsVco50";
        GarminTawsAlert["Vco40"] = "TawsVco40";
        GarminTawsAlert["Vco30"] = "TawsVco30";
        GarminTawsAlert["Vco20"] = "TawsVco20";
        GarminTawsAlert["Vco10"] = "TawsVco10";
    })(exports.GarminTawsAlert || (exports.GarminTawsAlert = {}));

    /**
     * Types of Garmin terrain alerting systems.
     */
    exports.TerrainSystemType = void 0;
    (function (TerrainSystemType) {
        TerrainSystemType["Svt"] = "Svt";
        TerrainSystemType["TawsA"] = "TawsA";
        TerrainSystemType["TawsB"] = "TawsB";
    })(exports.TerrainSystemType || (exports.TerrainSystemType = {}));
    /**
     * Garmin terrain alerting system operating modes.
     */
    exports.TerrainSystemOperatingMode = void 0;
    (function (TerrainSystemOperatingMode) {
        TerrainSystemOperatingMode["Off"] = "Off";
        TerrainSystemOperatingMode["Operating"] = "Operating";
        TerrainSystemOperatingMode["Test"] = "Test";
    })(exports.TerrainSystemOperatingMode || (exports.TerrainSystemOperatingMode = {}));

    /**
     * Terrain alerting system annunciation levels.
     */
    exports.TerrainSystemAnnunciationLevel = void 0;
    (function (TerrainSystemAnnunciationLevel) {
        TerrainSystemAnnunciationLevel[TerrainSystemAnnunciationLevel["Advisory"] = 0] = "Advisory";
        TerrainSystemAnnunciationLevel[TerrainSystemAnnunciationLevel["Caution"] = 1] = "Caution";
        TerrainSystemAnnunciationLevel[TerrainSystemAnnunciationLevel["Warning"] = 2] = "Warning";
    })(exports.TerrainSystemAnnunciationLevel || (exports.TerrainSystemAnnunciationLevel = {}));
    /**
     * A Garmin terrain alerting system annunciation display.
     */
    class TerrainSystemAnnunciation extends msfsSdk.DisplayComponent {
        constructor() {
            var _a, _b, _c, _d;
            super(...arguments);
            this.rootCssClass = msfsSdk.SetSubject.create();
            this.show = msfsSdk.SubscribableUtils.toSubscribable(this.props.show, true);
            this.display = msfsSdk.Subject.create('none');
            this.text = msfsSdk.Subject.create('');
            this.flashDuration = (_a = this.props.flashDuration) !== null && _a !== void 0 ? _a : 5000;
            this.flashDebounceTimer = new msfsSdk.DebounceTimer();
            this.cancelFlashFunc = this.rootCssClass.delete.bind(this.rootCssClass, 'terrain-annunc-flash');
            this.statusDefs = (_b = this.props.statusDefs) !== null && _b !== void 0 ? _b : {};
            this.inhibitDefs = (_c = this.props.inhibitDefs) !== null && _c !== void 0 ? _c : {};
            this.alertDefs = (_d = this.props.alertDefs) !== null && _d !== void 0 ? _d : {};
            this.subscriptions = [];
        }
        /** @inheritDoc */
        onAfterRender() {
            const updateDisplay = this.updateDisplay.bind(this);
            const displaySubs = [
                this.props.operatingMode.sub(updateDisplay, false, true),
                this.props.statusFlags.sub(updateDisplay, false, true),
                this.props.inhibitFlags.sub(updateDisplay, false, true),
                this.props.prioritizedAlert.sub(updateDisplay, false, true)
            ];
            this.subscriptions.push(...displaySubs);
            this.subscriptions.push(this.show.sub(show => {
                if (show) {
                    updateDisplay();
                    for (const sub of displaySubs) {
                        sub.resume();
                    }
                }
                else {
                    for (const sub of displaySubs) {
                        sub.pause();
                    }
                    this.display.set('none');
                }
            }, true));
        }
        /**
         * Updates the annunciation shown by this display.
         */
        updateDisplay() {
            let activeDef = undefined;
            const operatingMode = this.props.operatingMode.get();
            if (operatingMode !== exports.TerrainSystemOperatingMode.Off) {
                // Test is a special case: if an annunciation is defined, then it takes priority over everything else (even
                // annunciations of higher levels).
                const testDef = operatingMode === exports.TerrainSystemOperatingMode.Test ? this.props.testModeDef : undefined;
                if (testDef) {
                    activeDef = testDef;
                }
                else {
                    // Alerts have priority over everything else.
                    const alert = this.props.prioritizedAlert.get();
                    const alertDef = alert ? this.alertDefs[alert] : undefined;
                    if (alertDef) {
                        activeDef = alertDef;
                    }
                    else {
                        // Get definitions for all active status and inhibit flags and pick the highest level and priority.
                        let bestDef = undefined;
                        for (const status of this.props.statusFlags.get()) {
                            const def = this.statusDefs[status];
                            if (def && (bestDef === undefined || def.level > bestDef.level || (def.level === bestDef.level && def.priority > bestDef.priority))) {
                                bestDef = def;
                            }
                        }
                        for (const inhibit of this.props.inhibitFlags.get()) {
                            const def = this.inhibitDefs[inhibit];
                            if (def && (bestDef === undefined || def.level > bestDef.level || (def.level === bestDef.level && def.priority > bestDef.priority))) {
                                bestDef = def;
                            }
                        }
                        activeDef = bestDef;
                    }
                }
            }
            if (activeDef !== this.activeDef) {
                this.activeDef = activeDef;
                if (activeDef) {
                    this.display.set('');
                    this.setAnnunciationLevel(activeDef.level);
                    this.text.set(activeDef.text);
                    this.startFlash();
                }
                else {
                    this.display.set('none');
                    this.setAnnunciationLevel(exports.TerrainSystemAnnunciationLevel.Advisory);
                    this.text.set('');
                    this.stopFlash();
                    return;
                }
            }
        }
        /**
         * Sets this display's current annunciation level.
         * @param level The annunciation level to set.
         */
        setAnnunciationLevel(level) {
            this.rootCssClass.toggle('terrain-annunc-advisory', level === exports.TerrainSystemAnnunciationLevel.Advisory);
            this.rootCssClass.toggle('terrain-annunc-caution', level === exports.TerrainSystemAnnunciationLevel.Caution);
            this.rootCssClass.toggle('terrain-annunc-warning', level === exports.TerrainSystemAnnunciationLevel.Warning);
        }
        /**
         * Starts a flashing cycle on this display. The display immediately enters the flashing state and will automatically
         * exit the flashing state after an amount of time equal to this display's flashing duration.
         */
        startFlash() {
            this.rootCssClass.add('terrain-annunc-flash');
            this.flashDebounceTimer.schedule(this.cancelFlashFunc, this.flashDuration);
        }
        /**
         * Stops any currently active flashing cycle on this display. The display immediately exits the flashing state.
         */
        stopFlash() {
            this.flashDebounceTimer.clear();
            this.cancelFlashFunc();
        }
        /** @inheritDoc */
        render() {
            this.rootCssClass.add('terrain-annunc');
            if (typeof this.props.class === 'object') {
                const sub = msfsSdk.FSComponent.bindCssClassSet(this.rootCssClass, this.props.class, TerrainSystemAnnunciation.RESERVED_CLASSES);
                if (Array.isArray(sub)) {
                    this.subscriptions.push(...sub);
                }
                else {
                    this.subscriptions.push(sub);
                }
            }
            else if (this.props.class) {
                for (const classToAdd of msfsSdk.FSComponent.parseCssClassesFromString(this.props.class, classToFilter => !TerrainSystemAnnunciation.RESERVED_CLASSES.includes(classToFilter))) {
                    this.rootCssClass.add(classToAdd);
                }
            }
            return (msfsSdk.FSComponent.buildComponent("div", { class: this.rootCssClass, style: { 'display': this.display } }, this.text));
        }
        /** @inheritDoc */
        destroy() {
            this.flashDebounceTimer.clear();
            for (const sub of this.subscriptions) {
                sub.destroy();
            }
            super.destroy();
        }
    }
    TerrainSystemAnnunciation.RESERVED_CLASSES = [
        'terrain-annunc',
        'terrain-annunc-advisory',
        'terrain-annunc-caution',
        'terrain-annunc-warning',
        'terrain-annunc-flash'
    ];

    /**
     * A utility class for generating definitions for next-generation (NXi, G3000, etc) Garmin TAWS annunciations.
     */
    class NextGenTawsAnnunciationDefs {
        /**
         * Creates a new definition for test mode.
         * @returns A new definition for test mode.
         */
        static testMode() {
            return {
                level: exports.TerrainSystemAnnunciationLevel.Advisory,
                text: 'TAWS TEST'
            };
        }
        /**
         * Creates a new record of definitions for status flags, keyed by flag.
         * @returns A new record of definitions for status flags, keyed by flag.
         */
        static status() {
            return {
                [exports.GarminTawsStatus.TawsFailed]: {
                    level: exports.TerrainSystemAnnunciationLevel.Caution,
                    priority: 0,
                    text: 'TAWS FAIL'
                },
                [exports.GarminTawsStatus.GpwsFailed]: {
                    level: exports.TerrainSystemAnnunciationLevel.Caution,
                    priority: -10,
                    text: 'GPWS FAIL'
                },
                [exports.GarminTawsStatus.TawsNotAvailable]: {
                    level: exports.TerrainSystemAnnunciationLevel.Caution,
                    priority: -20,
                    text: 'TAWS N/A'
                }
            };
        }
        /**
         * Creates a new record of definitions for inhibit flags, keyed by flag.
         * @returns A new record of definitions for inhibit flags, keyed by flag.
         */
        static inhibit() {
            return {
                [exports.GarminTawsInhibit.FltaPda]: {
                    level: exports.TerrainSystemAnnunciationLevel.Advisory,
                    priority: 0,
                    text: 'TAWS INH'
                },
                [exports.GarminTawsInhibit.Gpws]: {
                    level: exports.TerrainSystemAnnunciationLevel.Advisory,
                    priority: -10,
                    text: 'GPWS INH'
                },
                [exports.GarminTawsInhibit.FitFlaps]: {
                    level: exports.TerrainSystemAnnunciationLevel.Advisory,
                    priority: -20,
                    text: 'FLAP OVR'
                },
                [exports.GarminTawsInhibit.GsdGlideslope]: {
                    level: exports.TerrainSystemAnnunciationLevel.Advisory,
                    priority: -30,
                    text: 'GS INH'
                },
                [exports.GarminTawsInhibit.GsdGlidepath]: {
                    level: exports.TerrainSystemAnnunciationLevel.Advisory,
                    priority: -30,
                    text: 'GP INH'
                }
            };
        }
        /**
         * Creates a new record of definitions for alerts, keyed by alert.
         * @returns A new record of definitions for alerts, keyed by alert.
         */
        static alert() {
            return {
                [exports.GarminTawsAlert.RtcWarning]: {
                    level: exports.TerrainSystemAnnunciationLevel.Warning,
                    text: 'PULL UP'
                },
                [exports.GarminTawsAlert.ItiWarning]: {
                    level: exports.TerrainSystemAnnunciationLevel.Warning,
                    text: 'PULL UP'
                },
                [exports.GarminTawsAlert.RocWarning]: {
                    level: exports.TerrainSystemAnnunciationLevel.Warning,
                    text: 'PULL UP'
                },
                [exports.GarminTawsAlert.IoiWarning]: {
                    level: exports.TerrainSystemAnnunciationLevel.Warning,
                    text: 'PULL UP'
                },
                [exports.GarminTawsAlert.EdrWarning]: {
                    level: exports.TerrainSystemAnnunciationLevel.Warning,
                    text: 'PULL UP'
                },
                [exports.GarminTawsAlert.EcrWarning]: {
                    level: exports.TerrainSystemAnnunciationLevel.Warning,
                    text: 'PULL UP'
                },
                [exports.GarminTawsAlert.RtcCaution]: {
                    level: exports.TerrainSystemAnnunciationLevel.Caution,
                    text: 'TERRAIN'
                },
                [exports.GarminTawsAlert.ItiCaution]: {
                    level: exports.TerrainSystemAnnunciationLevel.Caution,
                    text: 'TERRAIN'
                },
                [exports.GarminTawsAlert.RocCaution]: {
                    level: exports.TerrainSystemAnnunciationLevel.Caution,
                    text: 'TERRAIN'
                },
                [exports.GarminTawsAlert.IoiCaution]: {
                    level: exports.TerrainSystemAnnunciationLevel.Caution,
                    text: 'TERRAIN'
                },
                [exports.GarminTawsAlert.PdaCaution]: {
                    level: exports.TerrainSystemAnnunciationLevel.Caution,
                    text: 'TERRAIN'
                },
                [exports.GarminTawsAlert.EdrCaution]: {
                    level: exports.TerrainSystemAnnunciationLevel.Caution,
                    text: 'TERRAIN'
                },
                [exports.GarminTawsAlert.EcrCaution]: {
                    level: exports.TerrainSystemAnnunciationLevel.Caution,
                    text: 'TERRAIN'
                },
                [exports.GarminTawsAlert.FitTerrainCaution]: {
                    level: exports.TerrainSystemAnnunciationLevel.Caution,
                    text: 'TERRAIN'
                },
                [exports.GarminTawsAlert.FitGearCaution]: {
                    level: exports.TerrainSystemAnnunciationLevel.Caution,
                    text: 'TERRAIN'
                },
                [exports.GarminTawsAlert.FitFlapsCaution]: {
                    level: exports.TerrainSystemAnnunciationLevel.Caution,
                    text: 'TERRAIN'
                },
                [exports.GarminTawsAlert.FitTakeoffCaution]: {
                    level: exports.TerrainSystemAnnunciationLevel.Caution,
                    text: 'TERRAIN'
                },
                [exports.GarminTawsAlert.NcrCaution]: {
                    level: exports.TerrainSystemAnnunciationLevel.Caution,
                    text: 'TERRAIN'
                },
                [exports.GarminTawsAlert.GsdGlideslopeCaution]: {
                    level: exports.TerrainSystemAnnunciationLevel.Caution,
                    text: 'GLIDESLOPE'
                },
                [exports.GarminTawsAlert.GsdGlidepathCaution]: {
                    level: exports.TerrainSystemAnnunciationLevel.Caution,
                    text: 'GLIDEPATH'
                },
            };
        }
    }

    /**
     * Actions that {@link TouchButton} can take in response to being touched.
     */
    exports.TouchButtonOnTouchedAction = void 0;
    (function (TouchButtonOnTouchedAction) {
        /**
         * The button becomes primed. A primed button will be pressed if and when the mouse button is released. If the mouse
         * leaves the button before the mouse button is released, the button becomes un-primed and is not pressed.
         */
        TouchButtonOnTouchedAction["Prime"] = "Prime";
        /**
         * The button is immediately pressed once. The button does not enter the primed state. Holding down the mouse button
         * will not trigger additional presses.
         */
        TouchButtonOnTouchedAction["Press"] = "Press";
        /**
         * The button becomes held. The button will remain held until the mouse button is released, the mouse leaves the
         * button, mouse events are inhibited by dragging, or the button becomes disabled.
         */
        TouchButtonOnTouchedAction["Hold"] = "Hold";
        /** The button takes no action as if it were disabled. */
        TouchButtonOnTouchedAction["Ignore"] = "Ignore";
    })(exports.TouchButtonOnTouchedAction || (exports.TouchButtonOnTouchedAction = {}));
    /**
     * Actions that {@link TouchButton} can take while it is in the held state.
     */
    exports.TouchButtonHoldAction = void 0;
    (function (TouchButtonHoldAction) {
        /** The button is immediately pressed once. */
        TouchButtonHoldAction["Press"] = "Press";
        /** The button ends its held state. */
        TouchButtonHoldAction["EndHold"] = "EndHold";
        /** The button takes no specific action. */
        TouchButtonHoldAction["None"] = "None";
    })(exports.TouchButtonHoldAction || (exports.TouchButtonHoldAction = {}));
    /**
     * Reasons for ending a {@link TouchButton}'s held state.
     */
    exports.TouchButtonHoldEndReason = void 0;
    (function (TouchButtonHoldEndReason) {
        /** The held state ended for an unknown reason. */
        TouchButtonHoldEndReason["Unknown"] = "Unknown";
        /** The held state ended because the mouse button was released. */
        TouchButtonHoldEndReason["MouseUp"] = "MouseUp";
        /** The held state ended because the mouse left the button.  */
        TouchButtonHoldEndReason["MouseLeave"] = "MouseLeave";
        /** The held state ended as a result of a hold tick action. */
        TouchButtonHoldEndReason["TickAction"] = "TickAction";
        /** The held state ended because mouse events were inhibited by dragging. */
        TouchButtonHoldEndReason["DragInhibit"] = "DragInhibit";
        /** The held state ended because the button entered the primed state. */
        TouchButtonHoldEndReason["Primed"] = "Primed";
        /** The held state ended because the button was disabled. */
        TouchButtonHoldEndReason["Disabled"] = "Disabled";
    })(exports.TouchButtonHoldEndReason || (exports.TouchButtonHoldEndReason = {}));
    /**
     * A touchscreen button.
     *
     * The root element of the button contains the `touch-button` CSS class by default. The root element also
     * conditionally contains the `touch-button-disabled`, `touch-button-primed`, and `touch-button-held` classes when the
     * button is disabled, primed, and held, respectively.
     *
     * The root element optionally contains a child label element with the CSS class `touch-button-label`.
     */
    class TouchButton extends msfsSdk.DisplayComponent {
        constructor() {
            var _a, _b, _c, _d, _e, _f, _g;
            super(...arguments);
            this.rootRef = msfsSdk.FSComponent.createRef();
            this.cssClassSet = msfsSdk.SetSubject.create(['touch-button']);
            this.mouseDownListener = this.onMouseDown.bind(this);
            this.mouseUpListener = this.onMouseUp.bind(this);
            this.mouseLeaveListener = this.onMouseLeave.bind(this);
            this.mouseMoveListener = this.onMouseMove.bind(this);
            this.isEnabled = msfsSdk.SubscribableUtils.toSubscribable((_a = this.props.isEnabled) !== null && _a !== void 0 ? _a : true, true);
            this.isHighlighted = msfsSdk.SubscribableUtils.toSubscribable((_b = this.props.isHighlighted) !== null && _b !== void 0 ? _b : false, true);
            this.isVisible = msfsSdk.SubscribableUtils.toSubscribable((_c = this.props.isVisible) !== null && _c !== void 0 ? _c : true, true);
            this.labelContent = this.props.label !== undefined && msfsSdk.SubscribableUtils.isSubscribable(this.props.label)
                ? this.props.label.map(msfsSdk.SubscribableMapFunctions.identity())
                : this.props.label;
            this.isPrimed = false;
            this.isHeld = false;
            this.holdTickInterval = null;
            this.lastHoldTickTime = undefined;
            this.totalHoldTime = 0;
            this.holdTimeSinceLastPress = 0;
            this.holdTickFunc = () => {
                var _a;
                const time = Date.now();
                const dt = time - ((_a = this.lastHoldTickTime) !== null && _a !== void 0 ? _a : time);
                this.totalHoldTime += dt;
                this.holdTimeSinceLastPress += dt;
                const action = this.props.onHoldTick
                    ? this.props.onHoldTick(this, dt, this.totalHoldTime, this.holdTimeSinceLastPress)
                    : exports.TouchButtonHoldAction.None;
                this.lastHoldTickTime = time;
                switch (action) {
                    case exports.TouchButtonHoldAction.Press:
                        this.onPressed();
                        this.holdTimeSinceLastPress = 0;
                        break;
                    case exports.TouchButtonHoldAction.EndHold:
                        this.setHeld(false, exports.TouchButtonHoldEndReason.TickAction);
                        break;
                }
            };
            this.mouseClickPosition = new Vec2();
            this.currentMousePosition = new Vec2();
            this.focusOnDrag = (_d = this.props.focusOnDrag) !== null && _d !== void 0 ? _d : false;
            this.inhibitOnDrag = (_e = this.props.inhibitOnDrag) !== null && _e !== void 0 ? _e : false;
            this.dragThresholdPxActual = (_f = this.props.dragThresholdPx) !== null && _f !== void 0 ? _f : 40;
            this.inhibitOnDragAxisActual = (_g = this.props.inhibitOnDragAxis) !== null && _g !== void 0 ? _g : 'both';
        }
        /** @inheritdoc */
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        onAfterRender(node) {
            this.isEnabledSub = this.isEnabled.sub(isEnabled => {
                if (isEnabled) {
                    this.cssClassSet.delete('touch-button-disabled');
                }
                else {
                    this.cssClassSet.add('touch-button-disabled');
                }
                if (!isEnabled) {
                    this.setPrimed(false);
                    this.setHeld(false, exports.TouchButtonHoldEndReason.Disabled);
                }
            }, true);
            this.isHighlightedSub = this.isHighlighted.sub(isHighlighted => {
                if (isHighlighted) {
                    this.cssClassSet.add('touch-button-highlight');
                }
                else {
                    this.cssClassSet.delete('touch-button-highlight');
                }
            }, true);
            this.isVisibleSub = this.isVisible.sub(isVisible => {
                if (isVisible) {
                    this.cssClassSet.delete('touch-button-hidden');
                }
                else {
                    this.cssClassSet.add('touch-button-hidden');
                }
            }, true);
            this.rootRef.instance.addEventListener('mousedown', this.mouseDownListener);
            this.rootRef.instance.addEventListener('mouseup', this.mouseUpListener);
            this.rootRef.instance.addEventListener('mouseleave', this.mouseLeaveListener);
        }
        /**
         * Gets this button's root HTML element.
         * @returns This button's root HTML element.
         * @throws Error if this button has not yet been rendered.
         */
        getRootElement() {
            return this.rootRef.instance;
        }
        /**
         * Simulates this button being pressed. This will execute the `onPressed()` callback if one is defined.
         * @param ignoreDisabled Whether to simulate the button being pressed regardless of whether the button is disabled.
         * Defaults to `false`.
         */
        simulatePressed(ignoreDisabled = false) {
            if (ignoreDisabled || this.isEnabled.get()) {
                this.onPressed();
            }
        }
        /**
         * Sets the primed state of this button.
         * @param isPrimed The new primed state.
         */
        setPrimed(isPrimed) {
            if (this.isPrimed === isPrimed) {
                return;
            }
            if (isPrimed) {
                // A button can't be primed and held at the same time.
                this.setHeld(false, exports.TouchButtonHoldEndReason.Primed);
            }
            this.isPrimed = isPrimed;
            if (isPrimed) {
                this.cssClassSet.add('touch-button-primed');
                if (this.inhibitOnDrag) {
                    this.rootRef.instance.addEventListener('mousemove', this.mouseMoveListener);
                }
            }
            else {
                this.cssClassSet.delete('touch-button-primed');
                if (this.inhibitOnDrag) {
                    this.rootRef.instance.removeEventListener('mousemove', this.mouseMoveListener);
                }
            }
        }
        /**
         * Sets the held state of this button.
         * @param isHeld The new held state.
         * @param endReason The reason that the held state is set to `false`. Ignored if {@linkcode isHeld} is `true`.
         * Defaults to {@link TouchButtonHoldEndReason.Unknown}.
         */
        setHeld(isHeld, endReason = exports.TouchButtonHoldEndReason.Unknown) {
            if (this.isHeld === isHeld) {
                return;
            }
            if (isHeld) {
                // A button can't be primed and held at the same time.
                this.setPrimed(false);
            }
            this.isHeld = isHeld;
            if (this.holdTickInterval !== null) {
                clearInterval(this.holdTickInterval);
            }
            if (isHeld) {
                this.cssClassSet.add('touch-button-held');
                if (this.inhibitOnDrag) {
                    this.rootRef.instance.addEventListener('mousemove', this.mouseMoveListener);
                }
                const action = this.props.onHoldStarted
                    ? this.props.onHoldStarted(this)
                    : exports.TouchButtonHoldAction.None;
                this.lastHoldTickTime = undefined;
                this.totalHoldTime = 0;
                this.holdTimeSinceLastPress = 0;
                this.holdTickInterval = setInterval(this.holdTickFunc, 0);
                if (action === exports.TouchButtonHoldAction.Press) {
                    this.onPressed();
                }
            }
            else {
                this.cssClassSet.delete('touch-button-held');
                if (this.inhibitOnDrag) {
                    this.rootRef.instance.removeEventListener('mousemove', this.mouseMoveListener);
                }
                this.props.onHoldEnded && this.props.onHoldEnded(this, this.totalHoldTime, endReason);
            }
        }
        /**
         * Responds to mouse down events on this button's root element.
         * @param e The mouse event.
         */
        onMouseDown(e) {
            if (!this.isEnabled.get()) {
                return;
            }
            const action = this.props.onTouched ? this.props.onTouched(this) : exports.TouchButtonOnTouchedAction.Prime;
            switch (action) {
                case exports.TouchButtonOnTouchedAction.Prime:
                    this.mouseClickPosition.x = e.clientX;
                    this.mouseClickPosition.y = e.clientY;
                    if (this.focusOnDrag) {
                        e.stopPropagation();
                    }
                    this.setPrimed(true);
                    break;
                case exports.TouchButtonOnTouchedAction.Press:
                    if (this.focusOnDrag) {
                        e.stopPropagation();
                    }
                    this.onPressed();
                    break;
                case exports.TouchButtonOnTouchedAction.Hold:
                    this.mouseClickPosition.x = e.clientX;
                    this.mouseClickPosition.y = e.clientY;
                    if (this.focusOnDrag) {
                        e.stopPropagation();
                    }
                    this.setHeld(true);
                    break;
            }
        }
        /**
         * Responds to mouse up events on this button's root element.
         */
        onMouseUp() {
            const wasPrimed = this.isPrimed;
            this.setPrimed(false);
            this.setHeld(false, exports.TouchButtonHoldEndReason.MouseUp);
            if (wasPrimed && this.isEnabled.get()) {
                this.onPressed();
            }
        }
        /**
         * Responds to mouse leave events on this button's root element.
         * @param e The mouse event.
         */
        onMouseLeave(e) {
            if (!this.isPrimed && !this.isHeld) {
                return;
            }
            this.setPrimed(false);
            this.setHeld(false, exports.TouchButtonHoldEndReason.MouseLeave);
            if (this.focusOnDrag && this.rootRef.instance.parentElement) {
                const newE = new MouseEvent('mousedown', {
                    clientX: e.clientX,
                    clientY: e.clientY,
                    bubbles: true,
                });
                this.rootRef.instance.parentElement.dispatchEvent(newE);
            }
        }
        /**
         * Handle mouse moving after clicking.
         * @param e The mouse event.
         */
        onMouseMove(e) {
            if (!this.isPrimed && !this.isHeld) {
                return;
            }
            this.currentMousePosition.x = e.clientX;
            this.currentMousePosition.y = e.clientY;
            if (this.getDragDistance() > this.dragThresholdPxActual) {
                this.setPrimed(false);
                this.setHeld(false, exports.TouchButtonHoldEndReason.DragInhibit);
                if (this.focusOnDrag && this.rootRef.instance.parentElement) {
                    const newE = new MouseEvent('mousedown', {
                        clientX: e.clientX,
                        clientY: e.clientY,
                        bubbles: true,
                    });
                    this.rootRef.instance.parentElement.dispatchEvent(newE);
                }
            }
        }
        /**
         * Get the distance that the mouse has been dragged on the correct axis.
         * @returns The distance.
         */
        getDragDistance() {
            switch (this.inhibitOnDragAxisActual) {
                case 'x': return Math.abs(this.mouseClickPosition.x - this.currentMousePosition.x);
                case 'y': return Math.abs(this.mouseClickPosition.y - this.currentMousePosition.y);
                case 'both': return this.mouseClickPosition.Distance(this.currentMousePosition);
            }
        }
        /**
         * Responds to when this button is pressed.
         */
        onPressed() {
            this.props.onPressed && this.props.onPressed(this, this.isHeld);
        }
        /** @inheritdoc */
        render() {
            const reservedClasses = this.getReservedCssClasses();
            if (typeof this.props.class === 'object') {
                this.cssClassSub = msfsSdk.FSComponent.bindCssClassSet(this.cssClassSet, this.props.class, reservedClasses);
            }
            else if (this.props.class !== undefined && this.props.class.length > 0) {
                for (const cssClassToAdd of msfsSdk.FSComponent.parseCssClassesFromString(this.props.class, cssClass => !reservedClasses.has(cssClass))) {
                    this.cssClassSet.add(cssClassToAdd);
                }
            }
            return (msfsSdk.FSComponent.buildComponent("div", { ref: this.rootRef, class: this.cssClassSet },
                this.renderLabel(),
                this.props.children));
        }
        /**
         * Renders this button's label.
         * @returns This button's rendered label, or `null` if this button does not have a label.
         */
        renderLabel() {
            if (this.labelContent === undefined) {
                return null;
            }
            return (msfsSdk.FSComponent.buildComponent("div", { class: 'touch-button-label' }, this.labelContent));
        }
        /**
         * Gets the CSS classes that are reserved for this button's root element.
         * @returns The CSS classes that are reserved for this button's root element.
         */
        getReservedCssClasses() {
            return TouchButton.RESERVED_CSS_CLASSES;
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c, _d;
            this.props.onDestroy && this.props.onDestroy();
            if (this.holdTickInterval !== null) {
                clearInterval(this.holdTickInterval);
            }
            if (this.labelContent !== undefined) {
                if (msfsSdk.SubscribableUtils.isSubscribable(this.labelContent)) {
                    this.labelContent.destroy();
                }
                else if (typeof this.labelContent === 'object') {
                    msfsSdk.FSComponent.visitNodes(this.labelContent, node => {
                        if (node.instance instanceof msfsSdk.DisplayComponent) {
                            node.instance.destroy();
                            return true;
                        }
                        return false;
                    });
                }
            }
            (_a = this.isEnabledSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.isHighlightedSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.isVisibleSub) === null || _c === void 0 ? void 0 : _c.destroy();
            (_d = this.cssClassSub) === null || _d === void 0 ? void 0 : _d.destroy();
            this.rootRef.instance.removeEventListener('mousedown', this.mouseDownListener);
            this.rootRef.instance.removeEventListener('mouseup', this.mouseUpListener);
            this.rootRef.instance.removeEventListener('mouseleave', this.mouseLeaveListener);
            this.rootRef.instance.removeEventListener('mousemove', this.mouseMoveListener);
            super.destroy();
        }
    }
    TouchButton.RESERVED_CSS_CLASSES = new Set([
        'touch-button',
        'touch-button-disabled',
        'touch-button-primed',
        'touch-button-held',
        'touch-button-highlight',
        'touch-button-hidden'
    ]);

    /**
     * A touchscreen button which displays an optional label and image.
     *
     * The root element of the button contains the `touch-button-img` CSS class by default, in addition to all
     * root-element classes used by {@link TouchButton}.
     *
     * The root element contains an optional child `<img>` element with the CSS class `touch-button-img-img` and an
     * optional label element with the CSS class `touch-button-label`.
     */
    class ImgTouchButton extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.buttonRef = msfsSdk.FSComponent.createRef();
            this.cssClassSet = msfsSdk.SetSubject.create(['touch-button-img']);
            this.imgSrc = msfsSdk.SubscribableUtils.isSubscribable(this.props.imgSrc)
                ? this.props.imgSrc.map(msfsSdk.SubscribableMapFunctions.identity())
                : undefined;
        }
        /**
         * Gets this button's root HTML element.
         * @returns This button's root HTML element.
         * @throws Error if this button has not yet been rendered.
         */
        getRootElement() {
            return this.buttonRef.instance.getRootElement();
        }
        /**
         * Simulates this button being pressed. This will execute the `onPressed()` callback if one is defined.
         * @param ignoreDisabled Whether to simulate the button being pressed regardless of whether the button is disabled.
         * Defaults to `false`.
         */
        simulatePressed(ignoreDisabled = false) {
            var _a;
            (_a = this.buttonRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.simulatePressed(ignoreDisabled);
        }
        /** @inheritdoc */
        render() {
            const reservedClasses = this.getReservedCssClasses();
            if (typeof this.props.class === 'object') {
                this.cssClassSub = msfsSdk.FSComponent.bindCssClassSet(this.cssClassSet, this.props.class, reservedClasses);
            }
            else if (this.props.class !== undefined && this.props.class.length > 0) {
                for (const cssClassToAdd of msfsSdk.FSComponent.parseCssClassesFromString(this.props.class, cssClass => !reservedClasses.has(cssClass))) {
                    this.cssClassSet.add(cssClassToAdd);
                }
            }
            return (msfsSdk.FSComponent.buildComponent(TouchButton, { ref: this.buttonRef, isEnabled: this.props.isEnabled, isHighlighted: this.props.isHighlighted, label: this.props.label, onTouched: this.props.onTouched, onPressed: this.props.onPressed, onHoldStarted: this.props.onHoldStarted, onHoldTick: this.props.onHoldTick, onHoldEnded: this.props.onHoldEnded, focusOnDrag: this.props.focusOnDrag, inhibitOnDrag: this.props.inhibitOnDrag, dragThresholdPx: this.props.dragThresholdPx, inhibitOnDragAxis: this.props.inhibitOnDragAxis, isVisible: this.props.isVisible, class: this.cssClassSet },
                this.renderImg(),
                this.props.children));
        }
        /**
         * Renders this button's image.
         * @returns This button's rendered image, or `null` if this button does not have an image.
         */
        renderImg() {
            var _a;
            const imgSrc = (_a = this.imgSrc) !== null && _a !== void 0 ? _a : this.props.imgSrc;
            if (imgSrc === undefined) {
                return null;
            }
            return (msfsSdk.FSComponent.buildComponent("img", { src: imgSrc, class: 'touch-button-img-img' }));
        }
        /**
         * Gets the CSS classes that are reserved for this button's root element.
         * @returns The CSS classes that are reserved for this button's root element.
         */
        getReservedCssClasses() {
            return ImgTouchButton.RESERVED_CSS_CLASSES;
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c;
            this.props.onDestroy && this.props.onDestroy();
            (_a = this.buttonRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.imgSrc) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.cssClassSub) === null || _c === void 0 ? void 0 : _c.destroy();
            super.destroy();
        }
    }
    ImgTouchButton.RESERVED_CSS_CLASSES = new Set(['touch-button-img']);

    /**
     * A touchscreen button which displays whether the value of a bound state is equal to a set value. By default, pressing
     * the button will apply its set value to the state. This behavior can be overridden by providing a custom callback
     * function which runs when the button is pressed.
     *
     * The root element of the button contains the `touch-button-set-value` CSS class by default, in addition to all
     * root-element classes used by {@link TouchButton}.
     *
     * The root element contains a child {@link ToggleStatusBar} component with the CSS class
     * `touch-button-set-value-status-bar` and an optional label element with the CSS class `touch-button-label`.
     */
    class SetValueTouchButton extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.buttonRef = msfsSdk.FSComponent.createRef();
            this.statusBarRef = msfsSdk.FSComponent.createRef();
            this.cssClassSet = msfsSdk.SetSubject.create(['touch-button-set-value']);
            this.setValue = msfsSdk.SubscribableUtils.toSubscribable(this.props.setValue, true);
            this.toggleState = msfsSdk.MappedSubject.create(([state, setValue]) => state === setValue, this.props.state, this.setValue);
        }
        /**
         * Gets this button's root HTML element.
         * @returns This button's root HTML element.
         * @throws Error if this button has not yet been rendered.
         */
        getRootElement() {
            return this.buttonRef.instance.getRootElement();
        }
        /**
         * Simulates this button being pressed. This will execute the `onPressed()` callback if one is defined.
         * @param ignoreDisabled Whether to simulate the button being pressed regardless of whether the button is disabled.
         * Defaults to `false`.
         */
        simulatePressed(ignoreDisabled = false) {
            var _a;
            (_a = this.buttonRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.simulatePressed(ignoreDisabled);
        }
        /**
         * Responds to when this button is touched.
         * @param button The button that was touched.
         * @returns The action to take as a result of the button being touched.
         */
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        onTouched(button) {
            return this.props.onTouched
                ? this.props.onTouched(this, this.props.state, this.setValue.get())
                : exports.TouchButtonOnTouchedAction.Prime;
        }
        /**
         * Responds to when this button is pressed.
         * @param button The button that was pressed.
         * @param isHeld Whether the button was held when it was pressed.
         */
        onPressed(button, isHeld) {
            if (this.props.onPressed) {
                this.props.onPressed(this, this.props.state, this.setValue.get(), isHeld);
            }
            else {
                this.props.state.set(this.setValue.get());
            }
        }
        /**
         * Responds to when this button enters the held state.
         * @param button The button that is held.
         * @returns The action to take. Ignored if the value is equal to {@link TouchButtonHoldAction.EndHold}.
         */
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        onHoldStarted(button) {
            return this.props.onHoldStarted
                ? this.props.onHoldStarted(this, this.props.state, this.setValue.get())
                : exports.TouchButtonHoldAction.None;
        }
        /**
         * A callback which is called every frame when this button is held.
         * @param button The button that is held.
         * @param dt The elapsed time, in milliseconds, since the previous frame.
         * @param totalTime The total amount of time, in milliseconds, that this button has been held.
         * @param timeSinceLastPress The amount of time, in milliseconds, that this button has been held since the last time
         * the button was pressed as a tick action.
         * @returns The action to take.
         */
        onHoldTick(button, dt, totalTime, timeSinceLastPress) {
            return this.props.onHoldTick
                ? this.props.onHoldTick(this, this.props.state, this.setValue.get(), dt, totalTime, timeSinceLastPress)
                : exports.TouchButtonHoldAction.None;
        }
        /**
         * Responds to when this button exits the held state.
         * @param button The button that was held.
         * @param totalHoldDuration The total amount of time, in milliseconds, that this button was held.
         * @param endReason The reason that this button exited the held state.
         */
        onHoldEnded(button, totalHoldDuration, endReason) {
            this.props.onHoldEnded && this.props.onHoldEnded(this, this.props.state, this.setValue.get(), totalHoldDuration, endReason);
        }
        /** @inheritdoc */
        render() {
            const reservedClasses = this.getReservedCssClasses();
            if (typeof this.props.class === 'object') {
                this.cssClassSub = msfsSdk.FSComponent.bindCssClassSet(this.cssClassSet, this.props.class, reservedClasses);
            }
            else if (this.props.class !== undefined && this.props.class.length > 0) {
                for (const cssClassToAdd of msfsSdk.FSComponent.parseCssClassesFromString(this.props.class, cssClass => !reservedClasses.has(cssClass))) {
                    this.cssClassSet.add(cssClassToAdd);
                }
            }
            return (msfsSdk.FSComponent.buildComponent(TouchButton, { ref: this.buttonRef, isEnabled: this.props.isEnabled, isHighlighted: this.props.isHighlighted, isVisible: this.props.isVisible, label: this.props.label, onTouched: this.onTouched.bind(this), onPressed: this.onPressed.bind(this), onHoldStarted: this.onHoldStarted.bind(this), onHoldTick: this.onHoldTick.bind(this), onHoldEnded: this.onHoldEnded.bind(this), focusOnDrag: this.props.focusOnDrag, inhibitOnDrag: this.props.inhibitOnDrag, dragThresholdPx: this.props.dragThresholdPx, inhibitOnDragAxis: this.props.inhibitOnDragAxis, class: this.cssClassSet },
                msfsSdk.FSComponent.buildComponent(ToggleStatusBar, { ref: this.statusBarRef, state: this.toggleState, class: 'touch-button-set-value-status-bar' }),
                this.props.children));
        }
        /**
         * Gets the CSS classes that are reserved for this button's root element.
         * @returns The CSS classes that are reserved for this button's root element.
         */
        getReservedCssClasses() {
            return SetValueTouchButton.RESERVED_CSS_CLASSES;
        }
        /** @inheritdoc */
        destroy() {
            super.destroy();
            this.buttonRef.instance.destroy();
            this.statusBarRef.instance.destroy();
            this.toggleState.destroy();
            this.props.onDestroy && this.props.onDestroy();
        }
    }
    SetValueTouchButton.RESERVED_CSS_CLASSES = new Set(['touch-button-set-value']);

    /**
     * A touchscreen button which displays the value of a bound boolean state. By default, pressing the button will toggle
     * its state if the state is mutable. This behavior can be overridden by providing a custom callback function which
     * runs when the button is pressed.
     *
     * The root element of the button contains the `touch-button-toggle` CSS class by default, in addition to all
     * root-element classes used by {@link TouchButton}.
     *
     * The root element contains a child {@link ToggleStatusBar} component with the CSS class
     * `touch-button-toggle-status-bar` and an optional label element with the CSS class `touch-button-label`.
     */
    class ToggleTouchButton extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.buttonRef = msfsSdk.FSComponent.createRef();
            this.statusBarRef = msfsSdk.FSComponent.createRef();
            this.cssClassSet = msfsSdk.SetSubject.create(['touch-button-toggle']);
        }
        /**
         * Gets this button's root HTML element.
         * @returns This button's root HTML element.
         * @throws Error if this button has not yet been rendered.
         */
        getRootElement() {
            return this.buttonRef.instance.getRootElement();
        }
        /**
         * Simulates this button being pressed. This will execute the `onPressed()` callback if one is defined.
         * @param ignoreDisabled Whether to simulate the button being pressed regardless of whether the button is disabled.
         * Defaults to `false`.
         */
        simulatePressed(ignoreDisabled = false) {
            var _a;
            (_a = this.buttonRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.simulatePressed(ignoreDisabled);
        }
        /**
         * Responds to when this button is touched.
         * @param button The button that was touched.
         * @returns The action to take as a result of the button being touched.
         */
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        onTouched(button) {
            return this.props.onTouched
                ? this.props.onTouched(this, this.props.state)
                : exports.TouchButtonOnTouchedAction.Prime;
        }
        /**
         * Responds to when this button is pressed.
         * @param button The button that was pressed.
         * @param isHeld Whether the button was held when it was pressed.
         */
        onPressed(button, isHeld) {
            if (this.props.onPressed) {
                this.props.onPressed(this, this.props.state, isHeld);
            }
            else if ('isMutableSubscribable' in this.props.state) {
                this.props.state.set(!this.props.state.get());
            }
        }
        /**
         * Responds to when this button enters the held state.
         * @param button The button that is held.
         * @returns The action to take. Ignored if the value is equal to {@link TouchButtonHoldAction.EndHold}.
         */
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        onHoldStarted(button) {
            return this.props.onHoldStarted
                ? this.props.onHoldStarted(this, this.props.state)
                : exports.TouchButtonHoldAction.None;
        }
        /**
         * A callback which is called every frame when this button is held.
         * @param button The button that is held.
         * @param dt The elapsed time, in milliseconds, since the previous frame.
         * @param totalTime The total amount of time, in milliseconds, that this button has been held.
         * @param timeSinceLastPress The amount of time, in milliseconds, that this button has been held since the last time
         * the button was pressed as a tick action.
         * @returns The action to take.
         */
        onHoldTick(button, dt, totalTime, timeSinceLastPress) {
            return this.props.onHoldTick
                ? this.props.onHoldTick(this, this.props.state, dt, totalTime, timeSinceLastPress)
                : exports.TouchButtonHoldAction.None;
        }
        /**
         * Responds to when this button exits the held state.
         * @param button The button that was held.
         * @param totalHoldDuration The total amount of time, in milliseconds, that this button was held.
         * @param endReason The reason that this button exited the held state.
         */
        onHoldEnded(button, totalHoldDuration, endReason) {
            this.props.onHoldEnded && this.props.onHoldEnded(this, this.props.state, totalHoldDuration, endReason);
        }
        /** @inheritdoc */
        render() {
            const reservedClasses = this.getReservedCssClasses();
            if (typeof this.props.class === 'object') {
                this.cssClassSub = msfsSdk.FSComponent.bindCssClassSet(this.cssClassSet, this.props.class, reservedClasses);
            }
            else if (this.props.class !== undefined && this.props.class.length > 0) {
                for (const cssClassToAdd of msfsSdk.FSComponent.parseCssClassesFromString(this.props.class, cssClass => !reservedClasses.has(cssClass))) {
                    this.cssClassSet.add(cssClassToAdd);
                }
            }
            return (msfsSdk.FSComponent.buildComponent(TouchButton, { ref: this.buttonRef, isEnabled: this.props.isEnabled, isHighlighted: this.props.isHighlighted, isVisible: this.props.isVisible, label: this.props.label, onTouched: this.onTouched.bind(this), onPressed: this.onPressed.bind(this), onHoldStarted: this.onHoldStarted.bind(this), onHoldTick: this.onHoldTick.bind(this), onHoldEnded: this.onHoldEnded.bind(this), focusOnDrag: this.props.focusOnDrag, inhibitOnDrag: this.props.inhibitOnDrag, dragThresholdPx: this.props.dragThresholdPx, inhibitOnDragAxis: this.props.inhibitOnDragAxis, class: this.cssClassSet },
                msfsSdk.FSComponent.buildComponent(ToggleStatusBar, { ref: this.statusBarRef, state: this.props.state, class: 'touch-button-toggle-status-bar' }),
                this.props.children));
        }
        /**
         * Gets the CSS classes that are reserved for this button's root element.
         * @returns The CSS classes that are reserved for this button's root element.
         */
        getReservedCssClasses() {
            return ToggleTouchButton.RESERVED_CSS_CLASSES;
        }
        /** @inheritdoc */
        destroy() {
            super.destroy();
            this.buttonRef.instance.destroy();
            this.statusBarRef.instance.destroy();
            this.props.onDestroy && this.props.onDestroy();
        }
    }
    ToggleTouchButton.RESERVED_CSS_CLASSES = new Set(['touch-button-toggle']);

    /**
     * A touchscreen button which displays the value of a bound state.
     *
     * The root element of the button contains the `touch-button-value` CSS class by default, in addition to all
     * root-element classes used by {@link TouchButton}.
     *
     * The value of the button's bound state is rendered into a child `div` element containing the CSS class
     * `touch-button-value-value`.
     */
    class ValueTouchButton extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.buttonRef = msfsSdk.FSComponent.createRef();
            this.valueRef = msfsSdk.FSComponent.createRef();
            this.cssClassSet = msfsSdk.SetSubject.create(['touch-button-value']);
        }
        /** @inheritdoc */
        onAfterRender() {
            var _a;
            if ((this.props.renderValue === undefined || typeof this.props.renderValue === 'function')) {
                const renderFunc = (_a = this.props.renderValue) !== null && _a !== void 0 ? _a : ((value) => `${value}`);
                this.stateSub = this.props.state.sub(value => {
                    this.processRenderedValue(renderFunc(value));
                }, true);
            }
        }
        /**
         * Gets this button's root HTML element.
         * @returns This button's root HTML element.
         * @throws Error if this button has not yet been rendered.
         */
        getRootElement() {
            return this.buttonRef.instance.getRootElement();
        }
        /**
         * Simulates this button being pressed. This will execute the `onPressed()` callback if one is defined.
         * @param ignoreDisabled Whether to simulate the button being pressed regardless of whether the button is disabled.
         * Defaults to `false`.
         */
        simulatePressed(ignoreDisabled = false) {
            var _a;
            (_a = this.buttonRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.simulatePressed(ignoreDisabled);
        }
        /**
         * Processes a newly rendered value. The new rendered value will rendered into this button's value container,
         * replacing any existing rendered value.
         * @param rendered The newly rendered value.
         */
        processRenderedValue(rendered) {
            this.cleanUpRenderedValue();
            this.renderedValue = rendered;
            if (typeof rendered === 'string') {
                this.valueRef.instance.textContent = rendered;
            }
            else {
                msfsSdk.FSComponent.render(rendered, this.valueRef.instance);
            }
        }
        /**
         * Cleans up this button's rendered value.
         */
        cleanUpRenderedValue() {
            if (this.renderedValue === undefined) {
                return;
            }
            const valueContainer = this.valueRef.getOrDefault();
            if (typeof this.renderedValue === 'object') {
                msfsSdk.FSComponent.visitNodes(this.renderedValue, node => {
                    if (node.instance instanceof msfsSdk.DisplayComponent) {
                        node.instance.destroy();
                        return true;
                    }
                    return false;
                });
                if (valueContainer !== null) {
                    valueContainer.innerHTML = '';
                }
            }
            else {
                if (valueContainer !== null) {
                    valueContainer.textContent = '';
                }
            }
        }
        /**
         * Responds to when this button is touched.
         * @param button The button that was touched.
         * @returns The action to take as a result of the button being touched.
         */
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        onTouched(button) {
            return this.props.onTouched
                ? this.props.onTouched(this, this.props.state)
                : exports.TouchButtonOnTouchedAction.Prime;
        }
        /**
         * Responds to when this button is pressed.
         * @param button The button that was pressed.
         * @param isHeld Whether the button was held when it was pressed.
         */
        onPressed(button, isHeld) {
            this.props.onPressed && this.props.onPressed(this, this.props.state, isHeld);
        }
        /**
         * Responds to when this button enters the held state.
         * @param button The button that is held.
         * @returns The action to take. Ignored if the value is equal to {@link TouchButtonHoldAction.EndHold}.
         */
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        onHoldStarted(button) {
            return this.props.onHoldStarted
                ? this.props.onHoldStarted(this, this.props.state)
                : exports.TouchButtonHoldAction.None;
        }
        /**
         * A callback which is called every frame when this button is held.
         * @param button The button that is held.
         * @param dt The elapsed time, in milliseconds, since the previous frame.
         * @param totalTime The total amount of time, in milliseconds, that this button has been held.
         * @param timeSinceLastPress The amount of time, in milliseconds, that this button has been held since the last time
         * the button was pressed as a tick action.
         * @returns The action to take.
         */
        onHoldTick(button, dt, totalTime, timeSinceLastPress) {
            return this.props.onHoldTick
                ? this.props.onHoldTick(this, this.props.state, dt, totalTime, timeSinceLastPress)
                : exports.TouchButtonHoldAction.None;
        }
        /**
         * Responds to when this button exits the held state.
         * @param button The button that was held.
         * @param totalHoldDuration The total amount of time, in milliseconds, that this button was held.
         * @param endReason The reason that this button exited the held state.
         */
        onHoldEnded(button, totalHoldDuration, endReason) {
            this.props.onHoldEnded && this.props.onHoldEnded(this, this.props.state, totalHoldDuration, endReason);
        }
        /** @inheritdoc */
        render() {
            const reservedClasses = this.getReservedCssClasses();
            if (typeof this.props.class === 'object') {
                this.cssClassSub = msfsSdk.FSComponent.bindCssClassSet(this.cssClassSet, this.props.class, reservedClasses);
            }
            else if (this.props.class !== undefined && this.props.class.length > 0) {
                for (const cssClassToAdd of msfsSdk.FSComponent.parseCssClassesFromString(this.props.class, cssClass => !reservedClasses.has(cssClass))) {
                    this.cssClassSet.add(cssClassToAdd);
                }
            }
            return (msfsSdk.FSComponent.buildComponent(TouchButton, { ref: this.buttonRef, isEnabled: this.props.isEnabled, isVisible: this.props.isVisible, isHighlighted: this.props.isHighlighted, label: this.props.label, onTouched: this.onTouched.bind(this), onPressed: this.onPressed.bind(this), onHoldStarted: this.onHoldStarted.bind(this), onHoldTick: this.onHoldTick.bind(this), onHoldEnded: this.onHoldEnded.bind(this), focusOnDrag: this.props.focusOnDrag, inhibitOnDrag: this.props.inhibitOnDrag, dragThresholdPx: this.props.dragThresholdPx, inhibitOnDragAxis: this.props.inhibitOnDragAxis, class: this.cssClassSet },
                this.renderValueContainer(),
                this.props.children));
        }
        /**
         * Renders this button's value container.
         * @returns This button's rendered value container.
         */
        renderValueContainer() {
            if (this.props.renderValue !== undefined && typeof this.props.renderValue === 'object') {
                this.renderedValue = this.props.renderValue;
            }
            return (msfsSdk.FSComponent.buildComponent("div", { ref: this.valueRef, class: 'touch-button-value-value' }, this.renderedValue));
        }
        /**
         * Gets the CSS classes that are reserved for this button's root element.
         * @returns The CSS classes that are reserved for this button's root element.
         */
        getReservedCssClasses() {
            return ValueTouchButton.RESERVED_CSS_CLASSES;
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c;
            this.props.onDestroy && this.props.onDestroy();
            (_a = this.buttonRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
            this.cleanUpRenderedValue();
            (_b = this.cssClassSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.stateSub) === null || _c === void 0 ? void 0 : _c.destroy();
            super.destroy();
        }
    }
    ValueTouchButton.RESERVED_CSS_CLASSES = new Set(['touch-button-value']);

    const BG_IMG_TOUCH_BUTTON_CSS_CLASS = 'bg-img-touch-button';
    /**
     * A touchscreen button which uses different images for its "up" state and "down" state backgrounds and displays an optional label.
     *
     * The root element of the button contains the `bg-img-touch-button` CSS class by default, in addition to all
     * root-element classes used by {@link TouchButton}, with the exception of `touch-button` itself.
     *
     * The root element contains optional child `<img>` elements with the CSS classes `bg-img-touch-button-up-img` and
     * `bg-img-touch-button-down-img` as well as an optional label element with the CSS class `bg-img-touch-button-label`.
     */
    class BgImgTouchButton extends TouchButton {
        constructor() {
            super(...arguments);
            this.cssClassSet = msfsSdk.SetSubject.create([BG_IMG_TOUCH_BUTTON_CSS_CLASS]);
            this.upImgSrc = msfsSdk.SubscribableUtils.isSubscribable(this.props.upImgSrc)
                ? this.props.upImgSrc.map(msfsSdk.SubscribableMapFunctions.identity()) : undefined;
            this.downImgSrc = msfsSdk.SubscribableUtils.isSubscribable(this.props.downImgSrc)
                ? this.props.downImgSrc.map(msfsSdk.SubscribableMapFunctions.identity()) : undefined;
        }
        /** @inheritdoc */
        render() {
            var _a, _b;
            const reservedClasses = this.getReservedCssClasses();
            const upImgSrc = (_a = this.upImgSrc) !== null && _a !== void 0 ? _a : this.props.upImgSrc;
            const downImgSrc = (_b = this.downImgSrc) !== null && _b !== void 0 ? _b : this.props.downImgSrc;
            if (typeof this.props.class === 'object') {
                this.cssClassSub = msfsSdk.FSComponent.bindCssClassSet(this.cssClassSet, this.props.class, reservedClasses);
            }
            else if (this.props.class !== undefined && this.props.class.length > 0) {
                for (const cssClassToAdd of msfsSdk.FSComponent.parseCssClassesFromString(this.props.class, cssClass => !reservedClasses.has(cssClass))) {
                    this.cssClassSet.add(cssClassToAdd);
                }
            }
            return (msfsSdk.FSComponent.buildComponent("div", { ref: this.rootRef, class: this.cssClassSet },
                upImgSrc && msfsSdk.FSComponent.buildComponent("img", { src: upImgSrc, class: "bg-img-touch-button-up-img" }),
                downImgSrc && msfsSdk.FSComponent.buildComponent("img", { src: downImgSrc, class: "bg-img-touch-button-down-img" }),
                this.props.label && msfsSdk.FSComponent.buildComponent("div", { class: 'bg-img-touch-button-label' }, this.labelContent),
                this.props.children));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c;
            this.props.onDestroy && this.props.onDestroy();
            (_a = this.upImgSrc) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.downImgSrc) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.cssClassSub) === null || _c === void 0 ? void 0 : _c.destroy();
            super.destroy();
        }
    }
    BgImgTouchButton.RESERVED_CSS_CLASSES = new Set([
        BG_IMG_TOUCH_BUTTON_CSS_CLASS,
        'touch-button-disabled',
        'touch-button-primed',
        'touch-button-highlight',
        'touch-button-hidden',
    ]);

    /**
     * A touchscreen pad which tracks mouse drag motions.
     */
    class TouchPad extends msfsSdk.DisplayComponent {
        constructor() {
            var _a, _b, _c, _d, _e, _f, _g, _h, _j;
            super(...arguments);
            this.rootRef = msfsSdk.FSComponent.createRef();
            this.sliderContainerRef = msfsSdk.FSComponent.createRef();
            this.sliderThumbRef = msfsSdk.FSComponent.createRef();
            this.rootCssClass = msfsSdk.SetSubject.create(['touch-pad']);
            this.mouseDownListener = this.onMouseDown.bind(this);
            this.mouseUpListener = this.onMouseUp.bind(this);
            this.mouseLeaveListener = this.onMouseLeave.bind(this);
            this.mouseMoveListener = this.onMouseMove.bind(this);
            this.isEnabled = msfsSdk.SubscribableUtils.toSubscribable((_a = this.props.isEnabled) !== null && _a !== void 0 ? _a : true, true);
            this.isVisible = msfsSdk.SubscribableUtils.toSubscribable((_b = this.props.isVisible) !== null && _b !== void 0 ? _b : true, true);
            this.isPrimed = false;
            this.isFocusLocked = false;
            this.mouseDownPosition = msfsSdk.Vec2Math.create();
            this.prevMousePosition = msfsSdk.Vec2Math.create();
            this.currentMousePosition = msfsSdk.Vec2Math.create();
            this.focusOnDrag = (_c = this.props.focusOnDrag) !== null && _c !== void 0 ? _c : false;
            this.lockFocusOnDrag = (_d = this.props.lockFocusOnDrag) !== null && _d !== void 0 ? _d : false;
            this.lockFocusOnDragAxis = (_e = this.props.lockFocusOnDragAxis) !== null && _e !== void 0 ? _e : 'both';
            this.dragLockFocusThresholdPx = (_f = this.props.dragLockFocusThresholdPx) !== null && _f !== void 0 ? _f : 0;
            this.inhibitOnDrag = (_g = this.props.inhibitOnDrag) !== null && _g !== void 0 ? _g : false;
            this.inhibitOnDragAxis = (_h = this.props.inhibitOnDragAxis) !== null && _h !== void 0 ? _h : 'both';
            this.dragInhibitThresholdPx = (_j = this.props.dragInhibitThresholdPx) !== null && _j !== void 0 ? _j : 40;
            this.tickInterval = null;
            this.lastTickTime = undefined;
            this.lastTickMousePosition = msfsSdk.Vec2Math.create();
            this.tickFunc = () => {
                var _a;
                const time = Date.now();
                const dt = time - ((_a = this.lastTickTime) !== null && _a !== void 0 ? _a : time);
                this.props.onDragTick && this.props.onDragTick(this.currentMousePosition, this.lastTickTime === undefined ? undefined : this.lastTickMousePosition, this.mouseDownPosition, dt, this);
                this.lastTickTime = time;
                msfsSdk.Vec2Math.copy(this.currentMousePosition, this.lastTickMousePosition);
            };
        }
        /** @inheritdoc */
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        onAfterRender(node) {
            var _a;
            this.isEnabledSub = this.isEnabled.sub(isEnabled => {
                if (isEnabled) {
                    this.rootCssClass.delete('touch-pad-disabled');
                }
                else {
                    this.rootCssClass.add('touch-pad-disabled');
                }
                if (!isEnabled) {
                    this.setPrimed(false);
                }
            }, true);
            this.isVisibleSub = this.isVisible.sub(isVisible => {
                if (isVisible) {
                    this.rootCssClass.delete('touch-pad-hidden');
                }
                else {
                    this.rootCssClass.add('touch-pad-hidden');
                }
            }, true);
            this.rootRef.instance.addEventListener('mousedown', this.mouseDownListener);
            this.rootRef.instance.addEventListener('mousemove', this.mouseMoveListener);
            this.rootRef.instance.addEventListener('mouseup', this.mouseUpListener);
            this.rootRef.instance.addEventListener('mouseleave', this.mouseLeaveListener);
            this.instrumentMouseLeaveSub = (_a = this.props.bus) === null || _a === void 0 ? void 0 : _a.getSubscriber().on('vc_mouse_leave').handle(() => {
                this.onMouseLeave();
            });
        }
        /**
         * Sets the primed state of this pad.
         * @param isPrimed The new primed state.
         */
        setPrimed(isPrimed) {
            if (this.isPrimed === isPrimed) {
                return;
            }
            this.isPrimed = isPrimed;
            if (isPrimed) {
                this.rootCssClass.add('touch-pad-primed');
                this.props.onDragStarted && this.props.onDragStarted(this.currentMousePosition, this);
                if (this.props.onDragTick !== undefined) {
                    if (this.tickInterval !== null) {
                        clearInterval(this.tickInterval);
                    }
                    this.lastTickTime = undefined;
                    this.tickInterval = setInterval(this.tickFunc, 0);
                }
            }
            else {
                this.setFocusLocked(false);
                this.rootCssClass.delete('touch-pad-primed');
                if (this.tickInterval !== null) {
                    clearInterval(this.tickInterval);
                    this.tickInterval = null;
                }
                this.props.onDragEnded && this.props.onDragEnded(this.currentMousePosition, this.mouseDownPosition, this);
            }
        }
        /**
         * Sets the focus lock state of this pad.
         * @param isFocusLocked The new focus lock state.
         */
        setFocusLocked(isFocusLocked) {
            if (this.isFocusLocked === isFocusLocked) {
                return;
            }
            this.isFocusLocked = isFocusLocked;
            if (isFocusLocked) {
                window.addEventListener('mouseup', this.mouseUpListener);
                window.addEventListener('mousemove', this.mouseMoveListener);
            }
            else {
                window.removeEventListener('mouseup', this.mouseUpListener);
                window.removeEventListener('mousemove', this.mouseMoveListener);
            }
        }
        /**
         * Responds to mouse down events on this pad's root element.
         * @param e The mouse event.
         */
        onMouseDown(e) {
            if (this.isEnabled.get()) {
                if (this.focusOnDrag || this.isFocusLocked) {
                    e.stopPropagation();
                }
                msfsSdk.Vec2Math.set(e.clientX, e.clientY, this.mouseDownPosition);
                msfsSdk.Vec2Math.set(e.clientX, e.clientY, this.currentMousePosition);
                msfsSdk.Vec2Math.set(e.clientX, e.clientY, this.prevMousePosition);
                this.setPrimed(true);
            }
        }
        /**
         * Responds to mouse up events.
         */
        onMouseUp() {
            this.setPrimed(false);
        }
        /**
         * Responds to mouse leave events.
         * @param e The mouse event, or `undefined` if the mouse left the instrument window.
         */
        onMouseLeave(e) {
            var _a;
            if (!this.isPrimed) {
                return;
            }
            // Don't respond if focus is locked and the mouse has not left the instrument window
            if (this.isFocusLocked && e !== undefined) {
                return;
            }
            this.setPrimed(false);
            if (e !== undefined && this.focusOnDrag) {
                const newE = new MouseEvent('mousedown', {
                    clientX: e.clientX,
                    clientY: e.clientY,
                    bubbles: true,
                });
                (_a = this.rootRef.instance.parentElement) === null || _a === void 0 ? void 0 : _a.dispatchEvent(newE);
            }
        }
        /**
         * Responds to mouse move events.
         * @param e The mouse event.
         */
        onMouseMove(e) {
            var _a;
            if (!this.isPrimed) {
                return;
            }
            msfsSdk.Vec2Math.set(e.clientX, e.clientY, this.currentMousePosition);
            if (this.lockFocusOnDrag && !this.isFocusLocked && this.getDragDistance(this.lockFocusOnDragAxis, this.mouseDownPosition) > this.dragLockFocusThresholdPx) {
                this.setFocusLocked(true);
            }
            if (!this.isFocusLocked && this.inhibitOnDrag && Math.abs(this.getDragDistance(this.inhibitOnDragAxis, this.mouseDownPosition)) > this.dragInhibitThresholdPx) {
                this.setPrimed(false);
                if (this.focusOnDrag) {
                    const newE = new MouseEvent('mousedown', {
                        clientX: e.clientX,
                        clientY: e.clientY,
                        bubbles: true,
                    });
                    (_a = this.rootRef.instance.parentElement) === null || _a === void 0 ? void 0 : _a.dispatchEvent(newE);
                }
            }
            else {
                if (this.focusOnDrag || this.isFocusLocked) {
                    e.stopPropagation();
                }
                this.props.onDragMoved && this.props.onDragMoved(this.currentMousePosition, this.prevMousePosition, this.mouseDownPosition, this);
                msfsSdk.Vec2Math.set(e.clientX, e.clientY, this.prevMousePosition);
            }
        }
        /**
         * Get the distance that the mouse has been dragged along an axis relative to an initial position.
         * @param axis The axis along which to measure the distance.
         * @param initialPos The initial mouse position.
         * @returns The distance that the mouse has been dragged along the specified axis relative to the specified initial
         * position.
         */
        getDragDistance(axis, initialPos) {
            switch (axis) {
                case 'x':
                    return Math.abs(this.currentMousePosition[0] - initialPos[0]);
                case 'y':
                    return Math.abs(this.currentMousePosition[1] - initialPos[1]);
                default:
                    return msfsSdk.Vec2Math.distance(this.currentMousePosition, initialPos);
            }
        }
        /** @inheritdoc */
        render() {
            const reservedClasses = this.getReservedCssClasses();
            if (typeof this.props.class === 'object') {
                this.cssClassSub = msfsSdk.FSComponent.bindCssClassSet(this.rootCssClass, this.props.class, reservedClasses);
            }
            else if (this.props.class !== undefined && this.props.class.length > 0) {
                for (const cssClassToAdd of msfsSdk.FSComponent.parseCssClassesFromString(this.props.class, cssClass => !reservedClasses.has(cssClass))) {
                    this.rootCssClass.add(cssClassToAdd);
                }
            }
            return (msfsSdk.FSComponent.buildComponent("div", { ref: this.rootRef, class: this.rootCssClass }, this.props.children));
        }
        /**
         * Gets the CSS classes that are reserved for this pad's root element.
         * @returns The CSS classes that are reserved for this pad's root element.
         */
        getReservedCssClasses() {
            return TouchPad.RESERVED_CSS_CLASSES;
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c, _d;
            this.props.onDestroy && this.props.onDestroy();
            if (this.tickInterval !== null) {
                clearInterval(this.tickInterval);
            }
            (_a = this.cssClassSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.isEnabledSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.isVisibleSub) === null || _c === void 0 ? void 0 : _c.destroy();
            window.removeEventListener('mouseup', this.mouseUpListener);
            window.removeEventListener('mousemove', this.mouseMoveListener);
            (_d = this.instrumentMouseLeaveSub) === null || _d === void 0 ? void 0 : _d.destroy();
            this.rootRef.instance.removeEventListener('mousedown', this.mouseDownListener);
            this.rootRef.instance.removeEventListener('mouseup', this.mouseUpListener);
            this.rootRef.instance.removeEventListener('mouseleave', this.mouseLeaveListener);
            this.rootRef.instance.removeEventListener('mousemove', this.mouseMoveListener);
            super.destroy();
        }
    }
    TouchPad.DEFAULT_SNAP_ANIMATION_EASING = msfsSdk.Easing.withEndpointParams(msfsSdk.Easing.cubic('out'));
    TouchPad.RESERVED_CSS_CLASSES = new Set([
        'touch-pad',
        'touch-pad-disabled',
        'touch-pad-primed',
        'touch-pad-hidden'
    ]);

    /**
     * A touchscreen slider.
     */
    class TouchSlider extends msfsSdk.DisplayComponent {
        constructor() {
            var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
            super(...arguments);
            this.mainAxisIndex = this.props.orientation === 'to-left' || this.props.orientation === 'to-right' ? 0 : 1;
            this.crossAxisIndex = (this.mainAxisIndex + 1) % 2;
            this.mainAxisSign = this.props.orientation === 'to-left' || this.props.orientation === 'to-top' ? -1 : 1;
            this.sliderContainerRef = msfsSdk.FSComponent.createRef();
            this.touchPadRef = msfsSdk.FSComponent.createRef();
            this.sliderThumbRef = msfsSdk.FSComponent.createRef();
            this.snapAnimationSpeed = msfsSdk.SubscribableUtils.toSubscribable((_a = this.props.snapAnimationSpeed) !== null && _a !== void 0 ? _a : 0, true);
            this.snapAnimationEasing = (_b = this.props.snapAnimationEasing) !== null && _b !== void 0 ? _b : TouchSlider.DEFAULT_SNAP_ANIMATION_EASING;
            this.valueAnimator = new msfsSdk.Animator();
            // Note: clip path will disable itself if the size of the clip area is 0, so we make sure the length of the clip
            // area is always a positive number.
            this.sliderForegroundClipPathFunc = this.mainAxisIndex === 0
                ? this.mainAxisSign === -1
                    ? ((value) => {
                        const edge = 100 - Math.max(value * 100, 0.01);
                        return `polygon(${edge}% 0%, 100% 0%, 100% 100%, ${edge}% 100%)`;
                    })
                    : ((value) => {
                        const edge = Math.max(value * 100, 0.01);
                        return `polygon(0% 0%, ${edge}% 0%, ${edge}% 100%, 0% 100%)`;
                    })
                : this.mainAxisSign === -1
                    ? ((value) => {
                        const edge = 100 - Math.max(value * 100, 0.01);
                        return `polygon(0% ${edge}%, 100% ${edge}%, 100% 100%, 0% 100%)`;
                    })
                    : ((value) => {
                        const edge = Math.max(value * 100, 0.01);
                        return `polygon(0% 0%, 100% 0%, 100% ${edge}%, 0% ${edge}%)`;
                    });
            this.sliderThumbTranslateFunc = this.mainAxisIndex === 0
                ? ((value) => {
                    return `translate3d(${this.mainAxisSign * value * 100}%, 0px, 0px)`;
                })
                : ((value) => {
                    return `translate3d(0px, ${this.mainAxisSign * value * 100}%, 0px)`;
                });
            this.sliderForegroundStyle = msfsSdk.ObjectSubject.create({
                '-webkit-clip-path': this.sliderForegroundClipPathFunc(0)
            });
            this.sliderThumbStyle = msfsSdk.ObjectSubject.create({
                transform: this.sliderThumbTranslateFunc(0)
            });
            this.rootCssClass = msfsSdk.SetSubject.create([
                'touch-slider',
                `touch-slider-${this.mainAxisIndex === 0 ? 'horizontal' : 'vertical'}`,
                `touch-slider-${this.props.orientation}`
            ]);
            this.isEnabled = msfsSdk.SubscribableUtils.toSubscribable((_c = this.props.isEnabled) !== null && _c !== void 0 ? _c : true, true);
            this.isVisible = msfsSdk.SubscribableUtils.toSubscribable((_d = this.props.isVisible) !== null && _d !== void 0 ? _d : true, true);
            this.isPrimed = false;
            this.stops = msfsSdk.SubscribableUtils.toSubscribable((_e = this.props.stops) !== null && _e !== void 0 ? _e : [], true);
            this.sliderLength = 0;
            this.thumbLength = 0;
            this.mouseDownValue = 0;
            this.draggedValue = 0;
            this.mouseDownPosition = msfsSdk.Vec2Math.create();
            this.referenceMousePosition = msfsSdk.Vec2Math.create();
            this.currentMousePosition = msfsSdk.Vec2Math.create();
            this.focusOnDrag = (_f = this.props.focusOnDrag) !== null && _f !== void 0 ? _f : false;
            this.lockFocusOnDrag = (_g = this.props.lockFocusOnDrag) !== null && _g !== void 0 ? _g : false;
            this.dragLockFocusThresholdPx = (_h = this.props.dragLockFocusThresholdPx) !== null && _h !== void 0 ? _h : 10;
            this.inhibitOnDrag = (_j = this.props.inhibitOnDrag) !== null && _j !== void 0 ? _j : false;
            this.dragInhibitThresholdPx = (_k = this.props.dragInhibitThresholdPx) !== null && _k !== void 0 ? _k : 40;
        }
        /** @inheritdoc */
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        onAfterRender(node) {
            this.isEnabledSub = this.isEnabled.sub(isEnabled => {
                if (isEnabled) {
                    this.rootCssClass.delete('touch-slider-disabled');
                }
                else {
                    this.rootCssClass.add('touch-slider-disabled');
                }
            }, true);
            this.isVisibleSub = this.isVisible.sub(isVisible => {
                if (isVisible) {
                    this.rootCssClass.delete('touch-slider-hidden');
                }
                else {
                    this.rootCssClass.add('touch-slider-hidden');
                }
            }, true);
            this.stateSub = this.props.state.sub(value => {
                msfsSdk.Vec2Math.copy(this.currentMousePosition, this.referenceMousePosition);
                value = msfsSdk.MathUtils.clamp(value, 0, 1);
                this.mouseDownValue = value;
                this.draggedValue = value;
                this.setDisplayedValue(value, false);
            }, true);
            this.valueAnimator.value.sub(displayValue => {
                this.sliderForegroundStyle.set('-webkit-clip-path', this.sliderForegroundClipPathFunc(displayValue));
                this.sliderThumbStyle.set('transform', this.sliderThumbTranslateFunc(displayValue));
            }, true);
        }
        /**
         * Sets the primed state of this slider.
         * @param isPrimed The new primed state.
         */
        setPrimed(isPrimed) {
            if (this.isPrimed === isPrimed) {
                return;
            }
            this.isPrimed = isPrimed;
            this.rootCssClass.toggle('touch-slider-primed', isPrimed);
        }
        /**
         * Responds to when a mouse drag is started on this slider.
         * @param position The current mouse position.
         */
        onDragStarted(position) {
            this.setPrimed(true);
            this.sliderLength = this.sliderContainerRef.instance[this.mainAxisIndex === 0 ? 'clientWidth' : 'clientHeight'];
            this.thumbLength = this.sliderThumbRef.instance[this.mainAxisIndex === 0 ? 'offsetWidth' : 'offsetHeight'];
            this.mouseDownValue = this.props.state.get();
            this.draggedValue = this.mouseDownValue;
            msfsSdk.Vec2Math.copy(position, this.referenceMousePosition);
            msfsSdk.Vec2Math.copy(position, this.currentMousePosition);
            this.props.onDragStarted && this.props.onDragStarted(position, this);
        }
        /**
         * Responds to when this slider is dragged.
         * @param position The current mouse position.
         * @param prevPosition The position of the mouse at the previous update.
         * @param initialPosition The position of the mouse at the start of the current drag motion.
         */
        onDragMoved(position, prevPosition, initialPosition) {
            msfsSdk.Vec2Math.copy(position, this.currentMousePosition);
            const deltaPos = position[this.mainAxisIndex] - this.referenceMousePosition[this.mainAxisIndex];
            const deltaValue = deltaPos * this.mainAxisSign / (this.sliderLength - this.thumbLength);
            this.draggedValue = msfsSdk.MathUtils.clamp(this.mouseDownValue + deltaValue, 0, 1);
            if (this.props.changeValueOnDrag) {
                this.onValueChangedFromInput(this.findClosestStop(this.draggedValue));
            }
            else {
                this.setDisplayedValue(this.draggedValue, false);
            }
            this.props.onDragMoved && this.props.onDragMoved(position, prevPosition, initialPosition, this);
        }
        /**
         * Responds to when a mouse drag is released on this slider.
         * @param position The current position of the mouse.
         * @param initialPosition The position of the mouse at the start of the drag motion.
         */
        onDragEnded(position, initialPosition) {
            var _a, _b;
            const wasPrimed = this.isPrimed;
            this.setPrimed(false);
            if (!wasPrimed || !this.isEnabled.get()) {
                return;
            }
            if (this.props.changeValueOnDrag) {
                return;
            }
            let valueToSet = this.findClosestStop(this.draggedValue);
            (_a = this.stateSub) === null || _a === void 0 ? void 0 : _a.pause();
            this.onValueChangedFromInput(valueToSet);
            (_b = this.stateSub) === null || _b === void 0 ? void 0 : _b.resume();
            if (this.isPrimed) {
                return;
            }
            const valueToSetChanged = valueToSet !== this.props.state.get();
            if (valueToSetChanged) {
                valueToSet = msfsSdk.MathUtils.clamp(this.props.state.get(), 0, 1);
            }
            this.setDisplayedValue(valueToSet, !valueToSetChanged);
            this.props.onDragEnded && this.props.onDragEnded(position, initialPosition, this);
        }
        /**
         * Responds to when this slider's value changes from user input.
         * @param value The new slider value.
         */
        onValueChangedFromInput(value) {
            if (this.props.onValueChanged !== undefined) {
                this.props.onValueChanged(value, this.props.state, this);
            }
            else if (msfsSdk.SubscribableUtils.isMutableSubscribable(this.props.state)) {
                this.props.state.set(value);
            }
        }
        /**
         * Finds the stop closest to a given value. If this slider has no defined stops, this method will return the query
         * value as-is.
         * @param value The query value.
         * @returns The stop closest to the specified value, or the specified value if this slider has no defined stops.
         */
        findClosestStop(value) {
            if (this.stops.get().length > 0) {
                // find the closest stop
                return msfsSdk.MathUtils.clamp(this.stops.get().reduce((prev, curr) => {
                    return Math.abs(value - curr) < Math.abs(value - prev) ? curr : prev;
                }), 0, 1);
            }
            else {
                return value;
            }
        }
        /**
         * Sets this slider's displayed value.
         * @param value The value to set.
         * @param animate Whether to animate the change.
         */
        setDisplayedValue(value, animate) {
            animate && (animate = this.snapAnimationSpeed.get() > 0);
            if (animate) {
                const duration = Math.abs(value - this.valueAnimator.value.get()) / this.snapAnimationSpeed.get() * 1000;
                this.valueAnimator.start(value, duration, this.snapAnimationEasing);
            }
            else {
                this.valueAnimator.set(value);
            }
        }
        /** @inheritdoc */
        render() {
            var _a, _b;
            const reservedClasses = this.getReservedCssClasses();
            if (typeof this.props.class === 'object') {
                this.cssClassSub = msfsSdk.FSComponent.bindCssClassSet(this.rootCssClass, this.props.class, reservedClasses);
            }
            else if (this.props.class !== undefined && this.props.class.length > 0) {
                for (const cssClassToAdd of msfsSdk.FSComponent.parseCssClassesFromString(this.props.class, cssClass => !reservedClasses.has(cssClass))) {
                    this.rootCssClass.add(cssClassToAdd);
                }
            }
            return (msfsSdk.FSComponent.buildComponent(TouchPad, { ref: this.touchPadRef, bus: this.props.bus, onDragStarted: this.onDragStarted.bind(this), onDragMoved: this.onDragMoved.bind(this), onDragEnded: this.onDragEnded.bind(this), isEnabled: this.isEnabled, focusOnDrag: this.props.focusOnDrag, lockFocusOnDrag: this.props.lockFocusOnDrag, lockFocusOnDragAxis: this.mainAxisIndex === 0 ? 'x' : 'y', dragLockFocusThresholdPx: (_a = this.props.dragLockFocusThresholdPx) !== null && _a !== void 0 ? _a : 10, inhibitOnDrag: this.props.inhibitOnDrag, inhibitOnDragAxis: this.crossAxisIndex === 0 ? 'x' : 'y', dragInhibitThresholdPx: (_b = this.props.dragInhibitThresholdPx) !== null && _b !== void 0 ? _b : 40, class: this.rootCssClass },
                msfsSdk.FSComponent.buildComponent("div", { ref: this.sliderContainerRef, class: 'touch-slider-slider-container' },
                    msfsSdk.FSComponent.buildComponent("div", { class: 'touch-slider-slider-background' }, this.props.background),
                    msfsSdk.FSComponent.buildComponent("div", { class: 'touch-slider-slider-foreground', style: this.sliderForegroundStyle }, this.props.foreground),
                    msfsSdk.FSComponent.buildComponent("div", { class: 'touch-slider-slider-inset' }, this.props.inset),
                    msfsSdk.FSComponent.buildComponent("div", { class: 'touch-slider-slider-thumb-translate', style: this.sliderThumbStyle },
                        msfsSdk.FSComponent.buildComponent("div", { ref: this.sliderThumbRef, class: 'touch-slider-slider-thumb' }, this.props.thumb))),
                this.props.children));
        }
        /**
         * Gets the CSS classes that are reserved for this slider's root element.
         * @returns The CSS classes that are reserved for this slider's root element.
         */
        getReservedCssClasses() {
            return TouchSlider.RESERVED_CSS_CLASSES;
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c, _d, _e;
            this.props.onDestroy && this.props.onDestroy();
            (_a = this.touchPadRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
            this.valueAnimator.stop();
            (_b = this.cssClassSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.isEnabledSub) === null || _c === void 0 ? void 0 : _c.destroy();
            (_d = this.isVisibleSub) === null || _d === void 0 ? void 0 : _d.destroy();
            (_e = this.stateSub) === null || _e === void 0 ? void 0 : _e.destroy();
            super.destroy();
        }
    }
    TouchSlider.DEFAULT_SNAP_ANIMATION_EASING = msfsSdk.Easing.withEndpointParams(msfsSdk.Easing.cubic('out'));
    TouchSlider.RESERVED_CSS_CLASSES = new Set([
        'touch-slider',
        'touch-slider-horizontal',
        'touch-slider-vertical',
        'touch-slider-to-left',
        'touch-slider-to-right',
        'touch-slider-to-top',
        'touch-slider-to-bottom',
        'touch-slider-disabled',
        'touch-slider-primed',
        'touch-slider-hidden'
    ]);

    /**
     * An abstract component which is bound to a waypoint.
     */
    class WaypointComponent extends msfsSdk.DisplayComponent {
        // eslint-disable-next-line jsdoc/require-jsdoc
        onAfterRender() {
            this.waypointChangedSub = this.props.waypoint.sub(this.onWaypointChanged.bind(this), true);
        }
        /**
         * A callback which is called when this component's waypoint changes.
         * @param waypoint The new waypoint.
         */
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        onWaypointChanged(waypoint) {
            // noop
        }
        // eslint-disable-next-line jsdoc/require-jsdoc
        destroy() {
            var _a;
            (_a = this.waypointChangedSub) === null || _a === void 0 ? void 0 : _a.destroy();
        }
    }

    /** A waypoint icon. */
    class WaypointIcon extends WaypointComponent {
        constructor() {
            super(...arguments);
            this.imgRef = msfsSdk.FSComponent.createRef();
            this.srcSub = msfsSdk.ComputedSubject.create(null, (waypoint) => {
                var _a, _b;
                if (waypoint !== null && msfsSdk.FacilityWaypointUtils.isFacilityWaypoint(waypoint)) {
                    return (_b = (_a = this.props.imageCache.getForWaypoint(waypoint)) === null || _a === void 0 ? void 0 : _a.src) !== null && _b !== void 0 ? _b : '';
                }
                return '';
            });
            this.imgFrameRowCount = 1;
            this.imgFrameColCount = 1;
            this.imgOffset = undefined;
            this.subscriptions = [];
        }
        /** @inheritdoc */
        onAfterRender() {
            this.initImageLoadListener();
            super.onAfterRender();
            if (this.props.planeHeading) {
                this.subscriptions.push(this.props.planeHeading.sub(this.updateOffset.bind(this), true));
            }
        }
        /** @inheritdoc */
        onWaypointChanged(waypoint) {
            this.srcSub.set(waypoint);
            this.updateOffset();
        }
        /**
         * Initializes the image onload listener.
         */
        initImageLoadListener() {
            this.imgRef.instance.onload = this.onImageLoaded.bind(this);
        }
        /**
         * A callback which is called when this component's image element finishes loading an image.
         */
        onImageLoaded() {
            const img = this.imgRef.instance;
            this.imgFrameRowCount = Math.floor(img.naturalHeight / this.props.atlasIconSize);
            this.imgFrameColCount = Math.floor(img.naturalWidth / this.props.atlasIconSize);
            this.updateOffset();
        }
        /**
         * Updates this icon's offset, or unsets it.
         */
        updateOffset() {
            const waypoint = this.props.waypoint.get();
            if (this.imgFrameRowCount > 1 && this.imgFrameColCount > 1 && waypoint instanceof AirportWaypoint && waypoint.longestRunway !== null) {
                let headingOffset = this.props.planeHeading ? this.props.planeHeading.get() : NaN;
                if (isNaN(headingOffset)) {
                    headingOffset = 0;
                }
                this.updateAirportIconOffset(headingOffset, waypoint.longestRunway.direction);
            }
            else {
                this.setImgOffset('unset');
            }
        }
        /**
         * Updates this airport icon's offset.
         * @param headingOffset How mush to offset the runway heading by.
         * @param runwayHeading The heading of the runway.
         */
        updateAirportIconOffset(headingOffset, runwayHeading) {
            const headingDelta = runwayHeading - headingOffset;
            const frame = Math.round(msfsSdk.NavMath.normalizeHeading(headingDelta) / 22.5) % 8;
            const row = Math.min(Math.floor(frame / 4), this.imgFrameRowCount - 1);
            const col = Math.min(frame % 4, this.imgFrameColCount - 1);
            const xOffset = col * -this.props.atlasIconSize;
            const yOffset = row * -this.props.atlasIconSize;
            this.setImgOffset(`${xOffset}px ${yOffset}px`);
        }
        /**
         * Sets the object offset of this icon's image element.
         * @param offset The new offset.
         */
        setImgOffset(offset) {
            if (offset === this.imgOffset) {
                return;
            }
            if (offset === 'unset') {
                // If not a texture atlas, do this so that the icon is centered.
                this.imgRef.instance.style.display = 'inline-flex';
            }
            else {
                this.imgRef.instance.style.display = 'inline-block';
            }
            this.imgOffset = offset;
            this.imgRef.instance.style.objectPosition = offset;
        }
        /** @inheritdoc */
        render() {
            const { atlasIconSize } = this.props;
            let cssClass;
            if (typeof this.props.class === 'object') {
                cssClass = msfsSdk.SetSubject.create();
                const sub = msfsSdk.FSComponent.bindCssClassSet(cssClass, this.props.class, WaypointIcon.RESERVED_CLASSES);
                if (Array.isArray(sub)) {
                    this.subscriptions.push(...sub);
                }
                else {
                    this.subscriptions.push(sub);
                }
            }
            else {
                cssClass = this.props.class;
            }
            return (msfsSdk.FSComponent.buildComponent("img", { ref: this.imgRef, class: cssClass, src: this.srcSub, style: `width: ${atlasIconSize}px; height: ${atlasIconSize}px; object-fit: none; object-position: ${this.imgOffset};` }));
        }
        /** @inheritdoc */
        destroy() {
            for (const sub of this.subscriptions) {
                sub.destroy();
            }
            const img = this.imgRef.getOrDefault();
            if (img) {
                img.onload = null;
            }
            super.destroy();
        }
    }
    WaypointIcon.RESERVED_CLASSES = [];

    /**
     * Provides utility functions for working with Garmin weather radars.
     */
    class WeatherRadarUtils {
        // eslint-disable-next-line jsdoc/require-throws
        /**
         * Converts a radar return signal strength in dBZ to an approximate precipitation rate in millimeters per hour.
         * @param dbz A radar return signal strength in dBZ.
         * @returns The precipitation rate, in millimeters per hour, that is approximately correlated with the specified
         * radar return signal strength.
         */
        static dbzToPrecipRate(dbz) {
            const table = WeatherRadarUtils.DBZ_TO_RATE_TABLE;
            const first = table[0];
            const last = table[table.length - 1];
            if (dbz < first[0]) {
                return Math.pow(10, (dbz - first[0]) / 10) * first[1];
            }
            else if (dbz > last[0]) {
                return Math.pow(2, (dbz - last[0]) / 5) * last[1];
            }
            for (let i = 1; i < table.length; i++) {
                const breakpoint = table[i];
                if (dbz < breakpoint[0]) {
                    const prevBreakpoint = table[i - 1];
                    return Math.pow(breakpoint[1] / prevBreakpoint[1], (dbz - prevBreakpoint[0]) / (breakpoint[0] - prevBreakpoint[0])) * prevBreakpoint[1];
                }
                else if (dbz === breakpoint[0]) {
                    return breakpoint[1];
                }
            }
            // Should never happen.
            throw new Error('WeatherRadarUtils.dbzToPrecipRate(): reached an un-reachable state');
        }
        /**
         * Gets the calibrated (zero-gain) standard Garmin three-color weather radar color array.
         * @returns The calibrated (zero-gain) standard Garmin three-color weather radar color array.
         */
        static standardColors() {
            return WeatherRadarUtils.STANDARD_COLORS;
        }
        /**
         * Gets the calibrated (zero-gain) extended Garmin 16-color weather radar color array.
         * @returns The calibrated (zero-gain) extended Garmin 16-color weather radar color array.
         */
        static extendedColors() {
            return WeatherRadarUtils.EXTENDED_COLORS;
        }
    }
    WeatherRadarUtils.DBZ_TO_RATE_TABLE = [
        [20, 0.25],
        [25, 1.27],
        [30, 2.54],
        [35, 5.59],
        [40, 11.43],
        [45, 23.37],
        [50, 48.26],
        [55, 101.6],
        [60, 203.2],
        [65, 406.4]
    ];
    WeatherRadarUtils.STANDARD_COLORS = [
        [msfsSdk.BingComponent.hexaToRGBAColor('#00000000'), 23],
        [msfsSdk.BingComponent.hexaToRGBAColor('#00ff00ff'), 33],
        [msfsSdk.BingComponent.hexaToRGBAColor('#ffff00ff'), 41],
        [msfsSdk.BingComponent.hexaToRGBAColor('#ff0000ff'), 41]
    ];
    WeatherRadarUtils.EXTENDED_COLORS = [
        [msfsSdk.BingComponent.hexaToRGBAColor('#00000000'), 23],
        [msfsSdk.BingComponent.hexaToRGBAColor('#00ff00ff'), 25],
        [msfsSdk.BingComponent.hexaToRGBAColor('#0cde00ff'), 27],
        [msfsSdk.BingComponent.hexaToRGBAColor('#18bd00ff'), 29],
        [msfsSdk.BingComponent.hexaToRGBAColor('#249b00ff'), 31],
        [msfsSdk.BingComponent.hexaToRGBAColor('#307a00ff'), 33],
        [msfsSdk.BingComponent.hexaToRGBAColor('#ffff00ff'), 35],
        [msfsSdk.BingComponent.hexaToRGBAColor('#f6db00ff'), 37],
        [msfsSdk.BingComponent.hexaToRGBAColor('#eeb600ff'), 39],
        [msfsSdk.BingComponent.hexaToRGBAColor('#e59200ff'), 41],
        [msfsSdk.BingComponent.hexaToRGBAColor('#ff0000ff'), 43],
        [msfsSdk.BingComponent.hexaToRGBAColor('#dc0000ff'), 45],
        [msfsSdk.BingComponent.hexaToRGBAColor('#b90000ff'), 47],
        [msfsSdk.BingComponent.hexaToRGBAColor('#960000ff'), 49],
        [msfsSdk.BingComponent.hexaToRGBAColor('#dd50ffff'), 51],
        [msfsSdk.BingComponent.hexaToRGBAColor('#b228c3ff'), 54],
        [msfsSdk.BingComponent.hexaToRGBAColor('#870087ff'), 54]
    ];

    /**
     * The operating mode of a Garmin weather radar.
     */
    exports.WeatherRadarOperatingMode = void 0;
    (function (WeatherRadarOperatingMode) {
        WeatherRadarOperatingMode["Standby"] = "Standby";
        WeatherRadarOperatingMode["Weather"] = "Weather";
    })(exports.WeatherRadarOperatingMode || (exports.WeatherRadarOperatingMode = {}));
    /**
     * The scan mode of a Garmin weather radar.
     */
    exports.WeatherRadarScanMode = void 0;
    (function (WeatherRadarScanMode) {
        WeatherRadarScanMode["Horizontal"] = "Horizontal";
        WeatherRadarScanMode["Vertical"] = "Vertical";
    })(exports.WeatherRadarScanMode || (exports.WeatherRadarScanMode = {}));
    /**
     * A Garmin weather radar display.
     */
    class WeatherRadar extends msfsSdk.DisplayComponent {
        constructor() {
            var _a, _b, _c, _d;
            super(...arguments);
            this.bingRef = msfsSdk.FSComponent.createRef();
            this.rangeLabelRefs = Array.from({ length: 4 }, () => msfsSdk.FSComponent.createRef());
            this.rootStyle = msfsSdk.ObjectSubject.create({
                width: '0px',
                height: '0px',
                '--weather-radar-arc-origin-x': '0px',
                '--weather-radar-arc-origin-y': '0px',
                '--weather-radar-arc-radius': '0px',
                '--weather-radar-arc-left': '0px',
                '--weather-radar-arc-top': '0px',
                '--weather-radar-arc-right': '0px',
                '--weather-radar-arc-bottom': '0px',
            });
            this.bingStyle = msfsSdk.ObjectSubject.create({
                display: 'none',
                position: 'absolute',
                left: '0',
                top: '0',
                width: '0px',
                height: '0px'
            });
            this.rangeLabelStyles = Array.from({ length: 4 }, () => msfsSdk.ObjectSubject.create({
                position: 'absolute',
                left: '0',
                right: '0',
                top: '0',
                bottom: '0'
            }));
            this.verticalRangeLineStyle = msfsSdk.ObjectSubject.create({
                display: 'none'
            });
            this.verticalRangeLabelTopStyle = msfsSdk.ObjectSubject.create({
                display: 'none',
                position: 'absolute',
                left: '0',
                bottom: '0'
            });
            this.verticalRangeLabelBottomStyle = msfsSdk.ObjectSubject.create({
                display: 'none',
                position: 'absolute',
                left: '0',
                top: '0'
            });
            this.referenceLineContainerStyle = msfsSdk.ObjectSubject.create({
                display: 'none',
                position: 'absolute',
                left: '0',
                top: '0',
                width: '0px',
                height: '0px',
                transform: 'rotate3d(0, 0, 1, 0deg)',
                'transform-origin': '0% 50%'
            });
            this.rootCssClass = msfsSdk.SetSubject.create(['weather-radar']);
            this.svgPathStream = new msfsSdk.SvgPathStream(0.1);
            this.svgTransformPathStream = new msfsSdk.AffineTransformPathStream(this.svgPathStream);
            this.horizontalScanAngularWidth = msfsSdk.SubscribableUtils.toSubscribable(this.props.horizontalScanAngularWidth, true);
            this.verticalScanAngularWidth = msfsSdk.SubscribableUtils.toSubscribable(this.props.verticalScanAngularWidth, true);
            this.size = msfsSdk.SubscribableUtils.toSubscribable(this.props.size, true);
            this.horizontalScanPadding = msfsSdk.SubscribableUtils.toSubscribable((_a = this.props.horizontalScanPadding) !== null && _a !== void 0 ? _a : WeatherRadar.DEFAULT_PADDING, true);
            this.verticalScanPadding = msfsSdk.SubscribableUtils.toSubscribable((_b = this.props.verticalScanPadding) !== null && _b !== void 0 ? _b : WeatherRadar.DEFAULT_PADDING, true);
            this.verticalRangeLineExtend = msfsSdk.SubscribableUtils.toSubscribable((_c = this.props.verticalRangeLineExtend) !== null && _c !== void 0 ? _c : WeatherRadar.DEFAULT_VERTICAL_RANGE_LINE_EXTEND, true);
            this.referenceLineAngularWidth = ((_d = this.props.referenceLineAngularWidth) !== null && _d !== void 0 ? _d : WeatherRadar.DEFAULT_BEARING_LINE_ANGULAR_WIDTH) * Avionics.Utils.DEG2RAD;
            this.scanMode = exports.WeatherRadarScanMode.Horizontal;
            this.isSwitchingScanMode = false;
            /** The [x, y] position, in pixels, of the center of the weather radar arc. */
            this.arcOrigin = msfsSdk.Vec2Math.create();
            /** The radius, in pixels, of the weather radar arc. */
            this.arcRadius = 0;
            /** The angular width, in radians, of the weather radar arc. */
            this.arcAngularWidth = 0;
            /** The bounding rect of the weather radar arc. */
            this.arcBounds = msfsSdk.VecNMath.create(4);
            this.overlayViewBox = msfsSdk.Subject.create('0 0 0 0');
            this.boundaryLinePath = msfsSdk.Subject.create('');
            this.rangeLinesPath = msfsSdk.Subject.create('');
            this.referenceLineContainerViewBox = msfsSdk.Subject.create('0 0 0 0');
            this.referenceLinePath = msfsSdk.Subject.create('');
            this.verticalRangeLinesPath = msfsSdk.Subject.create('');
            this.position = msfsSdk.ConsumerSubject.create(null, new LatLongAlt(), (a, b) => a.lat === b.lat && a.long === b.long);
            this.wxrMode = msfsSdk.Subject.create({ mode: EWeatherRadar.HORIZONTAL, arcRadians: msfsSdk.MathUtils.HALF_PI }, (a, b) => a.mode === b.mode && a.arcRadians === b.arcRadians);
            this.ranges = Array.from({ length: 4 }, () => msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.NMILE.createNumber(0)));
            this.verticalRangeTop = msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.FOOT.createNumber(0));
            this.verticalRangeBottom = msfsSdk.NumberUnitSubject.create(msfsSdk.UnitType.FOOT.createNumber(0));
            this.isReferenceLineVisible = msfsSdk.MappedSubject.create(([showBearing, showTilt, scanMode]) => {
                return scanMode === exports.WeatherRadarScanMode.Horizontal ? showBearing : showTilt;
            }, this.props.showBearingLine, this.props.showTiltLine, this.props.scanMode);
            this.colors = msfsSdk.SubscribableUtils.toSubscribable(this.props.colors, true);
            this.bingWxrColorsWorkingArray = [];
            this.bingWxrColors = msfsSdk.ArraySubject.create();
            this.needResize = false;
            this.needReposition = false;
            this.needUpdateBingWxrMode = false;
            this.needUpdateBingWxrColors = false;
            this.needRedrawOverlay = false;
            this.needRedrawVerticalRangeLines = false;
            this.needUpdateBing = false;
            this.needUpdateReferenceLineVisibility = false;
            this.needRotateReferenceLine = false;
            this.isAlive = true;
            this.isInit = false;
            this.isAwake = true;
        }
        /** @inheritdoc */
        onAfterRender() {
            this.sizeSub = this.size.sub(() => { this.needResize = true; }, true);
            this.horizontalScanPaddingSub = this.horizontalScanPadding.sub(() => {
                this.needReposition || (this.needReposition = this.props.scanMode.get() === exports.WeatherRadarScanMode.Horizontal);
            }, true);
            this.verticalScanPaddingSub = this.verticalScanPadding.sub(() => {
                this.needReposition || (this.needReposition = this.props.scanMode.get() === exports.WeatherRadarScanMode.Vertical);
            }, true);
            this.verticalRangeLineExtendSub = this.verticalRangeLineExtend.sub(() => {
                this.needRedrawVerticalRangeLines || (this.needRedrawVerticalRangeLines = this.props.scanMode.get() === exports.WeatherRadarScanMode.Vertical);
            }, true);
            this.operatingModeSub = this.props.operatingMode.sub(() => { this.needUpdateBing = true; }, true);
            this.isDataFailedSub = this.props.isDataFailed.sub(() => { this.needUpdateBing = true; }, true);
            this.scanModeSub = this.props.scanMode.sub(mode => {
                if (mode === exports.WeatherRadarScanMode.Horizontal) {
                    this.rootCssClass.delete('scan-vertical');
                    this.rootCssClass.add('scan-horizontal');
                }
                else {
                    this.rootCssClass.delete('scan-horizontal');
                    this.rootCssClass.add('scan-vertical');
                }
                this.needReposition = true;
                this.needUpdateBingWxrMode = true;
                this.needRotateReferenceLine = true;
            }, true);
            this.horizontalScanAngularWidthSub = this.horizontalScanAngularWidth.sub(() => {
                const isHorizontal = this.props.scanMode.get() === exports.WeatherRadarScanMode.Horizontal;
                this.needReposition || (this.needReposition = isHorizontal);
                this.needUpdateBingWxrMode || (this.needUpdateBingWxrMode = isHorizontal);
            }, true);
            this.verticalScanAngularWidthSub = this.verticalScanAngularWidth.sub(() => {
                const isVertical = this.props.scanMode.get() === exports.WeatherRadarScanMode.Vertical;
                this.needReposition || (this.needReposition = isVertical);
                this.needUpdateBingWxrMode || (this.needUpdateBingWxrMode = isVertical);
            }, true);
            this.rangeSub = this.props.range.sub(range => {
                for (let i = 0; i < this.ranges.length; i++) {
                    this.ranges[i].set(range.asUnit(msfsSdk.UnitType.NMILE) * (i + 1) / 4);
                }
                this.needUpdateBing = true;
                this.needRedrawVerticalRangeLines || (this.needRedrawVerticalRangeLines = this.props.scanMode.get() === exports.WeatherRadarScanMode.Vertical);
            }, true);
            this.position.setConsumer(this.props.bus.getSubscriber().on('gps-position'));
            this.position.sub(() => { this.needUpdateBing = true; }, true);
            this.isReferenceLineVisible.sub(() => {
                this.needUpdateReferenceLineVisibility = true;
            }, true);
            this.colorsSub = this.colors.sub(() => { this.needUpdateBingWxrColors = true; }, true);
            this.gainSub = this.props.gain.sub(() => { this.needUpdateBingWxrColors = true; }, true);
            this.isInit = true;
            if (!this.isAwake) {
                this.bingRef.instance.sleep();
            }
        }
        /**
         * Wakes this weather radar. Once awake, this radar will be able to update.
         * @throws Error if this weather radar is dead.
         */
        wake() {
            if (!this.isAlive) {
                throw new Error('WeatherRadar: cannot wake a dead component');
            }
            if (this.isAwake) {
                return;
            }
            this.isAwake = true;
            if (!this.isInit) {
                return;
            }
            this.bingRef.instance.wake();
        }
        /**
         * Puts this weather radar to sleep. Once asleep, this radar will not be able to update until it is awakened.
         * @throws Error if this weather radar is dead.
         */
        sleep() {
            if (!this.isAlive) {
                throw new Error('WeatherRadar: cannot sleep a dead component');
            }
            if (!this.isAwake) {
                return;
            }
            this.isAwake = false;
            if (!this.isInit) {
                return;
            }
            this.bingRef.instance.sleep();
        }
        /**
         * Updates this weather radar.
         * @throws Error if this weather radar is dead.
         */
        update() {
            if (!this.isAlive) {
                throw new Error('WeatherRadar: cannot update a dead component');
            }
            if (!this.isInit || !this.isAwake) {
                return;
            }
            if (this.needResize) {
                this.resizeContainer(this.size.get());
                this.needReposition = true;
            }
            if (this.needUpdateBingWxrMode) {
                this.updateBingWxrMode();
            }
            if (this.needReposition) {
                this.recomputePositioning();
                this.repositionBing();
                this.needRedrawOverlay = true;
            }
            if (this.needRedrawOverlay) {
                this.redrawOverlay();
            }
            else if (this.needRedrawVerticalRangeLines) {
                this.drawVerticalRangeLines(this.size.get(), this.arcOrigin, this.arcRadius, this.arcAngularWidth, this.props.range.get().asUnit(msfsSdk.UnitType.FOOT));
            }
            if (this.needUpdateBingWxrColors) {
                this.updateBingWxrColors();
            }
            if (this.needUpdateBing) {
                if (!this.props.isDataFailed.get() && this.props.operatingMode.get() === exports.WeatherRadarOperatingMode.Weather) {
                    this.bingStyle.set('display', this.isSwitchingScanMode ? 'none' : '');
                    const pos = this.position.get();
                    this.bingRef.instance.setPositionRadius(new LatLong(pos.lat, pos.long), this.props.range.get().asUnit(msfsSdk.UnitType.METER));
                }
                else {
                    this.bingStyle.set('display', 'none');
                }
            }
            if (this.needUpdateReferenceLineVisibility) {
                this.referenceLineContainerStyle.set('display', this.isReferenceLineVisible.get() ? '' : 'none');
            }
            if (this.needRotateReferenceLine) {
                this.referenceLineContainerStyle.set('transform', `rotate3d(0, 0, 1, ${this.props.scanMode.get() === exports.WeatherRadarScanMode.Horizontal ? -90 : 0}deg)`);
            }
            this.needResize = false;
            this.needReposition = false;
            this.needUpdateBingWxrMode = false;
            this.needUpdateBingWxrColors = false;
            this.needRedrawOverlay = false;
            this.needRedrawVerticalRangeLines = false;
            this.needUpdateBing = this.isSwitchingScanMode;
            this.isSwitchingScanMode = false;
            this.needUpdateReferenceLineVisibility = false;
            this.needRotateReferenceLine = false;
        }
        /**
         * Resizes this weather radar's root container.
         * @param size The size of the root container.
         */
        resizeContainer(size) {
            this.rootStyle.set('width', `${size[0]}px`);
            this.rootStyle.set('height', `${size[1]}px`);
        }
        /**
         * Updates this weather radar's Bing component weather mode.
         */
        updateBingWxrMode() {
            const scanMode = this.props.scanMode.get();
            if (scanMode !== this.scanMode) {
                this.scanMode = scanMode;
                this.needUpdateBing || (this.needUpdateBing = !this.isSwitchingScanMode);
                this.isSwitchingScanMode = true;
            }
            this.arcAngularWidth = (this.scanMode === exports.WeatherRadarScanMode.Horizontal
                ? this.horizontalScanAngularWidth.get()
                : this.verticalScanAngularWidth.get()) * Avionics.Utils.DEG2RAD;
            this.wxrMode.set({ mode: this.scanMode === exports.WeatherRadarScanMode.Horizontal ? EWeatherRadar.HORIZONTAL : EWeatherRadar.VERTICAL, arcRadians: this.arcAngularWidth });
        }
        /**
         * Recomputes the size and positioning of this weather radar's radar arc.
         */
        recomputePositioning() {
            var _a, _b, _c;
            const size = this.size.get();
            const width = size[0], height = size[1];
            const aspectRatio = 1 / (2 * Math.sin(this.arcAngularWidth / 2));
            if (this.scanMode === exports.WeatherRadarScanMode.Horizontal) {
                const padding = this.horizontalScanPadding.get();
                this.arcRadius = Math.max(0, Math.min(height - padding[1] - padding[3], (width - padding[0] - padding[2]) * aspectRatio));
                msfsSdk.Vec2Math.set((padding[0] + width - padding[2]) / 2, height - padding[3], this.arcOrigin);
                const arcWidth = this.arcRadius / aspectRatio;
                msfsSdk.VecNMath.set(this.arcBounds, this.arcOrigin[0] - arcWidth / 2, this.arcOrigin[1] - this.arcRadius, this.arcOrigin[0] + arcWidth / 2, this.arcOrigin[1]);
            }
            else {
                const padding = this.verticalScanPadding.get();
                this.arcRadius = Math.max(0, Math.min((width - padding[0] - padding[2]), (height - padding[1] - padding[3]) * aspectRatio));
                msfsSdk.Vec2Math.set(padding[0], (padding[1] + height - padding[3]) / 2, this.arcOrigin);
                const arcHeight = this.arcRadius / aspectRatio;
                msfsSdk.VecNMath.set(this.arcBounds, this.arcOrigin[0], this.arcOrigin[1] - arcHeight / 2, this.arcOrigin[0] + this.arcRadius, this.arcOrigin[1] + arcHeight / 2);
            }
            this.rootStyle.set('--weather-radar-arc-origin-x', `${this.arcOrigin[0]}px`);
            this.rootStyle.set('--weather-radar-arc-origin-y', `${this.arcOrigin[1]}px`);
            this.rootStyle.set('--weather-radar-arc-radius', `${this.arcRadius}px`);
            this.rootStyle.set('--weather-radar-arc-left', `${this.arcBounds[0]}px`);
            this.rootStyle.set('--weather-radar-arc-top', `${this.arcBounds[1]}px`);
            this.rootStyle.set('--weather-radar-arc-right', `${this.arcBounds[2]}px`);
            this.rootStyle.set('--weather-radar-arc-bottom', `${this.arcBounds[3]}px`);
            (_a = this.props.arcOrigin) === null || _a === void 0 ? void 0 : _a.set(this.arcOrigin);
            (_b = this.props.arcRadius) === null || _b === void 0 ? void 0 : _b.set(this.arcRadius);
            (_c = this.props.arcBounds) === null || _c === void 0 ? void 0 : _c.set(this.arcBounds);
        }
        /**
         * Repositions this weather radar's Bing component.
         */
        repositionBing() {
            const size = this.arcRadius * 2;
            this.bingStyle.set('width', `${size}px`);
            this.bingStyle.set('height', `${size}px`);
            this.bingStyle.set('left', `${this.arcOrigin[0] - size / 2}px`);
            this.bingStyle.set('top', `${this.arcOrigin[1] - size / 2}px`);
        }
        /**
         * Redraws this weather radar's overlay elements, including the radar arc boundary lines, range lines, and reference
         * line.
         */
        redrawOverlay() {
            const size = this.size.get();
            const isScanHorizontal = this.props.scanMode.get() === exports.WeatherRadarScanMode.Horizontal;
            this.overlayViewBox.set(`0 0 ${size[0]} ${size[1]}`);
            const facing = isScanHorizontal ? -msfsSdk.MathUtils.HALF_PI : 0;
            this.drawBoundaryLines(this.arcOrigin, this.arcRadius, facing, this.arcAngularWidth);
            this.drawRangeLines(size, this.arcOrigin, this.arcRadius, facing, this.arcAngularWidth);
            this.drawReferenceLine(this.arcOrigin, this.arcRadius);
            if (isScanHorizontal) {
                this.hideVerticalRangeLines();
            }
            else {
                this.drawVerticalRangeLines(size, this.arcOrigin, this.arcRadius, this.arcAngularWidth, this.props.range.get().asUnit(msfsSdk.UnitType.FOOT));
            }
        }
        /**
         * Draws radar arc boundary lines.
         * @param arcOrigin The position of the center of the radar arc, as `[x, y]` in pixels.
         * @param arcRadius The radius of the radar arc, in pixels.
         * @param facing The facing angle of the radar arc, in radians. An angle of `0` points in the positive x-direction,
         * with positive angles proceeding clockwise.
         * @param arcAngularWidth The angular width of the radar arc, in radians.
         */
        drawBoundaryLines(arcOrigin, arcRadius, facing, arcAngularWidth) {
            this.svgTransformPathStream
                .resetTransform()
                .addRotation(facing - arcAngularWidth / 2)
                .addTranslation(arcOrigin[0], arcOrigin[1]);
            this.svgTransformPathStream.beginPath();
            this.svgTransformPathStream.moveTo(arcRadius, 0);
            this.svgTransformPathStream.lineTo(0, 0);
            this.svgTransformPathStream.addRotation(arcAngularWidth, 'before');
            this.svgTransformPathStream.lineTo(arcRadius, 0);
            this.boundaryLinePath.set(this.svgPathStream.getSvgPath());
        }
        /**
         * Draws radar arc range lines.
         * @param size The size of this weather radar display, as `[width, height]` in pixels.
         * @param arcOrigin The position of the center of the radar arc, as `[x, y]` in pixels.
         * @param arcRadius The radius of the radar arc, in pixels.
         * @param facing The facing angle of the radar arc, in radians. An angle of `0` points in the positive x-direction,
         * with positive angles proceeding clockwise.
         * @param arcAngularWidth The angular width of the radar arc, in radians.
         */
        drawRangeLines(size, arcOrigin, arcRadius, facing, arcAngularWidth) {
            const leftAngle = facing - arcAngularWidth / 2;
            const rightAngle = leftAngle + arcAngularWidth;
            // set anchor point for range labels based on quadrant they appear in
            const useLeft = Math.sin(rightAngle) <= 0;
            const useTop = Math.cos(rightAngle) >= 0;
            const labelXToSet = useLeft ? 'left' : 'right';
            const labelXScale = useLeft ? 1 : -1;
            const labelXOffset = useLeft ? 0 : size[0];
            const labelYToSet = useTop ? 'top' : 'bottom';
            const labelYScale = useTop ? 1 : -1;
            const labelYOffset = useTop ? 0 : size[1];
            const labelXToClear = useLeft ? 'right' : 'left';
            const labelYToClear = useTop ? 'bottom' : 'top';
            const leftEndpoint = msfsSdk.Vec2Math.setFromPolar(1, leftAngle, WeatherRadar.vec2Cache[0]);
            const rightEndpoint = msfsSdk.Vec2Math.setFromPolar(1, rightAngle, WeatherRadar.vec2Cache[1]);
            this.svgPathStream.beginPath();
            for (let i = 4; i > 0; i--) {
                const radius = arcRadius * i / 4;
                this.svgPathStream.moveTo(leftEndpoint[0] * radius + arcOrigin[0], leftEndpoint[1] * radius + arcOrigin[1]);
                this.svgPathStream.arc(arcOrigin[0], arcOrigin[1], radius, leftAngle, rightAngle);
                const labelStyle = this.rangeLabelStyles[i - 1];
                labelStyle.set(labelXToSet, `${(rightEndpoint[0] * radius + arcOrigin[0]) * labelXScale + labelXOffset}px`);
                labelStyle.set(labelYToSet, `${(rightEndpoint[1] * radius + arcOrigin[1]) * labelYScale + labelYOffset}px`);
                labelStyle.set(labelXToClear, '');
                labelStyle.set(labelYToClear, '');
            }
            this.rangeLinesPath.set(this.svgPathStream.getSvgPath());
        }
        /**
         * Draws a reference line.
         * @param arcOrigin The position of the center of the radar arc, as `[x, y]` in pixels.
         * @param arcRadius The radius of the radar arc, in pixels.
         */
        drawReferenceLine(arcOrigin, arcRadius) {
            this.referenceLineContainerViewBox.set(`0 ${-arcRadius / 2} ${arcRadius} ${arcRadius}`);
            this.referenceLineContainerStyle.set('left', `${arcOrigin[0]}px`);
            this.referenceLineContainerStyle.set('top', `${arcOrigin[1] - arcRadius / 2}px`);
            this.referenceLineContainerStyle.set('width', `${arcRadius}px`);
            this.referenceLineContainerStyle.set('height', `${arcRadius}px`);
            this.svgTransformPathStream
                .resetTransform()
                .addRotation(-this.referenceLineAngularWidth / 2);
            this.svgTransformPathStream.beginPath();
            this.svgTransformPathStream.moveTo(arcRadius * 0.15, 0);
            this.svgTransformPathStream.lineTo(arcRadius, 0);
            this.svgTransformPathStream.addRotation(this.referenceLineAngularWidth, 'before');
            this.svgTransformPathStream.lineTo(arcRadius, 0);
            this.svgTransformPathStream.lineTo(arcRadius * 0.15, 0);
            this.svgTransformPathStream.closePath();
            this.referenceLinePath.set(this.svgPathStream.getSvgPath());
        }
        /**
         * Draws radar arc vertical range lines.
         * @param size The size of this weather radar display, as `[width, height]` in pixels.
         * @param arcOrigin The position of the center of the radar arc, as `[x, y]` in pixels.
         * @param arcRadius The radius of the radar arc, in pixels.
         * @param arcAngularWidth The angular width of the radar arc, in radians.
         * @param range The display range of this weather radar.
         */
        drawVerticalRangeLines(size, arcOrigin, arcRadius, arcAngularWidth, range) {
            const maxLineOffset = arcRadius * Math.sin(arcAngularWidth / 2) * 0.75;
            if (maxLineOffset <= 0) {
                this.hideVerticalRangeLines();
                return;
            }
            let height = WeatherRadar.VERTICAL_RANGE_MARKER_HEIGHT;
            let lineOffset = height / range * arcRadius;
            while (lineOffset > maxLineOffset) {
                lineOffset *= 0.5;
                height *= 0.5;
            }
            const lineExtend = this.verticalRangeLineExtend.get();
            const lineStartX = arcOrigin[0] + lineOffset / Math.tan(arcAngularWidth / 2) - lineExtend;
            const lineEndX = arcOrigin[0] + arcRadius + lineExtend;
            this.verticalRangeLinesPath.set(`M ${lineStartX} ${arcOrigin[1] + lineOffset} L ${lineEndX} ${arcOrigin[1] + lineOffset} M ${lineStartX} ${arcOrigin[1] - lineOffset} L ${lineEndX} ${arcOrigin[1] - lineOffset}`);
            this.verticalRangeLineStyle.set('display', '');
            this.verticalRangeLabelTopStyle.set('display', '');
            this.verticalRangeLabelTopStyle.set('left', `${lineEndX}px`);
            this.verticalRangeLabelTopStyle.set('bottom', `${size[1] - (arcOrigin[1] - lineOffset)}px`);
            this.verticalRangeLabelBottomStyle.set('display', '');
            this.verticalRangeLabelBottomStyle.set('left', `${lineEndX}px`);
            this.verticalRangeLabelBottomStyle.set('top', `${arcOrigin[1] + lineOffset}px`);
            this.verticalRangeTop.set(height);
            this.verticalRangeBottom.set(-height);
        }
        /**
         * Hides this weather radar's vertical range lines.
         */
        hideVerticalRangeLines() {
            this.verticalRangeLineStyle.set('display', 'none');
            this.verticalRangeLabelTopStyle.set('display', 'none');
            this.verticalRangeLabelBottomStyle.set('display', 'none');
        }
        /**
         * Updates this weather radar's Bing component weather colors.
         */
        updateBingWxrColors() {
            var _a;
            var _b;
            const colors = this.colors.get();
            const gain = this.props.gain.get();
            this.bingWxrColorsWorkingArray.length = colors.length;
            for (let i = 0; i < colors.length; i++) {
                const stop = colors[i];
                const bingStop = ((_a = (_b = this.bingWxrColorsWorkingArray)[i]) !== null && _a !== void 0 ? _a : (_b[i] = [0, 0]));
                bingStop[0] = stop[0];
                bingStop[1] = msfsSdk.MathUtils.round(WeatherRadarUtils.dbzToPrecipRate(stop[1] - gain), 0.01);
            }
            this.bingWxrColors.set(this.bingWxrColorsWorkingArray);
        }
        /** @inheritdoc */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: this.rootCssClass, style: this.rootStyle },
                msfsSdk.FSComponent.buildComponent("div", { class: 'weather-radar-bing-container', style: this.bingStyle },
                    msfsSdk.FSComponent.buildComponent(msfsSdk.BingComponent, { ref: this.bingRef, id: this.props.bingId, mode: EBingMode.PLANE, wxrMode: this.wxrMode, wxrColors: this.bingWxrColors })),
                msfsSdk.FSComponent.buildComponent("svg", { viewBox: this.overlayViewBox, class: 'weather-radar-overlay', style: 'position: absolute; left: 0; top: 0; width: 100%; height: 100%;' },
                    msfsSdk.FSComponent.buildComponent("path", { d: this.boundaryLinePath, class: 'weather-radar-boundary-line' }),
                    msfsSdk.FSComponent.buildComponent("path", { d: this.rangeLinesPath, class: 'weather-radar-range-lines' }),
                    msfsSdk.FSComponent.buildComponent("path", { d: this.verticalRangeLinesPath, class: 'weather-radar-vertical-range-lines', style: this.verticalRangeLineStyle })),
                msfsSdk.FSComponent.buildComponent("svg", { viewBox: this.referenceLineContainerViewBox, class: 'weather-radar-reference-line-container', style: this.referenceLineContainerStyle },
                    msfsSdk.FSComponent.buildComponent("path", { d: this.referenceLinePath, class: 'weather-radar-reference-line' })),
                msfsSdk.FSComponent.buildComponent("div", { style: this.verticalRangeLabelTopStyle },
                    msfsSdk.FSComponent.buildComponent(NumberUnitDisplay, { value: this.verticalRangeTop, displayUnit: null, formatter: msfsSdk.NumberFormatter.create({ precision: 1, showCommas: true, forceSign: true }), class: 'weather-radar-vertical-range-label weather-radar-vertical-range-label-top' })),
                msfsSdk.FSComponent.buildComponent("div", { style: this.verticalRangeLabelBottomStyle },
                    msfsSdk.FSComponent.buildComponent(NumberUnitDisplay, { value: this.verticalRangeBottom, displayUnit: null, formatter: msfsSdk.NumberFormatter.create({ precision: 1, showCommas: true, forceSign: true }), class: 'weather-radar-vertical-range-label weather-radar-vertical-range-label-bottom' })),
                this.ranges.map((range, index) => {
                    return (msfsSdk.FSComponent.buildComponent("div", { style: this.rangeLabelStyles[index] },
                        msfsSdk.FSComponent.buildComponent(NumberUnitDisplay, { ref: this.rangeLabelRefs[index], value: range, displayUnit: this.props.rangeUnit, formatter: msfsSdk.NumberFormatter.create({ precision: 0.1, forceDecimalZeroes: false, maxDigits: 3 }), class: `weather-radar-range-label weather-radar-range-label-${index + 1}` })));
                }),
                this.props.children));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
            super.destroy();
            this.isAlive = false;
            (_a = this.bingRef.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy();
            this.rangeLabelRefs.forEach(ref => { var _a; (_a = ref.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy(); });
            this.position.destroy();
            (_b = this.operatingModeSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.scanModeSub) === null || _c === void 0 ? void 0 : _c.destroy();
            (_d = this.horizontalScanAngularWidthSub) === null || _d === void 0 ? void 0 : _d.destroy();
            (_e = this.verticalScanAngularWidthSub) === null || _e === void 0 ? void 0 : _e.destroy();
            (_f = this.rangeSub) === null || _f === void 0 ? void 0 : _f.destroy();
            (_g = this.isDataFailedSub) === null || _g === void 0 ? void 0 : _g.destroy();
            (_h = this.sizeSub) === null || _h === void 0 ? void 0 : _h.destroy();
            (_j = this.horizontalScanPaddingSub) === null || _j === void 0 ? void 0 : _j.destroy();
            (_k = this.verticalScanPaddingSub) === null || _k === void 0 ? void 0 : _k.destroy();
            (_l = this.verticalRangeLineExtendSub) === null || _l === void 0 ? void 0 : _l.destroy();
            (_m = this.colorsSub) === null || _m === void 0 ? void 0 : _m.destroy();
            (_o = this.gainSub) === null || _o === void 0 ? void 0 : _o.destroy();
        }
    }
    WeatherRadar.DEFAULT_PADDING = msfsSdk.VecNMath.create(4); // px
    WeatherRadar.DEFAULT_VERTICAL_RANGE_LINE_EXTEND = 0; // px
    WeatherRadar.DEFAULT_BEARING_LINE_ANGULAR_WIDTH = 1; // degrees
    WeatherRadar.VERTICAL_RANGE_MARKER_HEIGHT = 60000; // feet
    WeatherRadar.vec2Cache = [msfsSdk.Vec2Math.create(), msfsSdk.Vec2Math.create()];

    /**
     * Keys for a DefaultWaypointIconImageCache.
     */
    exports.DefaultWaypointIconImageKey = void 0;
    (function (DefaultWaypointIconImageKey) {
        DefaultWaypointIconImageKey["AirportToweredServiced"] = "AIRPORT_TOWERED_SERVICED";
        DefaultWaypointIconImageKey["AirportToweredUnserviced"] = "AIRPORT_TOWERED_NONSERVICED";
        DefaultWaypointIconImageKey["AirportUntoweredServiced"] = "AIRPORT_NONTOWERED_SERVICED";
        DefaultWaypointIconImageKey["AirportUntoweredUnserviced"] = "AIRPORT_NONTOWERED_NONSERVICED";
        DefaultWaypointIconImageKey["AirportSmallServiced"] = "AIRPORT_SMALL_SERVICED";
        DefaultWaypointIconImageKey["AirportSmallUnserviced"] = "AIRPORT_SMALL_NONSERVICED";
        DefaultWaypointIconImageKey["AirportPrivate"] = "AIRPORT_PRIVATE";
        DefaultWaypointIconImageKey["AirportUnknown"] = "AIRPORT_UNKNOWN";
        DefaultWaypointIconImageKey["Vor"] = "VOR";
        DefaultWaypointIconImageKey["VorDme"] = "VORDME";
        DefaultWaypointIconImageKey["Vortac"] = "VORTAC";
        DefaultWaypointIconImageKey["Tacan"] = "TACAN";
        DefaultWaypointIconImageKey["DmeOnly"] = "DME";
        DefaultWaypointIconImageKey["Ndb"] = "NDB";
        DefaultWaypointIconImageKey["Intersection"] = "INTERSECTION";
        DefaultWaypointIconImageKey["User"] = "USER";
        DefaultWaypointIconImageKey["FlightPath"] = "FPLN";
        DefaultWaypointIconImageKey["VNav"] = "VNAV";
    })(exports.DefaultWaypointIconImageKey || (exports.DefaultWaypointIconImageKey = {}));
    /**
     * A default implementation of {@link WaypointIconImageCache}.
     */
    class DefaultWaypointIconImageCache {
        constructor() {
            this.cache = new Map();
        }
        /**
         * Registers an image with this cache.
         * @param key The key of the image to register.
         * @param src The source URI of the image to register.
         */
        register(key, src) {
            const img = new Image();
            img.src = src;
            this.cache.set(key, img);
        }
        /**
         * Retrieves an image from this cache.
         * @param key The key of the image to retrieve.
         * @returns The image registered under the specified key, or `undefined` if one could not be found.
         */
        get(key) {
            return this.cache.get(key);
        }
        /** @inheritdoc */
        getForWaypoint(waypoint) {
            if (msfsSdk.FacilityWaypointUtils.isFacilityWaypoint(waypoint)) {
                switch (msfsSdk.ICAO.getFacilityType(waypoint.facility.get().icao)) {
                    case msfsSdk.FacilityType.Airport:
                        return this.getForAirport(waypoint);
                    case msfsSdk.FacilityType.VOR:
                        return this.getForVor(waypoint);
                    case msfsSdk.FacilityType.NDB:
                        return this.get(exports.DefaultWaypointIconImageKey.Ndb);
                    case msfsSdk.FacilityType.Intersection:
                    case msfsSdk.FacilityType.RWY:
                    case msfsSdk.FacilityType.VIS:
                        return this.get(exports.DefaultWaypointIconImageKey.Intersection);
                    case msfsSdk.FacilityType.USR:
                        return this.get(exports.DefaultWaypointIconImageKey.User);
                }
            }
            else if (waypoint instanceof msfsSdk.FlightPathWaypoint) {
                return this.get(exports.DefaultWaypointIconImageKey.FlightPath);
            }
            else if (waypoint instanceof msfsSdk.VNavWaypoint) {
                return this.get(exports.DefaultWaypointIconImageKey.VNav);
            }
        }
        /**
         * Retrieves an image for an airport.
         * @param airport The airport for which to retrieve the image.
         * @returns The image for the specified airport, or `undefined` if one could not be found.
         */
        getForAirport(airport) {
            const fac = airport.facility.get();
            // HINT class 1 airports are always assumed serviced
            const serviced = (fac.fuel1 !== '' || fac.fuel2 !== '') || fac.airportClass === 1;
            if (fac.airportPrivateType !== msfsSdk.AirportPrivateType.Public) {
                return this.get(exports.DefaultWaypointIconImageKey.AirportPrivate);
            }
            else if (serviced && fac.towered) {
                return this.get(exports.DefaultWaypointIconImageKey.AirportToweredServiced);
            }
            else if (serviced && !fac.towered) {
                if (fac.airportClass === 1) {
                    return this.get(exports.DefaultWaypointIconImageKey.AirportUntoweredServiced);
                }
                else {
                    return this.get(exports.DefaultWaypointIconImageKey.AirportSmallServiced);
                }
            }
            else if (!serviced && fac.towered) {
                return this.get(exports.DefaultWaypointIconImageKey.AirportToweredUnserviced);
            }
            else if (!serviced && !fac.towered) {
                if (fac.airportClass === 1) {
                    return this.get(exports.DefaultWaypointIconImageKey.AirportUntoweredUnserviced);
                }
                else {
                    return this.get(exports.DefaultWaypointIconImageKey.AirportSmallUnserviced);
                }
            }
            else {
                return this.get(exports.DefaultWaypointIconImageKey.AirportUnknown);
            }
        }
        /**
         * Retrieves an image for a VOR.
         * @param vor The VOR for which to retrieve the image.
         * @returns The image for the specified VOR, or `undefined` if one could not be found.
         */
        getForVor(vor) {
            switch (vor.facility.get().type) {
                case msfsSdk.VorType.DME:
                    return this.get(exports.DefaultWaypointIconImageKey.DmeOnly);
                case msfsSdk.VorType.ILS:
                case msfsSdk.VorType.VORDME:
                    return this.get(exports.DefaultWaypointIconImageKey.VorDme);
                case msfsSdk.VorType.VORTAC:
                case msfsSdk.VorType.TACAN:
                    return this.get(exports.DefaultWaypointIconImageKey.Vortac);
                default:
                    return this.get(exports.DefaultWaypointIconImageKey.Vor);
            }
        }
    }

    /**
     * A default implementation of {@link MinimumsDataProvider}.
     */
    class DefaultMinimumsDataProvider {
        /**
         * Constructor.
         * @param bus The event bus.
         * @param supportRadarMinimums Whether to support radar minimums.
         */
        constructor(bus, supportRadarMinimums) {
            this.bus = bus;
            this.supportRadarMinimums = supportRadarMinimums;
            this._mode = msfsSdk.ConsumerSubject.create(null, msfsSdk.MinimumsMode.OFF);
            this.mode = this._mode;
            this._minimums = msfsSdk.Subject.create(null);
            /** @inheritdoc */
            this.minimums = this._minimums;
            this.baroMinimumsSource = msfsSdk.ConsumerSubject.create(null, 0);
            /** @inheritdoc */
            this.baroMinimums = this.baroMinimumsSource;
            this.radarMinimumsSource = msfsSdk.ConsumerSubject.create(null, 0);
            /** @inheritdoc */
            this.radarMinimums = this.radarMinimumsSource;
            this.isInit = false;
            this.isAlive = true;
            this.isPaused = false;
        }
        /**
         * Initializes this data provider. Once initialized, this data provider will continuously update its data until
         * paused or destroyed.
         * @param paused Whether to initialize this data provider as paused. If `true`, this data provider will provide an
         * initial set of data but will not update the provided data until it is resumed. Defaults to `false`.
         * @throws Error if this data provider is dead.
         */
        init(paused = false) {
            if (!this.isAlive) {
                throw new Error('DefaultMinimumsDataProvider: cannot initialize a dead provider');
            }
            if (this.isInit) {
                return;
            }
            this.isInit = true;
            this.isPaused = paused;
            const sub = this.bus.getSubscriber();
            this._mode.setConsumer(sub.on('minimums_mode'));
            this.baroMinimumsSource.setConsumer(sub.on('decision_altitude_feet'));
            const baroMinimums = msfsSdk.MappedSubject.create(([minimumsMode, baroMinimumsSource]) => {
                return minimumsMode === msfsSdk.MinimumsMode.BARO ? baroMinimumsSource : null;
            }, this._mode, this.baroMinimumsSource);
            if (this.supportRadarMinimums) {
                this.radarMinimumsSource.setConsumer(sub.on('decision_height_feet'));
                baroMinimums.pause();
                const baroPipe = baroMinimums.pipe(this._minimums, true);
                const radarMinimums = msfsSdk.MappedSubject.create(([minimumsMode, radarMinimumsSource]) => {
                    return minimumsMode === msfsSdk.MinimumsMode.RA ? radarMinimumsSource : null;
                }, this._mode, this.radarMinimumsSource);
                radarMinimums.pause();
                const radarPipe = radarMinimums.pipe(this._minimums, true);
                this._mode.sub(minimumsMode => {
                    baroMinimums.pause();
                    baroPipe.pause();
                    radarMinimums.pause();
                    radarPipe.pause();
                    switch (minimumsMode) {
                        case msfsSdk.MinimumsMode.BARO:
                            baroMinimums.resume();
                            baroPipe.resume(true);
                            break;
                        case msfsSdk.MinimumsMode.RA:
                            radarMinimums.resume();
                            radarPipe.resume(true);
                            break;
                        default:
                            this._minimums.set(null);
                    }
                }, true);
            }
            else {
                baroMinimums.pipe(this._minimums);
            }
            if (paused) {
                this.pause();
            }
        }
        /**
         * Resumes this data provider. Once resumed, this data provider will continuously update its data until paused or
         * destroyed.
         * @throws Error if this data provider is dead.
         */
        resume() {
            if (!this.isAlive) {
                throw new Error('DefaultMinimumsDataProvider: cannot resume a dead provider');
            }
            if (!this.isPaused || !this.isInit) {
                return;
            }
            this.isPaused = false;
            this._mode.resume();
            this.baroMinimumsSource.resume();
            this.radarMinimumsSource.resume();
        }
        /**
         * Pauses this data provider. Once paused, this data provider will not update its data until it is resumed.
         * @throws Error if this data provider is dead.
         */
        pause() {
            if (!this.isAlive) {
                throw new Error('DefaultMinimumsDataProvider: cannot pause a dead provider');
            }
            if (this.isPaused || !this.isInit) {
                return;
            }
            this._mode.pause();
            this.baroMinimumsSource.pause();
            this.radarMinimumsSource.pause();
            this.isPaused = true;
        }
        /**
         * Destroys this data provider. Once destroyed, this data provider will no longer update its provided data, and can
         * no longer be paused or resumed.
         */
        destroy() {
            this.isAlive = false;
            this._mode.destroy();
            this.baroMinimumsSource.destroy();
            this.radarMinimumsSource.destroy();
        }
    }

    /**
     * A manager for minimums units. Keeps the minimums unit in sync with the Garmin altitude display units setting.
     */
    class MinimumsUnitsManager {
        /**
         * Constructor.
         * @param bus The event bus.
         */
        constructor(bus) {
            this.bus = bus;
            this.publisher = this.bus.getPublisher();
            this.altitudeUnitsSetting = UnitsUserSettings.getManager(this.bus).getSetting('unitsAltitude');
            this.isAlive = true;
            this.isInit = false;
        }
        /**
         * Initializes this manager. Once initialized, this manager will automatically keep the minimums unit in sync with
         * the Garmin altitude display units setting until it is destroyed.
         * @throws Error if this manager has been destroyed.
         */
        init() {
            if (!this.isAlive) {
                throw new Error('MinimumsUnitsManager: cannot initialize a dead manager');
            }
            if (this.isInit) {
                return;
            }
            this.isInit = true;
            this.altitudeUnitsSub = this.altitudeUnitsSetting.sub(mode => {
                if (mode === exports.UnitsAltitudeSettingMode.Meters) {
                    this.publisher.pub('set_da_distance_unit', 'meters', true, false);
                }
                else {
                    this.publisher.pub('set_da_distance_unit', 'feet', true, false);
                }
            }, true);
        }
        /**
         * Destroys this manager.
         */
        destroy() {
            var _a;
            this.isAlive = false;
            (_a = this.altitudeUnitsSub) === null || _a === void 0 ? void 0 : _a.destroy();
        }
    }

    /**
     * An abstract implementation of {@link NavReferenceBase}.
     */
    class AbstractNavReferenceBase {
        constructor() {
            /** @inheritdoc */
            this.ident = msfsSdk.Subject.create(null);
            /** @inheritdoc */
            this.signalStrength = msfsSdk.Subject.create(null);
            /** @inheritdoc */
            this.bearing = msfsSdk.Subject.create(null);
            /** @inheritdoc */
            this.distance = msfsSdk.Subject.create(null);
            /** @inheritdoc */
            this.course = msfsSdk.Subject.create(null);
            /** @inheritdoc */
            this.localizerCourse = msfsSdk.Subject.create(null);
            // This is a hacky way to get a mutable subscribable to support both input and output type GeoPointInterface | null
            // without requiring a new GeoPoint object be created with every call to .set(). We maintain two instances of
            // GeoPoint, and when we detect that a new non-null input is different from the current value, we swap the two
            // GeoPoint instances, making sure that the incoming instance is set equal to the input. If an input is equal to the
            // value, we leave the GeoPoint instances in place. Because ComputedSubject uses strict equality checks, this ensures
            // that subscribers will be notified if and only if the input is different from the current value.
            this._locationRefs = [new msfsSdk.GeoPoint(0, 0), new msfsSdk.GeoPoint(0, 0)];
            this._locationRefPointer = 0;
            /** @inheritdoc */
            this.location = msfsSdk.ComputedSubject.create(null, input => {
                if (input === null) {
                    return null;
                }
                else {
                    if (this._locationRefs[this._locationRefPointer].equals(input)) {
                        return this._locationRefs[this._locationRefPointer];
                    }
                    else {
                        this._locationRefPointer = (this._locationRefPointer + 1) % 2;
                        return this._locationRefs[this._locationRefPointer].set(input);
                    }
                }
            });
            /** @inheritdoc */
            this.isLocalizer = msfsSdk.Subject.create(null);
            /** @inheritdoc */
            this.hasNav = msfsSdk.Subject.create(null);
            /** @inheritdoc */
            this.hasDme = msfsSdk.Subject.create(null);
            /** @inheritdoc */
            this.hasLocalizer = msfsSdk.Subject.create(null);
            /** @inheritdoc */
            this.hasGlideSlope = msfsSdk.Subject.create(null);
            /** @inheritdoc */
            this.activeFrequency = msfsSdk.Subject.create(null);
            /** @inheritdoc */
            this.toFrom = msfsSdk.Subject.create(null);
            /** @inheritdoc */
            this.lateralDeviation = msfsSdk.Subject.create(null);
            /** @inheritdoc */
            this.lateralDeviationScale = msfsSdk.Subject.create(null);
            /** @inheritdoc */
            this.lateralDeviationScalingMode = msfsSdk.Subject.create(null);
            /** @inheritdoc */
            this.verticalDeviation = msfsSdk.Subject.create(null);
            /** @inheritdoc */
            this.verticalDeviationScale = msfsSdk.Subject.create(null);
            this.fields = new Map([
                ['ident', this.ident],
                ['signalStrength', this.signalStrength],
                ['bearing', this.bearing],
                ['distance', this.distance],
                ['course', this.course],
                ['localizerCourse', this.localizerCourse],
                ['location', this.location],
                ['isLocalizer', this.isLocalizer],
                ['hasDme', this.hasDme],
                ['hasNav', this.hasNav],
                ['hasLocalizer', this.hasLocalizer],
                ['hasGlideSlope', this.hasGlideSlope],
                ['activeFrequency', this.activeFrequency],
                ['toFrom', this.toFrom],
                ['lateralDeviation', this.lateralDeviation],
                ['lateralDeviationScale', this.lateralDeviationScale],
                ['lateralDeviationScalingMode', this.lateralDeviationScalingMode],
                ['verticalDeviation', this.verticalDeviation],
                ['verticalDeviationScale', this.verticalDeviationScale],
            ]);
        }
        /**
         * Sets all fields to `null`.
         */
        clearAll() {
            for (const field of this.fields.values()) {
                field.set(null);
            }
        }
    }

    /**
     * A basic implementation of {@link NavReferenceIndicator} whose data is derived directly from its source.
     */
    class BasicNavReferenceIndicator extends AbstractNavReferenceBase {
        /**
         * Creates a new instance of BasicNavReferenceIndicator.
         * @param navSources The possible nav sources from which this indicator can derive data.
         * @param sourceName The initial source to use, if any.
         */
        constructor(navSources, sourceName = null) {
            super();
            this.navSources = navSources;
            this._source = msfsSdk.Subject.create(null);
            /** @inheritdoc */
            this.source = this._source;
            this.sourceSubs = [];
            this.setSource(sourceName);
        }
        /** @inheritdoc */
        setSource(sourceName) {
            const oldSource = this.source.get();
            if (oldSource && oldSource.name === sourceName) {
                return;
            }
            if (oldSource === null && sourceName === null) {
                return;
            }
            const newSource = (sourceName ? this.navSources.get(sourceName) : null);
            this._source.set(newSource);
            this.updateFromSource(this._source.get(), oldSource);
        }
        /**
         * Updates this nav indicator from a new source.
         * @param newSource The new nav source.
         * @param oldSource The old nav source.
         */
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        updateFromSource(newSource, oldSource) {
            this.sourceSubs.forEach(sub => { sub.destroy(); });
            this.sourceSubs.length = 0;
            if (newSource) {
                this.fields.forEach((field, key) => {
                    this.sourceSubs.push(newSource[key].pipe(field));
                });
            }
            else {
                this.clearAll();
            }
        }
    }
    /**
     * A basic implementation of {@link NavReferenceIndicators} which stores the indicators in a Map.
     * @template SourceName The names of the nav sources supported by the nav indicators contained in the collection.
     * @template IndicatorName The names of the nav indicators contained in the collection.
     */
    class NavReferenceIndicatorsCollection {
        /**
         * Creates a new instance of NavReferenceIndicatorsCollection.
         * @param indicators A map of this collection's nav indicators, keyed by name.
         */
        constructor(indicators = new Map()) {
            this.indicators = indicators;
        }
        /** @inheritdoc */
        get(name) {
            const indicator = this.indicators.get(name);
            if (!indicator) {
                throw new Error('NavReferenceIndicatorsCollection: no nav indicator exists with the name: ' + name);
            }
            else {
                return indicator;
            }
        }
    }

    /**
     * A {@link NavReferenceSource} which derives its data from an ADF radio signal.
     */
    class AdfRadioNavSource extends AbstractNavReferenceBase {
        /**
         * Creates a new instance of AdfRadioNavSource.
         * @param bus The event bus.
         * @param name The name of this source.
         * @param index The index of this source.
         */
        constructor(bus, name, index) {
            super();
            this.name = name;
            this.index = index;
            const navComSubscriber = bus.getSubscriber();
            this.signal = msfsSdk.ConsumerSubject.create(navComSubscriber.on(`adf_signal_${index}`), 0);
            this.relativeBearing = msfsSdk.ConsumerSubject.create(navComSubscriber.on(`adf_bearing_${index}`), 0);
            const tempLocation = new msfsSdk.GeoPoint(0, 0);
            // Pretty sure there is no NDB at {0 N, 0 E}, so we can safely assume if we ever get that data from the sim there
            // is no valid tuned station.
            navComSubscriber.on(`adf_lla_${index}`).handle(val => {
                if (val.lat === 0 && val.long === 0) {
                    this.location.set(null);
                }
                else {
                    this.location.set(tempLocation.set(val.lat, val.long));
                }
            });
            const ahrs = bus.getSubscriber();
            this.heading = msfsSdk.ConsumerSubject.create(ahrs.on('hdg_deg'), 0);
            const navComSimVarsSubscriber = bus.getSubscriber();
            navComSimVarsSubscriber.on(`adf_active_frequency_${index}`).handle(val => { this.activeFrequency.set(val); });
            const bearing = msfsSdk.MappedSubject.create(([relativeBearing, heading]) => {
                return msfsSdk.NavMath.normalizeHeading(relativeBearing + heading);
            }, this.relativeBearing, this.heading).pause();
            this.signal.pipe(this.signalStrength);
            const bearingPipe = bearing.pipe(this.bearing, true);
            this.signal.sub(signal => {
                if (signal > 0) {
                    bearing.resume();
                    bearingPipe.resume(true);
                }
                else {
                    bearing.pause();
                    bearingPipe.pause();
                    this.bearing.set(null);
                }
            }, true);
        }
        /** @inheritdoc */
        getType() {
            return msfsSdk.NavSourceType.Adf;
        }
    }

    /* eslint-disable @typescript-eslint/no-non-null-assertion */
    /**
     * A {@link NavReferenceSource} which derives its data from LNAV.
     */
    class GpsNavSource extends AbstractNavReferenceBase {
        /**
         * Creates a new instance of GpsNavSource.
         * @param bus The event bus.
         * @param name The name of this source.
         * @param index The index of this source.
         * @param options Options with which to configure the source.
         */
        constructor(bus, name, index, options) {
            var _a, _b;
            super();
            this.name = name;
            this.index = index;
            this.lnavIsTracking = msfsSdk.Subject.create(false);
            this.lnavIsTrackingSource = msfsSdk.ConsumerSubject.create(null, false);
            this.lnavIdent = msfsSdk.ConsumerSubject.create(null, '').pause();
            this.lnavBrgMag = msfsSdk.ConsumerSubject.create(null, 0).pause();
            this.lnavDis = msfsSdk.ConsumerSubject.create(null, 0).pause();
            this.lnavDtkMag = msfsSdk.ConsumerSubject.create(null, 0).pause();
            this.lnavXtk = msfsSdk.ConsumerSubject.create(null, 0).pause();
            this.lnavToFrom = msfsSdk.ConsumerSubject.create(null, msfsSdk.VorToFrom.OFF).pause();
            this.lnavCdiScaleLabel = msfsSdk.ConsumerSubject.create(null, exports.CDIScaleLabel.Enroute);
            this.lnavCdiScale = msfsSdk.ConsumerSubject.create(null, 0);
            this.gpAvailable = msfsSdk.ConsumerSubject.create(null, false);
            this.gpDeviation = msfsSdk.ConsumerSubject.create(null, 0);
            this.gpScale = msfsSdk.ConsumerSubject.create(null, 0);
            this.lnavIndex = msfsSdk.SubscribableUtils.toSubscribable((_a = options === null || options === void 0 ? void 0 : options.lnavIndex) !== null && _a !== void 0 ? _a : 0, true);
            this.vnavIndex = msfsSdk.SubscribableUtils.toSubscribable((_b = options === null || options === void 0 ? void 0 : options.vnavIndex) !== null && _b !== void 0 ? _b : 0, true);
            const lnav = bus.getSubscriber();
            const lnavIsTrackingPipe = this.lnavIsTrackingSource.pipe(this.lnavIsTracking, true);
            const cdiScaleLabelPipe = this.lnavCdiScaleLabel.pipe(this.lateralDeviationScalingMode, true);
            const cdiScalePipe = this.lnavCdiScale.pipe(this.lateralDeviationScale, true);
            const lateralDeviation = msfsSdk.MappedSubject.create(([xtk, scale]) => {
                return scale !== 0 ? -xtk / scale : null;
            }, this.lnavXtk, this.lnavCdiScale).pause();
            const identPipe = this.lnavIdent.pipe(this.ident, true);
            const bearingPipe = this.lnavBrgMag.pipe(this.bearing, true);
            const distancePipe = this.lnavDis.pipe(this.distance, true);
            const dtkPipe = this.lnavDtkMag.pipe(this.course, true);
            const lateralDeviationPipe = lateralDeviation.pipe(this.lateralDeviation, true);
            const toFromPipe = this.lnavToFrom.pipe(this.toFrom, true);
            this.lnavIsTracking.sub(isTracking => {
                if (isTracking) {
                    this.lnavIdent.resume();
                    this.lnavBrgMag.resume();
                    this.lnavDis.resume();
                    this.lnavDtkMag.resume();
                    this.lnavXtk.resume();
                    lateralDeviation.resume();
                    this.lnavToFrom.resume();
                    identPipe.resume(true);
                    bearingPipe.resume(true);
                    distancePipe.resume(true);
                    dtkPipe.resume(true);
                    lateralDeviationPipe.resume(true);
                    toFromPipe.resume(true);
                    this.signalStrength.set(1);
                }
                else {
                    this.signalStrength.set(0);
                    this.lnavIdent.pause();
                    this.lnavBrgMag.pause();
                    this.lnavDis.pause();
                    this.lnavDtkMag.pause();
                    this.lnavXtk.pause();
                    lateralDeviation.pause();
                    this.lnavToFrom.pause();
                    identPipe.pause();
                    bearingPipe.pause();
                    distancePipe.pause();
                    dtkPipe.pause();
                    lateralDeviationPipe.pause();
                    toFromPipe.pause();
                    this.ident.set(null);
                    this.bearing.set(null);
                    this.distance.set(null);
                    this.course.set(null);
                    this.lateralDeviation.set(null);
                    this.toFrom.set(null);
                }
            }, true);
            this.lnavIndex.sub(lnavIndex => {
                if (msfsSdk.LNavUtils.isValidLNavIndex(index)) {
                    const suffix = msfsSdk.LNavUtils.getEventBusTopicSuffix(lnavIndex);
                    this.lnavIsTrackingSource.setConsumer(lnav.on(`lnav_is_tracking${suffix}`));
                    this.lnavIdent.setConsumer(lnav.on(`lnavdata_waypoint_ident${suffix}`));
                    this.lnavBrgMag.setConsumer(lnav.on(`lnavdata_waypoint_bearing_mag${suffix}`));
                    this.lnavDis.setConsumer(lnav.on(`lnavdata_waypoint_distance${suffix}`));
                    this.lnavDtkMag.setConsumer(lnav.on(`lnavdata_dtk_mag${suffix}`));
                    this.lnavXtk.setConsumer(lnav.on(`lnavdata_xtk${suffix}`));
                    this.lnavToFrom.setConsumer(lnav.on(`lnavdata_tofrom${suffix}`));
                    this.lnavCdiScaleLabel.setConsumer(lnav.on(`lnavdata_cdi_scale_label${suffix}`));
                    this.lnavCdiScale.setConsumer(lnav.on(`lnavdata_cdi_scale${suffix}`));
                    cdiScaleLabelPipe.resume(true);
                    cdiScalePipe.resume(true);
                    lnavIsTrackingPipe.resume(true);
                }
                else {
                    cdiScaleLabelPipe.pause();
                    cdiScalePipe.pause();
                    lnavIsTrackingPipe.pause();
                    this.lnavIsTrackingSource.setConsumer(null);
                    this.lnavIdent.setConsumer(null);
                    this.lnavBrgMag.setConsumer(null);
                    this.lnavDis.setConsumer(null);
                    this.lnavDtkMag.setConsumer(null);
                    this.lnavXtk.setConsumer(null);
                    this.lnavToFrom.setConsumer(null);
                    this.lnavCdiScaleLabel.setConsumer(null);
                    this.lnavCdiScale.setConsumer(null);
                    this.lnavIsTracking.set(false);
                    this.signalStrength.set(0);
                    this.lateralDeviationScalingMode.set(null);
                    this.lateralDeviationScale.set(null);
                }
            }, true);
            const vnav = bus.getSubscriber();
            const gpScalePipe = this.gpScale.pipe(this.verticalDeviationScale, scale => scale <= 0 ? null : scale, true);
            const verticalDeviation = msfsSdk.MappedSubject.create(([gpDeviation, scale]) => {
                return scale !== 0 ? gpDeviation / scale : null;
            }, this.gpDeviation, this.gpScale).pause();
            const verticalDeviationPipe = verticalDeviation.pipe(this.verticalDeviation, true);
            const gpAvailableSub = this.gpAvailable.sub(isGpAvailable => {
                if (isGpAvailable) {
                    verticalDeviation.resume();
                    verticalDeviationPipe.resume(true);
                }
                else {
                    verticalDeviation.pause();
                    verticalDeviationPipe.pause();
                    this.verticalDeviation.set(null);
                }
            }, false, true);
            this.vnavIndex.sub(vnavIndex => {
                if (msfsSdk.VNavUtils.isValidVNavIndex(index)) {
                    const suffix = msfsSdk.VNavUtils.getEventBusTopicSuffix(vnavIndex);
                    this.gpAvailable.setConsumer(vnav.on(`gp_available${suffix}`));
                    this.gpDeviation.setConsumer(vnav.on(`gp_vertical_deviation${suffix}`));
                    this.gpScale.setConsumer(vnav.on(`gp_gsi_scaling${suffix}`));
                    gpScalePipe.resume(true);
                    gpAvailableSub.resume(true);
                }
                else {
                    gpScalePipe.pause();
                    gpAvailableSub.pause();
                    verticalDeviation.pause();
                    verticalDeviationPipe.pause();
                    this.gpAvailable.setConsumer(null);
                    this.gpDeviation.setConsumer(null);
                    this.gpScale.setConsumer(null);
                    this.verticalDeviation.set(null);
                    this.verticalDeviationScale.set(null);
                }
            }, true);
        }
        /** @inheritdoc */
        getType() {
            return msfsSdk.NavSourceType.Gps;
        }
    }

    /* eslint-disable @typescript-eslint/no-non-null-assertion */
    /**
     * A {@link NavReferenceSource} which derives its data from a NAV radio signal.
     */
    class NavRadioNavSource extends AbstractNavReferenceBase {
        /**
         * Creates a new instance of NavRadioNavSource.
         * @param bus The event bus.
         * @param name The name of this source.
         * @param index The index of this source.
         */
        constructor(bus, name, index) {
            super();
            this.name = name;
            this.index = index;
            this.hasSignal = this.signalStrength.map(signalStrength => signalStrength !== null && signalStrength > 0);
            this.glideSlopeErrorDegrees = msfsSdk.Subject.create(0);
            this.navLocalizerCrsRad = msfsSdk.Subject.create(0);
            this.navCdi = msfsSdk.Subject.create(0);
            const navComSubscriber = bus.getSubscriber();
            navComSubscriber.on(`nav_signal_${index}`).handle(val => { this.signalStrength.set(val); });
            navComSubscriber.on(`nav_has_dme_${index}`).handle(val => { this.hasDme.set(val); });
            navComSubscriber.on(`nav_has_nav_${index}`).handle(val => { this.hasNav.set(val); });
            navComSubscriber.on(`nav_ident_${index}`).handle(val => { this.ident.set(val); });
            navComSubscriber.on(`nav_localizer_${index}`).handle(val => { this.hasLocalizer.set(val); });
            navComSubscriber.on(`nav_localizer_crs_${index}`).handle(val => { this.navLocalizerCrsRad.set(val); });
            navComSubscriber.on(`nav_gs_error_${index}`).handle(val => { this.glideSlopeErrorDegrees.set(val); });
            navComSubscriber.on(`nav_glideslope_${index}`).handle(val => { this.hasGlideSlope.set(val); });
            navComSubscriber.on(`nav_obs_${index}`).handle(val => { this.course.set(val); });
            navComSubscriber.on(`nav_cdi_${index}`).handle(val => { this.navCdi.set(val); });
            navComSubscriber.on(`nav_active_frequency_${index}`).handle(val => { this.activeFrequency.set(val); });
            this.vorLla = msfsSdk.ConsumerSubject.create(navComSubscriber.on(`nav_lla_${index}`), new LatLongAlt(0, 0), (a, b) => {
                return a.lat === b.lat && a.long === b.long;
            }).pause();
            this.dmeLla = msfsSdk.ConsumerSubject.create(navComSubscriber.on(`nav_dme_lla_${index}`), new LatLongAlt(0, 0), (a, b) => {
                return a.lat === b.lat && a.long === b.long;
            }).pause();
            const tempLocation = new msfsSdk.GeoPoint(0, 0);
            // Pretty sure there is no VOR at {0 N, 0 E}, so we can safely assume if we ever get that data from the sim there
            // is no valid tuned station.
            const vorLlaPipe = this.vorLla.pipe(this.location, lla => {
                if (lla.lat === 0 && lla.long === 0) {
                    return null;
                }
                else {
                    return tempLocation.set(lla.lat, lla.long);
                }
            }, true);
            const dmeLlaPipe = this.dmeLla.pipe(this.location, lla => {
                if (lla.lat === 0 && lla.long === 0) {
                    return null;
                }
                else {
                    return tempLocation.set(lla.lat, lla.long);
                }
            }, true);
            const locationState = msfsSdk.MappedSubject.create(this.hasLocalizer, this.hasNav, this.hasDme);
            locationState.sub(([hasLoc, hasNav, hasDme]) => {
                if (hasLoc || hasNav) {
                    this.dmeLla.pause();
                    dmeLlaPipe.pause();
                    this.vorLla.resume();
                    vorLlaPipe.resume(true);
                }
                else if (hasDme) {
                    this.vorLla.pause();
                    vorLlaPipe.pause();
                    this.dmeLla.resume();
                    dmeLlaPipe.resume(true);
                }
                else {
                    this.vorLla.pause();
                    vorLlaPipe.pause();
                    this.dmeLla.pause();
                    dmeLlaPipe.pause();
                    this.location.set(null);
                }
            }, true);
            // Distance
            this.dmePipe = navComSubscriber.on(`nav_dme_${index}`).handle(val => { this.distance.set(val); }, true);
            msfsSdk.MappedSubject.create(this.hasDme, this.hasSignal).sub(([hasDme, hasSignal]) => {
                if (hasDme !== null && hasDme && hasSignal) {
                    this.dmePipe.resume(true);
                }
                else {
                    this.dmePipe.pause();
                    this.distance.set(null);
                }
            }, true);
            // Bearing
            this.bearingPipe = navComSubscriber.on(`nav_radial_${index}`).handle(val => { this.bearing.set((val + 180) % 360); }, true);
            msfsSdk.MappedSubject.create(this.hasNav, this.hasSignal).sub(([hasNav, hasSignal]) => {
                if (hasNav !== null && hasNav && hasSignal) {
                    this.bearingPipe.resume(true);
                }
                else {
                    this.bearingPipe.pause();
                    this.bearing.set(null);
                }
            }, true);
            // Is localizer
            const updateIsLocalizer = this.updateIsLocalizer.bind(this);
            this.hasLocalizer.sub(updateIsLocalizer);
            this.activeFrequency.sub(updateIsLocalizer);
            // Localizer course
            const updateLocalizerCourse = this.updateLocalizerCourse.bind(this);
            this.hasLocalizer.sub(updateLocalizerCourse);
            this.navLocalizerCrsRad.sub(updateLocalizerCourse);
            // Vertical deviation
            const updateVerticalDeviation = this.updateVerticalDeviation.bind(this);
            this.hasGlideSlope.sub(updateVerticalDeviation);
            this.glideSlopeErrorDegrees.sub(updateVerticalDeviation);
            // Lateral deviation
            const updateLateralDeviation = this.updateLateralDeviation.bind(this);
            this.navCdi.sub(updateLateralDeviation);
            this.hasNav.sub(updateLateralDeviation);
            // TO/FROM
            this.toFromPipe = navComSubscriber.on(`nav_to_from_${index}`).handle(val => { this.toFrom.set(val); }, true);
            msfsSdk.MappedSubject.create(this.isLocalizer, this.hasSignal, this.hasNav).sub(([isLoc, hasSignal, hasNav]) => {
                if (isLoc || !hasSignal || !hasNav) {
                    this.toFromPipe.pause();
                    this.toFrom.set(null);
                }
                else {
                    this.toFromPipe.resume(true);
                }
            }, true);
        }
        /** @inheritdoc */
        getType() {
            return msfsSdk.NavSourceType.Nav;
        }
        /**
         * Updates whether this source's reference is a localizer.
         */
        updateIsLocalizer() {
            var _a;
            const navHasLocalizer = this.hasLocalizer.get();
            const _isLocalizerFrequency = msfsSdk.RadioUtils.isLocalizerFrequency((_a = this.activeFrequency.get()) !== null && _a !== void 0 ? _a : 0);
            this.isLocalizer.set(navHasLocalizer || _isLocalizerFrequency);
        }
        /**
         * Updates this source's localizer course.
         */
        updateLocalizerCourse() {
            this.localizerCourse.set(this.hasLocalizer.get()
                ? this.navLocalizerCrsRad.get() * Avionics.Utils.RAD2DEG
                : null);
        }
        /**
         * Updates this source's vertical deviation.
         */
        updateVerticalDeviation() {
            this.verticalDeviation.set(this.hasGlideSlope.get() ? -this.glideSlopeErrorDegrees.get() / 0.7 : null);
        }
        /**
         * Updates this source's lateral deviation.
         */
        updateLateralDeviation() {
            // The NAV CDI simvar holds the deviation as a range from -127 to 127
            this.lateralDeviation.set(this.hasNav.get() ? this.navCdi.get() / 127 : null);
        }
    }

    /**
     * A basic implementation of {@link NavReferenceSources} which stores the sources in a Map.
     * @template SourceName The names of the navigation reference sources supported by the collection.
     */
    class NavReferenceSourceCollection {
        /**
         * Creates a new instance of NavReferenceSourceCollection.
         * @param sources The navigation reference sources to include in the collection.
         */
        constructor(...sources) {
            this.sources = sources;
        }
        /** @inheritdoc */
        get(name) {
            const indicator = this.sources.find(x => x.name === name);
            if (!indicator) {
                throw new Error('NavReferenceSourceCollection: no nav source exists with given name: ' + name);
            }
            else {
                return indicator;
            }
        }
    }

    /**
     * A `NavReferenceSource` that always provides null data.
     */
    class NullNavSource extends AbstractNavReferenceBase {
        /**
         * Creates a new instance of EmptyNavSource.
         * @param name The name of this source.
         * @param type The type of this source.
         * @param index The index of this source.
         */
        constructor(name, type, index) {
            super();
            this.name = name;
            this.type = type;
            this.index = index;
        }
        /** @inheritDoc */
        getType() {
            return this.type;
        }
    }

    /**
     * A manager for COM radio channel spacing. Syncs COM radio spacing modes with the COM radio spacing user setting and
     * intercepts COM radio spacing toggle key events in order to have them toggle the value of the user setting instead.
     */
    class ComRadioSpacingManager {
        /**
         * Constructor.
         * @param bus The event bus.
         * @param comRadioSettingManager A manager for COM radio user settings.
         * @param comRadioCount The number of supported COM radios. Defaults to `2`.
         */
        constructor(bus, comRadioSettingManager, comRadioCount = 2) {
            this.bus = bus;
            this.comRadioSettingManager = comRadioSettingManager;
            this.comRadioCount = comRadioCount;
            this.spacingSetting = this.comRadioSettingManager.getSetting('comRadioSpacing');
            this.keys = Array.from({ length: this.comRadioCount }, (v, index) => `COM_${index + 1}_SPACING_MODE_SWITCH`);
            this.isDebouncing = false;
            this.isAlive = true;
            this.isInit = false;
            msfsSdk.KeyEventManager.getManager(bus).then(manager => {
                this.keyEventManager = manager;
                if (this.isAlive && this.isInit) {
                    this.doInit(manager);
                }
            });
        }
        /**
         * Initializes this manager. Once this manager is initialized, it will automatically set the spacing mode of COM
         * radios based on the COM radio spacing user setting and intercept COM radio spacing toggle key events in order to
         * have them toggle the value of the user setting instead. If this manager is not yet ready to intercept key events
         * when this method is called, initialization will be suspended until the manager is ready to intercept key events.
         * @throws Error if this manager has been destroyed.
         */
        init() {
            if (!this.isAlive) {
                throw new Error('ComRadioSpacingManager: cannot initialize a dead manager');
            }
            if (this.isInit) {
                return;
            }
            this.isInit = true;
            if (this.keyEventManager !== undefined) {
                this.doInit(this.keyEventManager);
            }
        }
        /**
         * Performs initialization of this manager.
         * @param keyEventManager A key event manager.
         */
        async doInit(keyEventManager) {
            if (this.comRadioCount === 0) {
                return;
            }
            this.keys.forEach(key => keyEventManager.interceptKey(key, false));
            // Wait a short time to let any pending key events settle, then start syncing COM spacing to the spacing setting.
            await msfsSdk.Wait.awaitDelay(250);
            this.spacingSettingSub = this.spacingSetting.sub(() => {
                if (this.isDebouncing) {
                    return;
                }
                this.updateSpacing(keyEventManager);
            }, true);
            this.keyEventSub = this.bus.getSubscriber().on('key_intercept').handle(data => {
                if (this.keys.includes(data.key)) {
                    this.spacingSetting.value = this.spacingSetting.value === exports.ComRadioSpacingSettingMode.Spacing8_33Khz
                        ? exports.ComRadioSpacingSettingMode.Spacing25Khz
                        : exports.ComRadioSpacingSettingMode.Spacing8_33Khz;
                }
            });
        }
        /**
         * Updates the spacing mode of this manager's supported COM radios based on the COM radio spacing user setting.
         * @param keyEventManager A key event manager.
         */
        async updateSpacing(keyEventManager) {
            this.isDebouncing = true;
            // Debounce two frames to ensure that the COM SPACING MODE simvar has been updated since the last time the SWITCH
            // key event was sent.
            await msfsSdk.Wait.awaitDelay(0);
            await msfsSdk.Wait.awaitDelay(0);
            if (!this.isAlive) {
                return;
            }
            const spacingMode = this.spacingSetting.value === exports.ComRadioSpacingSettingMode.Spacing8_33Khz ? msfsSdk.ComSpacing.Spacing833Khz : msfsSdk.ComSpacing.Spacing25Khz;
            for (let i = 0; i < this.keys.length; i++) {
                if (SimVar.GetSimVarValue(`COM SPACING MODE:${i + 1}`, msfsSdk.SimVarValueType.Number) !== spacingMode) {
                    keyEventManager.triggerKey(this.keys[i], true);
                }
            }
            this.isDebouncing = false;
        }
        /**
         * Destroys this manager.
         */
        destroy() {
            var _a, _b;
            this.isAlive = false;
            (_a = this.spacingSettingSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.keyEventSub) === null || _b === void 0 ? void 0 : _b.destroy();
        }
    }

    /**
     * An individual Garmin softkey tab display.
     */
    class SoftKey extends msfsSdk.DisplayComponent {
        constructor() {
            var _a;
            super(...arguments);
            this.rootCssClass = msfsSdk.SetSubject.create(['softkey']);
            this.label = msfsSdk.Subject.create('');
            this.disabled = msfsSdk.Subject.create(true);
            this.highlighted = msfsSdk.Subject.create(false);
            this.value = msfsSdk.Subject.create(undefined);
            this.toggleStatusBarState = msfsSdk.Subject.create(false);
            this.valueText = msfsSdk.Subject.create('');
            this.pressedTimer = new msfsSdk.DebounceTimer();
            this.pressedDuration = (_a = this.props.pressedDuration) !== null && _a !== void 0 ? _a : SoftKey.DEFAULT_PRESSED_DURATION;
            this.onPressedHandler = this.onPressed.bind(this);
        }
        /** @inheritdoc */
        onAfterRender() {
            this.disabled.sub(this.onDisabledChanged.bind(this), true);
            this.highlighted.sub(this.onHighlightedChanged.bind(this), true);
            this.value.sub(this.onValueChanged.bind(this), true);
            this.menuItemSub = this.props.menuItem.sub(menuItem => {
                this.unsubscribeFromMenuItem();
                if (menuItem !== null) {
                    this.labelPipe = menuItem.label.pipe(this.label);
                    this.disabledPipe = menuItem.disabled.pipe(this.disabled);
                    this.highlightedPipe = menuItem.highlighted.pipe(this.highlighted);
                    this.valuePipe = menuItem.value.pipe(this.value);
                    this.pressedSub = menuItem.pressed.on(this.onPressedHandler);
                }
                else {
                    this.label.set('');
                    this.disabled.set(true);
                    this.highlighted.set(false);
                    this.value.set(undefined);
                }
            }, true);
        }
        /**
         * Unsubscribes from change events on the menu item.
         */
        unsubscribeFromMenuItem() {
            var _a, _b, _c, _d, _e;
            (_a = this.labelPipe) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.disabledPipe) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.highlightedPipe) === null || _c === void 0 ? void 0 : _c.destroy();
            (_d = this.valuePipe) === null || _d === void 0 ? void 0 : _d.destroy();
            (_e = this.pressedSub) === null || _e === void 0 ? void 0 : _e.destroy();
            this.labelPipe = undefined;
            this.disabledPipe = undefined;
            this.highlightedPipe = undefined;
            this.valuePipe = undefined;
            this.pressedSub = undefined;
        }
        /**
         * Responds to changes in this softkey's disabled state.
         * @param isDisabled Whether this softkey is disabled.
         */
        onDisabledChanged(isDisabled) {
            if (isDisabled) {
                this.rootCssClass.add('softkey-disabled');
            }
            else {
                this.rootCssClass.delete('softkey-disabled');
            }
        }
        /**
         * Responds to changes in this softkey's highlighted state.
         * @param isHighlighted Whether this softkey is highlighted.
         */
        onHighlightedChanged(isHighlighted) {
            if (isHighlighted) {
                this.rootCssClass.add('softkey-highlighted');
            }
            else {
                this.rootCssClass.add('softkey-highlighted');
            }
        }
        /**
         * Responds to changes in this softkey's value.
         * @param value The new value.
         */
        onValueChanged(value) {
            this.rootCssClass.delete('softkey-value-text-show');
            this.rootCssClass.delete('softkey-value-toggle-show');
            if (typeof value === 'string') {
                this.rootCssClass.add('softkey-value-text-show');
                this.valueText.set(value);
            }
            else if (typeof value === 'boolean') {
                this.rootCssClass.add('softkey-value-toggle-show');
                this.toggleStatusBarState.set(value);
            }
        }
        /**
         * Responds to when this softkey is pressed.
         */
        onPressed() {
            this.rootCssClass.add('softkey-pressed');
            this.pressedTimer.schedule(() => {
                this.rootCssClass.delete('softkey-pressed');
            }, this.pressedDuration);
        }
        /**
         * Renders the component.
         * @returns The rendered component VNode.
         */
        render() {
            if (typeof this.props.class === 'object') {
                this.cssClassSub = msfsSdk.FSComponent.bindCssClassSet(this.rootCssClass, this.props.class, SoftKey.RESERVED_CSS_CLASSES);
            }
            else if (typeof this.props.class === 'string') {
                msfsSdk.FSComponent.parseCssClassesFromString(this.props.class)
                    .filter(cssClass => !SoftKey.RESERVED_CSS_CLASSES.includes(cssClass))
                    .forEach(cssClass => { this.rootCssClass.add(cssClass); });
            }
            return (msfsSdk.FSComponent.buildComponent("div", { class: this.rootCssClass },
                msfsSdk.FSComponent.buildComponent("div", { class: 'softkey-borders' }),
                msfsSdk.FSComponent.buildComponent("div", { class: 'softkey-content' },
                    msfsSdk.FSComponent.buildComponent("div", { class: 'softkey-label' }, this.label),
                    msfsSdk.FSComponent.buildComponent("div", { class: 'softkey-value-text' }, this.valueText),
                    msfsSdk.FSComponent.buildComponent(ToggleStatusBar, { state: this.toggleStatusBarState, class: 'softkey-value-toggle' }))));
        }
        /** @inheritdoc */
        destroy() {
            var _a, _b;
            super.destroy();
            (_a = this.cssClassSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.menuItemSub) === null || _b === void 0 ? void 0 : _b.destroy();
            this.unsubscribeFromMenuItem();
        }
    }
    SoftKey.DEFAULT_PRESSED_DURATION = 150; // milliseconds
    SoftKey.RESERVED_CSS_CLASSES = [
        'softkey', 'softkey-disabled', 'softkey-highlighted', 'softkey-pressed',
        'softkey-value-text-show', 'softkey-value-toggle-show'
    ];

    /**
     * A Garmin softkey menu. Each menu contains up to 12 indexed menu items, each of which defines the display and
     * behavior of a corresponding softkey.
     */
    class SoftKeyMenu {
        /**
         * Creates an instance of a SoftKeyMenu.
         * @param menuSystem The menu system that will manage this menu.
         */
        constructor(menuSystem) {
            this.menuSystem = menuSystem;
            /** The menu items in this menu. */
            this.menuItems = Array.from({ length: SoftKeyMenu.SOFTKEY_COUNT }, () => null);
        }
        /**
         * Adds a menu item to the softkey menu.
         * @param index The softkey index to add the menu item to. Must be between 0 and 11, inclusive.
         * @param label The label of the menu item.
         * @param handler The handler to call when the menu item is selected.
         * @param value The value of the menu item, if any.
         * @param disabled Whether or not the menu item is disabled.
         * @returns The new menu item.
         * @throws Error if `index` is out of bounds.
         */
        addItem(index, label, handler, value, disabled = false) {
            if (index < 0 || index >= SoftKeyMenu.SOFTKEY_COUNT) {
                throw new Error(`SoftKeyMenu: menu item index (${index}) out of bounds`);
            }
            return this.menuItems[index] = {
                label: msfsSdk.Subject.create(label),
                handler,
                value: msfsSdk.Subject.create(value),
                pressed: new msfsSdk.SubEvent(),
                disabled: msfsSdk.Subject.create(handler === undefined || disabled),
                highlighted: msfsSdk.Subject.create(false)
            };
        }
        /**
         * Removes a menu item from the menu.
         * @param index The softkey index to remove the menu item from. Must be between 0 and 11, inclusive.
         * @throws Error if `index` is out of bounds.
         */
        removeItem(index) {
            if (index < 0 || index >= SoftKeyMenu.SOFTKEY_COUNT) {
                throw new Error(`SoftKeyMenu: menu item index (${index}) out of bounds`);
            }
            this.menuItems[index] = null;
        }
        /**
         * Gets a menu item.
         * @param index The index of the menu item. Must be between 0 and 11, inclusive.
         * @returns The requested menu item.
         * @throws Error if `index` is out of bounds.
         */
        getItem(index) {
            if (index < 0 || index >= SoftKeyMenu.SOFTKEY_COUNT) {
                throw new Error(`SoftKeyMenu: menu item index (${index}) out of bounds`);
            }
            return this.menuItems[index];
        }
        /**
         * Handles a back menu action.
         */
        handleBack() {
            this.menuSystem.back();
        }
        /**
         * Iterates over the menu items.
         * @param each The function to run over each menu item.
         */
        forEach(each) {
            this.menuItems.forEach(each);
        }
        /**
         * Handles when a menu item is pressed.
         * @param index The index of the menu item that was pressed.
         */
        handleItemPressed(index) {
            const menuItem = this.menuItems[index];
            if (menuItem !== null && menuItem.handler && !menuItem.disabled.get()) {
                menuItem.pressed.notify(this, undefined);
                menuItem.handler(this);
            }
        }
        /**
         * Destroys this menu.
         */
        destroy() {
            // noop
        }
    }
    /** The number of softkeys in each menu. */
    SoftKeyMenu.SOFTKEY_COUNT = 12;

    /**
     * A Garmin softkey bar display. Each softkey bar has 12 softkey tabs. The behavior and display of each softkey tab is
     * defined by the current softkey menu of the display's softkey menu system.
     */
    class SoftKeyBar extends msfsSdk.DisplayComponent {
        constructor() {
            super(...arguments);
            this.softkeyRefs = Array.from({ length: SoftKeyMenu.SOFTKEY_COUNT }, () => msfsSdk.FSComponent.createRef());
            this.menuItems = Array.from({ length: SoftKeyMenu.SOFTKEY_COUNT }, () => msfsSdk.Subject.create(null));
        }
        /** @inheritdoc */
        onAfterRender() {
            this.menuSub = this.props.menuSystem.currentMenu.sub(menu => {
                this.menuItems.forEach((menuItem, index) => {
                    var _a;
                    menuItem.set((_a = menu === null || menu === void 0 ? void 0 : menu.getItem(index)) !== null && _a !== void 0 ? _a : null);
                });
            }, true);
        }
        /**
         * Renders the component.
         * @returns The rendered component.
         */
        render() {
            return (msfsSdk.FSComponent.buildComponent("div", { class: 'softkey-container', "data-checklist": 'SoftKeys' }, this.buildSoftKeys()));
        }
        /**
         * Builds the softkeys tab elements.
         * @returns A collection of soft key div elements.
         */
        buildSoftKeys() {
            return Array.from({ length: SoftKeyMenu.SOFTKEY_COUNT }, (value, index) => {
                return (msfsSdk.FSComponent.buildComponent(SoftKey, { ref: this.softkeyRefs[index], menuItem: this.menuItems[index], pressedDuration: this.props.pressedDuration, class: `softkey-${index}` }));
            });
        }
        /** @inheritdoc */
        destroy() {
            var _a;
            super.destroy();
            this.softkeyRefs.forEach(softkey => { var _a; return (_a = softkey.getOrDefault()) === null || _a === void 0 ? void 0 : _a.destroy(); });
            (_a = this.menuSub) === null || _a === void 0 ? void 0 : _a.destroy();
        }
    }

    /**
     * A system that manages Garmin softkey menus.
     *
     * Individual softkey menus are registered with the menu system under a unique string name. After a menu is registered,
     * it can be pushed onto the system's menu stack. The top-most menu in the stack is considered the current menu. Menu
     * navigation is achieved using operations on the stack:
     * * push: navigates to a new menu while preserving history.
     * * replace: navigates to a new menu without preserving history.
     * * pop: return to the previous menu stored in history.
     * * clear: removes the current menu and clears all history.
     */
    class SoftKeyMenuSystem {
        // eslint-disable-next-line jsdoc/require-returns
        /** The top element in this system's menu stack. */
        get menuStackTop() {
            var _a;
            return (_a = this.menuStack[this.menuStack.length - 1]) !== null && _a !== void 0 ? _a : null;
        }
        /**
         * Creates an instance of SoftKeyMenuSystem.
         * @param bus The event bus to use with this instance.
         * @param hEventMap A function which maps H events to softkey indexes. The function should return the index of the
         * pressed softkey for softkey press H events, and `undefined` for all other H events.
         */
        constructor(bus, hEventMap) {
            this.bus = bus;
            /** This system's menu stack. */
            this.menuStack = [];
            /** This system's registered menus. */
            this.registeredMenus = new Map();
            this._currentMenu = msfsSdk.Subject.create(null);
            /** The current menu, or `null` if there is no current menu. */
            this.currentMenu = this._currentMenu;
            bus.getSubscriber().on('hEvent').handle(hEvent => {
                const index = hEventMap(hEvent);
                if (index !== undefined) {
                    this.onSoftKeyPressed(index);
                }
            });
        }
        /**
         * Registers a softkey menu with this menu system under a given name. If an existing menu is registered under the
         * same name, it will be replaced by the new menu.
         * @param name The name under which to register the menu.
         * @param factory A function which creates the menu to register.
         */
        registerMenu(name, factory) {
            const existing = this.registeredMenus.get(name);
            if (existing) {
                existing.destroy();
            }
            this.registeredMenus.set(name, factory(this));
        }
        /**
         * Gets the softkey menu registered under a given name.
         * @param menuName The name of the menu.
         * @returns The softkey menu registered under the specified name, or `undefined` if there is no such menu.
         */
        getMenu(menuName) {
            return this.registeredMenus.get(menuName);
        }
        /**
         * Pushes a menu onto this system's menu stack. The pushed menu will become the new current menu.
         * @param name The name of the menu to push.
         * @throws Error if this system has no menu registered under the given name.
         */
        pushMenu(name) {
            const menu = this.registeredMenus.get(name);
            if (menu === undefined) {
                throw new Error(`SoftKeyMenuSystem: cannot find menu with name '${name}'`);
            }
            this.menuStack.push(menu);
            this._currentMenu.set(this.menuStackTop);
        }
        /**
         * Replaces the current menu with another menu. The current menu will be removed from the stack and the replacement
         * menu will become the new current menu.
         * @param name The name of the replacement menu.
         * @throws Error if this system has no menu registered under the given name.
         */
        replaceMenu(name) {
            const menu = this.registeredMenus.get(name);
            if (menu === undefined) {
                throw new Error(`SoftKeyMenuSystem: cannot find menu with name '${name}'`);
            }
            this.menuStack.pop();
            this.menuStack.push(menu);
            this._currentMenu.set(this.menuStackTop);
        }
        /**
         * Removes this system's current menu from the menu stack and makes the next highest menu on the stack the new
         * current menu.
         */
        back() {
            this.menuStack.pop();
            this._currentMenu.set(this.menuStackTop);
        }
        /**
         * Clears this system's menu stack of all menus.
         */
        clear() {
            this.menuStack.length = 0;
            this._currentMenu.set(null);
        }
        /**
         * Responds to when a softkey is pressed.
         * @param index The index of the pressed softkey.
         */
        onSoftKeyPressed(index) {
            var _a;
            (_a = this.currentMenu.get()) === null || _a === void 0 ? void 0 : _a.handleItemPressed(index);
        }
    }

    /**
     * A controller which binds a softkey to a boolean state. Once bound, the softkey will display the bound state and
     * each press of the softkey will toggle the value of the state.
     */
    class SoftKeyBooleanController {
        /**
         * Constructor.
         * @param softkeyMenu The softkey menu to which this controller's softkey belongs.
         * @param softkeyIndex The index in the softkey menu at which this controller's softkey is located.
         * @param softkeyLabel The text label of this controller's softkey.
         * @param state The state bound to this controller's softkey.
         */
        constructor(softkeyMenu, softkeyIndex, softkeyLabel, state) {
            this.softkeyMenu = softkeyMenu;
            this.softkeyIndex = softkeyIndex;
            this.softkeyLabel = softkeyLabel;
            this.state = state;
            this.isAlive = true;
            this.isInit = false;
        }
        /**
         * Initializes this controller. This will create a softkey menu item and bind it to this controller's state.
         * @returns The softkey menu item bound to this controller's state.
         * @throws Error if this controller has been destroyed.
         */
        init() {
            if (!this.isAlive) {
                throw new Error('SoftKeyBooleanController: cannot initialize a dead controller');
            }
            if (this.isInit) {
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                return this.item;
            }
            this.item = this.softkeyMenu.addItem(this.softkeyIndex, this.softkeyLabel, () => { this.state.set(!this.state.get()); });
            this.statePipe = this.state.pipe(this.item.value);
            this.isInit = true;
            return this.item;
        }
        /**
         * Destroys this controller. This will remove the softkey menu item bound to this controller's state.
         */
        destroy() {
            var _a;
            if (!this.isAlive) {
                return;
            }
            this.isAlive = false;
            if (!this.isInit) {
                return;
            }
            this.softkeyMenu.removeItem(this.softkeyIndex);
            (_a = this.statePipe) === null || _a === void 0 ? void 0 : _a.destroy();
        }
    }
    /**
     * A controller which binds a softkey to a state which can take one of several enumerated values. Once bound, the
     * softkey will display the bound state and each press of the softkey will cycle the state through possible values.
     */
    class SoftKeyEnumController {
        /**
         * Constructor.
         * @param softkeyMenu The softkey menu to which this controller's softkey belongs.
         * @param softkeyIndex The index in the softkey menu at which this controller's softkey is located.
         * @param softkeyLabel The text label of this controller's softkey.
         * @param state The state bound to this controller's softkey.
         * @param textMap A function which maps values to their text representations.
         * @param nextFunc A function which gets the next value given the current value.
         */
        constructor(softkeyMenu, softkeyIndex, softkeyLabel, state, textMap, nextFunc) {
            this.softkeyMenu = softkeyMenu;
            this.softkeyIndex = softkeyIndex;
            this.softkeyLabel = softkeyLabel;
            this.state = state;
            this.textMap = textMap;
            this.nextFunc = nextFunc;
            this.isAlive = true;
            this.isInit = false;
        }
        /**
         * Initializes this controller. This will create a softkey menu item and bind it to this controller's state.
         * @returns The softkey menu item bound to this controller's state.
         * @throws Error if this controller has been destroyed.
         */
        init() {
            if (!this.isAlive) {
                throw new Error('SoftKeyEnumController: cannot initialize a dead controller');
            }
            if (this.isInit) {
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                return this.item;
            }
            this.item = this.softkeyMenu.addItem(this.softkeyIndex, this.softkeyLabel, () => { this.state.set(this.nextFunc(this.state.get())); });
            this.statePipe = this.state.pipe(this.item.value, this.textMap);
            this.isInit = true;
            return this.item;
        }
        /**
         * Destroys this controller. This will remove the softkey menu item bound to this controller's state.
         */
        destroy() {
            var _a;
            if (!this.isAlive) {
                return;
            }
            this.isAlive = false;
            if (!this.isInit) {
                return;
            }
            this.softkeyMenu.removeItem(this.softkeyIndex);
            (_a = this.statePipe) === null || _a === void 0 ? void 0 : _a.destroy();
            this.isInit = false;
        }
    }
    /**
     * A controller which binds one or more softkeys to a state which can take one or more enumerated values. Each
     * softkey is bound to a specific value. Once bound, each softkey will display whether the state is equal to its bound
     * value, and each press of the softkey will set the state to its bound value.
     */
    class MultipleSoftKeyEnumController {
        /**
         * Constructor.
         * @param softkeyMenu The softkey menu to which this controller's bound softkeys belong.
         * @param state The state bound to this controller's softkeys.
         * @param softkeyDefs The definitions for the softkeys bound to this controller's setting.
         */
        constructor(softkeyMenu, state, softkeyDefs) {
            this.softkeyMenu = softkeyMenu;
            this.state = state;
            this.softkeyDefs = softkeyDefs;
            this.menuItems = [];
            this.statePipes = [];
            this.isAlive = true;
            this.isInit = false;
        }
        /**
         * Initializes this controller. This will create softkey menu items and bind them to this controller's state.
         * @returns The softkey menu items bound to this controller's state. The order of the items is the same as the order
         * of the softkey definitions passed to this controller's constructor.
         * @throws Error if this controller has been destroyed.
         */
        init() {
            if (!this.isAlive) {
                throw new Error('MultipleSoftKeyEnumController: cannot initialize a dead controller');
            }
            if (this.isInit) {
                return this.menuItems;
            }
            for (let i = 0; i < this.softkeyDefs.length; i++) {
                const def = this.softkeyDefs[i];
                const item = this.softkeyMenu.addItem(def.index, def.label, () => { this.state.set(def.value); });
                this.menuItems.push(item);
                this.statePipes.push(this.state.pipe(item.value, value => value === def.value));
            }
            this.isInit = true;
            return this.menuItems;
        }
        /**
         * Destroys this controller. This will remove the softkey menu items bound to this controller's state.
         */
        destroy() {
            if (!this.isAlive) {
                return;
            }
            this.isAlive = false;
            if (!this.isInit) {
                return;
            }
            this.softkeyDefs.forEach(def => { this.softkeyMenu.removeItem(def.index); });
            this.statePipes.forEach(pipe => { pipe.destroy(); });
            this.isInit = false;
        }
    }

    /**
     * A Garmin ADC system.
     */
    class AdcSystem extends msfsSdk.BasicAvionicsSystem {
        /**
         * Creates an instance of an ADC system.
         * @param index The index of the ADC.
         * @param bus An instance of the event bus.
         * @param airspeedIndicatorIndex The index of the sim airspeed indicator from which this ADC derives its data.
         * @param altimeterIndex The index of the sim altimeter from which this ADC derives its data.
         * @param powerSource The {@link ElectricalEvents} topic or electricity logic element to which to connect the
         * system's power.
         */
        constructor(index, bus, airspeedIndicatorIndex, altimeterIndex, powerSource) {
            super(index, bus, `adc_state_${index}`);
            this.airspeedIndicatorIndex = airspeedIndicatorIndex;
            this.altimeterIndex = altimeterIndex;
            this.initializationTime = 15000;
            this.isAltitudeDataValid = true;
            this.isAirspeedDataValid = true;
            this.isTemperatureDataValid = true;
            this.altitudeDataSourceTopicMap = {
                [`adc_indicated_alt_${this.index}`]: `indicated_alt_${this.altimeterIndex}`,
                [`adc_pressure_alt_${this.index}`]: 'pressure_alt',
                [`adc_vertical_speed_${this.index}`]: 'vertical_speed',
                [`adc_ambient_pressure_inhg_${this.index}`]: 'ambient_pressure_inhg'
            };
            this.airspeedDataSourceTopicMap = {
                [`adc_ias_${this.index}`]: `ias_${this.airspeedIndicatorIndex}`,
                [`adc_mach_number_${this.index}`]: `indicated_mach_number_${this.airspeedIndicatorIndex}`,
                [`adc_mach_to_kias_factor_${this.index}`]: `indicated_mach_to_kias_factor_${this.airspeedIndicatorIndex}`
            };
            this.temperatureDataSourceTopicMap = {
                [`adc_tas_${this.index}`]: `indicated_tas_${this.airspeedIndicatorIndex}`,
                [`adc_tas_to_ias_factor_${this.index}`]: `indicated_tas_to_ias_factor_${this.airspeedIndicatorIndex}`,
                [`adc_ambient_temp_c_${this.index}`]: 'ambient_temp_c',
                [`adc_isa_temp_c_${this.index}`]: 'isa_temp_c',
                [`adc_ram_air_temp_c_${this.index}`]: 'ram_air_temp_c'
            };
            this.otherDataSourceTopicMap = {
                [`adc_altimeter_baro_setting_inhg_${this.index}`]: `altimeter_baro_setting_inhg_${this.altimeterIndex}`,
                [`adc_altimeter_baro_preselect_inhg_${this.index}`]: `altimeter_baro_preselect_inhg_${this.altimeterIndex}`,
                [`adc_altimeter_baro_preselect_mb_${this.index}`]: `altimeter_baro_preselect_mb_${this.altimeterIndex}`,
                [`adc_altimeter_baro_preselect_raw_${this.index}`]: `altimeter_baro_preselect_raw_${this.altimeterIndex}`,
                [`adc_altimeter_baro_is_std_${this.index}`]: `altimeter_baro_is_std_${this.altimeterIndex}`
            };
            this.dataSourceSubscriber = this.bus.getSubscriber();
            this.altitudeDataSubs = [];
            this.airspeedDataSubs = [];
            this.temperatureDataSubs = [];
            this.otherDataSubs = [];
            this.publisher.pub(`adc_altitude_data_valid_${this.index}`, this.isAltitudeDataValid, false, true);
            this.publisher.pub(`adc_airspeed_data_valid_${this.index}`, this.isAirspeedDataValid, false, true);
            this.publisher.pub(`adc_temperature_data_valid_${this.index}`, this.isTemperatureDataValid, false, true);
            if (powerSource !== undefined) {
                this.connectToPower(powerSource);
            }
            this.startDataPublish();
        }
        /**
         * Starts publishing ADC data on the event bus.
         */
        startDataPublish() {
            for (const topic in this.altitudeDataSourceTopicMap) {
                if (this.bus.getTopicSubscriberCount(topic) > 0) {
                    this.onTopicSubscribed(topic, this.altitudeDataSourceTopicMap[topic], this.altitudeDataSubs, this.isAltitudeDataValid);
                }
            }
            for (const topic in this.airspeedDataSourceTopicMap) {
                if (this.bus.getTopicSubscriberCount(topic) > 0) {
                    this.onTopicSubscribed(topic, this.airspeedDataSourceTopicMap[topic], this.airspeedDataSubs, this.isAirspeedDataValid);
                }
            }
            for (const topic in this.temperatureDataSourceTopicMap) {
                if (this.bus.getTopicSubscriberCount(topic) > 0) {
                    this.onTopicSubscribed(topic, this.temperatureDataSourceTopicMap[topic], this.temperatureDataSubs, this.isTemperatureDataValid);
                }
            }
            for (const topic in this.otherDataSourceTopicMap) {
                if (this.bus.getTopicSubscriberCount(topic) > 0) {
                    this.onTopicSubscribed(topic, this.otherDataSourceTopicMap[topic], this.otherDataSubs, true);
                }
            }
            this.bus.getSubscriber().on('event_bus_topic_first_sub').handle(topic => {
                if (topic in this.altitudeDataSourceTopicMap) {
                    this.onTopicSubscribed(topic, this.altitudeDataSourceTopicMap[topic], this.altitudeDataSubs, this.isAltitudeDataValid);
                }
                else if (topic in this.airspeedDataSourceTopicMap) {
                    this.onTopicSubscribed(topic, this.airspeedDataSourceTopicMap[topic], this.airspeedDataSubs, this.isAirspeedDataValid);
                }
                else if (topic in this.temperatureDataSourceTopicMap) {
                    this.onTopicSubscribed(topic, this.temperatureDataSourceTopicMap[topic], this.temperatureDataSubs, this.isTemperatureDataValid);
                }
                else if (topic in this.otherDataSourceTopicMap) {
                    this.onTopicSubscribed(topic, this.otherDataSourceTopicMap[topic], this.otherDataSubs, true);
                }
            });
        }
        /**
         * Responds to when someone first subscribes to one of this system's data topics on the event bus.
         * @param topic The topic that was subscribed to.
         * @param sourceTopic The topic that is the data source of the subscribed topic.
         * @param dataSubArray The array to which to add the data source subscription created for the subscribed topoic.
         * @param isDataValid Whether the data published to the subscribed topic is currently valid.
         */
        onTopicSubscribed(topic, sourceTopic, dataSubArray, isDataValid) {
            dataSubArray.push(this.dataSourceSubscriber.on(sourceTopic).handle(val => {
                this.publisher.pub(topic, val, false, true);
            }, !isDataValid));
        }
        /** @inheritdoc */
        onStateChanged(previousState, currentState) {
            const wasAltitudeDataValid = this.isAltitudeDataValid;
            const wasAirspeedDataValid = this.isAirspeedDataValid;
            const wasTemperatureDataValid = this.isTemperatureDataValid;
            if (currentState === msfsSdk.AvionicsSystemState.On) {
                this.isAltitudeDataValid = true;
                this.isAirspeedDataValid = true;
                this.isTemperatureDataValid = true;
                for (const sub of this.altitudeDataSubs) {
                    sub.resume(true);
                }
                for (const sub of this.airspeedDataSubs) {
                    sub.resume(true);
                }
                for (const sub of this.temperatureDataSubs) {
                    sub.resume(true);
                }
            }
            else {
                this.isAltitudeDataValid = false;
                this.isAirspeedDataValid = false;
                this.isTemperatureDataValid = false;
                for (const sub of this.altitudeDataSubs) {
                    sub.pause();
                }
                for (const sub of this.airspeedDataSubs) {
                    sub.pause();
                }
                for (const sub of this.temperatureDataSubs) {
                    sub.pause();
                }
            }
            wasAltitudeDataValid !== this.isAltitudeDataValid && this.publisher.pub(`adc_altitude_data_valid_${this.index}`, this.isAltitudeDataValid, false, true);
            wasAirspeedDataValid !== this.isAirspeedDataValid && this.publisher.pub(`adc_airspeed_data_valid_${this.index}`, this.isAirspeedDataValid, false, true);
            wasTemperatureDataValid !== this.isTemperatureDataValid && this.publisher.pub(`adc_temperature_data_valid_${this.index}`, this.isTemperatureDataValid, false, true);
        }
    }

    /**
     * Automatically selects the best ADC from a set of candidates based on the current states of all systems. System state
     * desiribility depends on whether it is providing valid airspeed and altitude data.
     */
    class AdcSystemSelector {
        /**
         * Creates a new instance of AdcSystemSelector.
         * @param bus The event bus.
         * @param candidateSystemIndexes The indexes of the ADC systems from which to select.
         * @param options Options with which to configure the selector.
         */
        constructor(bus, candidateSystemIndexes, options) {
            var _a;
            this.bus = bus;
            this._selectedIndex = msfsSdk.Subject.create(-1);
            /** The index of the selected ADC, or `-1` if one could not be selected. */
            this.selectedIndex = this._selectedIndex;
            this._isSelectedAltitudeDataValid = msfsSdk.Subject.create(false);
            /** Whether the selected ADC is providing valid altitude data. */
            this.isSelectedAltitudeDataValid = this._isSelectedAltitudeDataValid;
            this._isSelectedAirspeedDataValid = msfsSdk.Subject.create(false);
            /** Whether the selected ADC is providing valid airspeed data. */
            this.isSelectedAirspeedDataValid = this._isSelectedAirspeedDataValid;
            this._isSelectedTemperatureDataValid = msfsSdk.Subject.create(false);
            /** Whether the selected ADC is providing valid temperature data. */
            this.isSelectedTemperatureDataValid = this._isSelectedTemperatureDataValid;
            this.adcEntries = new Map();
            this.adcOrder = [];
            this.adcPriorityComparator = (a, b) => {
                var _a, _b;
                return ((_a = this.systemPriorities.getValue(b)) !== null && _a !== void 0 ? _a : 0) - ((_b = this.systemPriorities.getValue(a)) !== null && _b !== void 0 ? _b : 0);
            };
            this.needReselect = true;
            this.isAlive = true;
            this.isInit = false;
            this.candidateSystemIndexes = 'isSubscribableSet' in candidateSystemIndexes ? candidateSystemIndexes : msfsSdk.SetSubject.create(candidateSystemIndexes);
            if (options === null || options === void 0 ? void 0 : options.systemPriorities) {
                if ('isSubscribableMap' in options.systemPriorities) {
                    this.systemPriorities = options.systemPriorities;
                }
                else if (options.systemPriorities instanceof Map) {
                    this.systemPriorities = msfsSdk.MapSubject.create(options.systemPriorities);
                }
                else {
                    this.systemPriorities = msfsSdk.MapSubject.create(options.systemPriorities.map((adcIndex, arrayIndex, array) => [adcIndex, array.length - arrayIndex]));
                }
            }
            else {
                this.systemPriorities = msfsSdk.MapSubject.create();
            }
            this.desirabilityComparator = (_a = options === null || options === void 0 ? void 0 : options.desirabilityComparator) !== null && _a !== void 0 ? _a : AdcSystemSelector.defaultDesirabilityComparator;
        }
        /**
         * Initializes this selector. Once initialized, this selector will automatically select the best ADC among its
         * candidates.
         * @throws Error if this selector has been destroyed.
         */
        init() {
            if (!this.isAlive) {
                throw new Error('AdcSystemSelector: cannot initialize a dead selector');
            }
            if (this.isInit) {
                return;
            }
            this.isInit = true;
            const sub = this.bus.getSubscriber();
            const scheduleReselect = () => { this.needReselect = true; };
            this.candidateSystemIndexesSub = this.candidateSystemIndexes.sub((set, type, key) => {
                const existing = this.adcEntries.get(key);
                if (existing) {
                    for (const subject of existing.subjects) {
                        subject.destroy();
                    }
                }
                if (type === msfsSdk.SubscribableSetEventType.Added) {
                    const subjects = [
                        msfsSdk.ConsumerSubject.create(sub.on(`adc_altitude_data_valid_${key}`), false),
                        msfsSdk.ConsumerSubject.create(sub.on(`adc_airspeed_data_valid_${key}`), false),
                        msfsSdk.ConsumerSubject.create(sub.on(`adc_temperature_data_valid_${key}`), false)
                    ];
                    const entry = {
                        subjects,
                        state: msfsSdk.MappedSubject.create(...subjects)
                    };
                    this.adcEntries.set(key, entry);
                    // When there is a change in an ADC state, we don't reselect immediately because the ADC could be in an
                    // intermediate transition state. Instead, we will schedule a reselect during the next update loop.
                    entry.state.sub(scheduleReselect);
                }
                else {
                    this.adcEntries.delete(key);
                }
                this.needReselect = true;
            }, true);
            this.systemPrioritiesSub = this.systemPriorities.sub(scheduleReselect);
            this.updateSub = sub.on('realTime').handle(this.update.bind(this));
        }
        /**
         * Updates this selector.
         */
        update() {
            if (this.needReselect) {
                this.selectIndex();
                this.needReselect = false;
            }
        }
        /**
         * Selects the index of the ADC with the most desirable state.
         */
        selectIndex() {
            let bestIndex;
            let bestState;
            if (this.adcEntries.size === 0) {
                bestIndex = -1;
                bestState = undefined;
            }
            else if (this.adcEntries.size === 1) {
                const entry = this.adcEntries.entries().next().value;
                bestIndex = entry[0];
                bestState = entry[1].state.get();
            }
            else {
                // Sort the systems in order of decreasing priority.
                this.adcOrder.length = 0;
                for (const index of this.adcEntries.keys()) {
                    this.adcOrder.push(index);
                }
                this.adcOrder.sort(this.adcPriorityComparator);
                bestIndex = -1;
                for (let i = 0; i < this.adcOrder.length; i++) {
                    const index = this.adcOrder[i];
                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                    const state = this.adcEntries.get(index).state.get();
                    if (!bestState || this.desirabilityComparator(state, bestState) < 0) {
                        bestIndex = index;
                        bestState = state;
                    }
                }
            }
            this._selectedIndex.set(bestIndex);
            if (bestState) {
                this._isSelectedAltitudeDataValid.set(bestState[0]);
                this._isSelectedAirspeedDataValid.set(bestState[1]);
                this._isSelectedTemperatureDataValid.set(bestState[2]);
            }
            else {
                this._isSelectedAltitudeDataValid.set(false);
                this._isSelectedAirspeedDataValid.set(false);
                this._isSelectedTemperatureDataValid.set(false);
            }
        }
        /**
         * Destroys this selector.
         */
        destroy() {
            var _a, _b, _c;
            this.isAlive = false;
            (_a = this.updateSub) === null || _a === void 0 ? void 0 : _a.destroy();
            for (const entry of this.adcEntries.values()) {
                for (const subject of entry.subjects) {
                    subject.destroy();
                }
            }
            (_b = this.candidateSystemIndexesSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.systemPrioritiesSub) === null || _c === void 0 ? void 0 : _c.destroy();
        }
        /**
         * A function that compares the desirability of two ADC systems using a set of default criteria. Systems are
         * assigned one point for each set of valid data they provide: altitude, airspeed, and temperature. A system is
         * deemed more desirable than another if and only if the former is assigned more points than the latter.
         * @param a The state of the first ADC system.
         * @param b The state of the second ADC system.
         * @returns A number representing the relative desirability of the two ADC systems: `-1` if system `a` is more
         * desirable than `b`, `1` if system `b` is more desirable than `a`, and `0` if both are equally desirable.
         */
        static defaultDesirabilityComparator(a, b) {
            return (b[0] ? 1 : 0) + (b[1] ? 1 : 0) + (b[2] ? 1 : 0)
                - (a[0] ? 1 : 0) + (a[1] ? 1 : 0) + (a[2] ? 1 : 0);
        }
    }

    /**
     * A Garmin AHRS system.
     */
    class AhrsSystem extends msfsSdk.BasicAvionicsSystem {
        /**
         * Creates an instance of an AHRS system.
         * @param index The index of the AHRS.
         * @param bus An instance of the event bus.
         * @param attitudeIndicatorIndex The index of the sim attitude indicator from which this AHRS derives its data.
         * @param directionIndicatorIndex The index of the sim direction indicator from which this AHRS derives its data.
         * @param powerSource The {@link ElectricalEvents} topic or electricity logic element to which to connect the
         * system's power.
         */
        constructor(index, bus, attitudeIndicatorIndex, directionIndicatorIndex, powerSource) {
            super(index, bus, `ahrs_state_${index}`);
            this.attitudeIndicatorIndex = attitudeIndicatorIndex;
            this.directionIndicatorIndex = directionIndicatorIndex;
            this.powerSource = powerSource;
            this.initializationTime = 45000;
            this.magnetometerState = undefined;
            this.adcState = undefined;
            // TODO: add GPS data state
            this.isHeadingDataValid = true;
            this.isAttitudeDataValid = true;
            this.rollSub = this.bus.getSubscriber().on('roll_deg').whenChanged().handle(this.onRollChanged.bind(this), true);
            this.headingDataSourceTopicMap = {
                [`ahrs_hdg_deg_${this.index}`]: `hdg_deg_${this.directionIndicatorIndex}`,
                [`ahrs_hdg_deg_true_${this.index}`]: `hdg_deg_true_${this.directionIndicatorIndex}`
            };
            this.attitudeDataSourceTopicMap = {
                [`ahrs_delta_heading_rate_${this.index}`]: `delta_heading_rate_${this.attitudeIndicatorIndex}`,
                [`ahrs_pitch_deg_${this.index}`]: `pitch_deg_${this.attitudeIndicatorIndex}`,
                [`ahrs_roll_deg_${this.index}`]: `roll_deg_${this.attitudeIndicatorIndex}`,
                [`ahrs_turn_coordinator_ball_${this.index}`]: 'turn_coordinator_ball'
            };
            this.dataSourceSubscriber = this.bus.getSubscriber();
            this.headingDataSubs = [];
            this.attitudeDataSubs = [];
            if (this.powerSource !== undefined) {
                this.connectToPower(this.powerSource);
            }
            this.publisher.pub(`ahrs_heading_data_valid_${index}`, this.isHeadingDataValid);
            this.publisher.pub(`ahrs_attitude_data_valid_${index}`, this.isAttitudeDataValid);
            this.bus.getSubscriber()
                .on(`magnetometer_state_${index}`)
                .handle(evt => {
                this.magnetometerState = evt.current;
                this.updateHeadingDataState();
            });
            this.bus.getSubscriber()
                .on(`adc_state_${index}`)
                .handle(evt => {
                this.adcState = evt.current;
                this.updateAttitudeDataState();
            });
            this.startDataPublish();
        }
        /** @inheritdoc */
        onPowerChanged(isPowered) {
            const wasPowered = this.isPowered;
            this.isPowered = isPowered;
            if (wasPowered === undefined) {
                this.setState(isPowered ? msfsSdk.AvionicsSystemState.On : msfsSdk.AvionicsSystemState.Off);
            }
            else {
                if (isPowered) {
                    this.setState(msfsSdk.AvionicsSystemState.Initializing);
                    this.rollSub.resume(true);
                    this.initializationTimer.schedule(() => {
                        this.rollSub.pause();
                        this.setState(msfsSdk.AvionicsSystemState.On);
                    }, 45000);
                }
                else {
                    this.rollSub.pause();
                    this.initializationTimer.clear();
                    this.setState(msfsSdk.AvionicsSystemState.Off);
                }
            }
        }
        /**
         * Starts publishing AHRS data on the event bus.
         */
        startDataPublish() {
            for (const topic of Object.keys(this.headingDataSourceTopicMap)) {
                if (this.bus.getTopicSubscriberCount(topic) > 0) {
                    this.onHeadingTopicSubscribed(topic);
                }
            }
            for (const topic of Object.keys(this.attitudeDataSourceTopicMap)) {
                if (this.bus.getTopicSubscriberCount(topic) > 0) {
                    this.onAttitudeTopicSubscribed(topic);
                }
            }
            this.bus.getSubscriber().on('event_bus_topic_first_sub').handle(topic => {
                if (topic in this.headingDataSourceTopicMap) {
                    this.onHeadingTopicSubscribed(topic);
                }
                else if (topic in this.attitudeDataSourceTopicMap) {
                    this.onAttitudeTopicSubscribed(topic);
                }
            });
        }
        /**
         * Responds to when someone first subscribes to one of this system's heading data topics on the event bus.
         * @param topic The topic that was subscribed to.
         */
        onHeadingTopicSubscribed(topic) {
            this.headingDataSubs.push(this.dataSourceSubscriber.on(this.headingDataSourceTopicMap[topic]).handle(val => {
                this.publisher.pub(topic, val, false, true);
            }, !this.isHeadingDataValid));
        }
        /**
         * Responds to when someone first subscribes to one of this system's attitude data topics on the event bus.
         * @param topic The topic that was subscribed to.
         */
        onAttitudeTopicSubscribed(topic) {
            this.attitudeDataSubs.push(this.dataSourceSubscriber.on(this.attitudeDataSourceTopicMap[topic]).handle(val => {
                this.publisher.pub(topic, val, false, true);
            }, !this.isAttitudeDataValid));
        }
        /** @inheritdoc */
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        onStateChanged(previousState, currentState) {
            this.updateHeadingDataState();
            this.updateAttitudeDataState();
        }
        /**
         * Updates the validity state of this system's heading data. If heading data is valid, this system will start
         * publishing heading data. If heading data is invalid, this system will stop publishing heading data.
         */
        updateHeadingDataState() {
            const isHeadingDataValid = (this._state === undefined || this._state === msfsSdk.AvionicsSystemState.On)
                && (this.magnetometerState === undefined || this.magnetometerState === msfsSdk.AvionicsSystemState.On);
            if (isHeadingDataValid !== this.isHeadingDataValid) {
                this.isHeadingDataValid = isHeadingDataValid;
                if (isHeadingDataValid) {
                    for (const sub of this.headingDataSubs) {
                        sub.resume(true);
                    }
                }
                else {
                    for (const sub of this.headingDataSubs) {
                        sub.pause();
                    }
                }
                this.publisher.pub(`ahrs_heading_data_valid_${this.index}`, this.isHeadingDataValid, false, true);
            }
        }
        /**
         * Updates the validity state of this system's attitude data. If attitude data is valid, this system will start
         * publishing attitude data. If attitude data is invalid, this system will stop publishing attitude data.
         */
        updateAttitudeDataState() {
            const isAttitudeDataValid = (this._state === undefined || this._state === msfsSdk.AvionicsSystemState.On);
            // TODO: add logic for no-ADC and no-GPS reversionary modes
            if (isAttitudeDataValid !== this.isAttitudeDataValid) {
                this.isAttitudeDataValid = isAttitudeDataValid;
                if (isAttitudeDataValid) {
                    for (const sub of this.attitudeDataSubs) {
                        sub.resume(true);
                    }
                }
                else {
                    for (const sub of this.attitudeDataSubs) {
                        sub.pause();
                    }
                }
                this.publisher.pub(`ahrs_attitude_data_valid_${this.index}`, this.isAttitudeDataValid, false, true);
            }
        }
        /**
         * Handles when the bank angle changes while AHRS is initializing.
         * @param bankAngle The bank angle of the aircraft.
         */
        onRollChanged(bankAngle) {
            if (Math.abs(bankAngle) >= 20) {
                this.initializationTimer.schedule(() => {
                    this.rollSub.pause();
                    this.setState(msfsSdk.AvionicsSystemState.On);
                }, 45000);
            }
        }
    }

    /**
     * Automatically selects the best AHRS from a set of candidates based on the current states of all systems. System state
     * desiribility depends on whether it is providing valid airspeed and altitude data.
     */
    class AhrsSystemSelector {
        /**
         * Creates a new instance of AhrsSystemSelector.
         * @param bus The event bus.
         * @param candidateSystemIndexes The indexes of the AHRS systems from which to select.
         * @param options Options with which to configure the selector.
         */
        constructor(bus, candidateSystemIndexes, options) {
            var _a;
            this.bus = bus;
            this._selectedIndex = msfsSdk.Subject.create(-1);
            /** The index of the selected AHRS, or `-1` if one could not be selected. */
            this.selectedIndex = this._selectedIndex;
            this._isSelectedAttitudeDataValid = msfsSdk.Subject.create(false);
            /** Whether the selected AHRS is providing valid attitude data. */
            this.isSelectedAttitudeDataValid = this._isSelectedAttitudeDataValid;
            this._isSelectedHeadingDataValid = msfsSdk.Subject.create(false);
            /** Whether the selected AHRS is providing valid heading data. */
            this.isSelectedHeadingDataValid = this._isSelectedHeadingDataValid;
            this.adcEntries = new Map();
            this.ahrsOrder = [];
            this.ahrsPriorityComparator = (a, b) => {
                var _a, _b;
                return ((_a = this.systemPriorities.getValue(b)) !== null && _a !== void 0 ? _a : 0) - ((_b = this.systemPriorities.getValue(a)) !== null && _b !== void 0 ? _b : 0);
            };
            this.needReselect = true;
            this.isAlive = true;
            this.isInit = false;
            this.candidateSystemIndexes = 'isSubscribableSet' in candidateSystemIndexes ? candidateSystemIndexes : msfsSdk.SetSubject.create(candidateSystemIndexes);
            if (options === null || options === void 0 ? void 0 : options.systemPriorities) {
                if ('isSubscribableMap' in options.systemPriorities) {
                    this.systemPriorities = options.systemPriorities;
                }
                else if (options.systemPriorities instanceof Map) {
                    this.systemPriorities = msfsSdk.MapSubject.create(options.systemPriorities);
                }
                else {
                    this.systemPriorities = msfsSdk.MapSubject.create(options.systemPriorities.map((ahrsIndex, arrayIndex, array) => [ahrsIndex, array.length - arrayIndex]));
                }
            }
            else {
                this.systemPriorities = msfsSdk.MapSubject.create();
            }
            this.desirabilityComparator = (_a = options === null || options === void 0 ? void 0 : options.desirabilityComparator) !== null && _a !== void 0 ? _a : AhrsSystemSelector.defaultDesirabilityComparator;
        }
        /**
         * Initializes this selector. Once initialized, this selector will automatically select the best AHRS among its
         * candidates.
         * @throws Error if this selector has been destroyed.
         */
        init() {
            if (!this.isAlive) {
                throw new Error('AhrsSystemSelector: cannot initialize a dead selector');
            }
            if (this.isInit) {
                return;
            }
            this.isInit = true;
            const sub = this.bus.getSubscriber();
            const scheduleReselect = () => { this.needReselect = true; };
            this.candidateSystemIndexesSub = this.candidateSystemIndexes.sub((set, type, key) => {
                const existing = this.adcEntries.get(key);
                if (existing) {
                    for (const subject of existing.subjects) {
                        subject.destroy();
                    }
                }
                if (type === msfsSdk.SubscribableSetEventType.Added) {
                    const subjects = [
                        msfsSdk.ConsumerSubject.create(sub.on(`ahrs_attitude_data_valid_${key}`), false),
                        msfsSdk.ConsumerSubject.create(sub.on(`ahrs_heading_data_valid_${key}`), false)
                    ];
                    const entry = {
                        subjects,
                        state: msfsSdk.MappedSubject.create(...subjects)
                    };
                    this.adcEntries.set(key, entry);
                    // When there is a change in an ADC state, we don't reselect immediately because the ADC could be in an
                    // intermediate transition state. Instead, we will schedule a reselect during the next update loop.
                    entry.state.sub(scheduleReselect);
                }
                else {
                    this.adcEntries.delete(key);
                }
                this.needReselect = true;
            }, true);
            this.systemPrioritiesSub = this.systemPriorities.sub(scheduleReselect);
            this.updateSub = sub.on('realTime').handle(this.update.bind(this));
        }
        /**
         * Updates this selector.
         */
        update() {
            if (this.needReselect) {
                this.selectIndex();
                this.needReselect = false;
            }
        }
        /**
         * Selects the index of the ADC with the most desirable state.
         */
        selectIndex() {
            let bestIndex;
            let bestState;
            if (this.adcEntries.size === 0) {
                bestIndex = -1;
                bestState = undefined;
            }
            else if (this.adcEntries.size === 1) {
                const entry = this.adcEntries.entries().next().value;
                bestIndex = entry[0];
                bestState = entry[1].state.get();
            }
            else {
                // Sort the systems in order of decreasing priority.
                this.ahrsOrder.length = 0;
                for (const index of this.adcEntries.keys()) {
                    this.ahrsOrder.push(index);
                }
                this.ahrsOrder.sort(this.ahrsPriorityComparator);
                bestIndex = -1;
                for (let i = 0; i < this.ahrsOrder.length; i++) {
                    const index = this.ahrsOrder[i];
                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                    const state = this.adcEntries.get(index).state.get();
                    if (!bestState || this.desirabilityComparator(state, bestState) < 0) {
                        bestIndex = index;
                        bestState = state;
                    }
                }
            }
            this._selectedIndex.set(bestIndex);
            if (bestState) {
                this._isSelectedAttitudeDataValid.set(bestState[0]);
                this._isSelectedHeadingDataValid.set(bestState[1]);
            }
            else {
                this._isSelectedAttitudeDataValid.set(false);
                this._isSelectedHeadingDataValid.set(false);
            }
        }
        /**
         * Destroys this selector.
         */
        destroy() {
            var _a, _b, _c;
            this.isAlive = false;
            (_a = this.updateSub) === null || _a === void 0 ? void 0 : _a.destroy();
            for (const entry of this.adcEntries.values()) {
                for (const subject of entry.subjects) {
                    subject.destroy();
                }
            }
            (_b = this.candidateSystemIndexesSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.systemPrioritiesSub) === null || _c === void 0 ? void 0 : _c.destroy();
        }
        /**
         * A function that compares the desirability of two AHRS systems using a set of default criteria. Systems are
         * assigned one point for each set of valid data they provide: attitude and heading. A system is deemed more
         * desirable than another if and only if the former is assigned more points than the latter.
         * @param a The state of the first AHRS system.
         * @param b The state of the second AHRS system.
         * @returns A number representing the relative desirability of the two ADC systems: `-1` if system `a` is more
         * desirable than `b`, `1` if system `b` is more desirable than `a`, and `0` if both are equally desirable.
         */
        static defaultDesirabilityComparator(a, b) {
            return (b[0] ? 1 : 0) + (b[1] ? 1 : 0)
                - (a[0] ? 1 : 0) + (a[1] ? 1 : 0);
        }
    }

    /**
     * A Garmin angle of attack computer system.
     */
    class AoaSystem extends msfsSdk.BasicAvionicsSystem {
        /**
         * Creates an instance of an angle of attack computer system.
         * @param index The index of the AoA computer.
         * @param bus An instance of the event bus.
         * @param powerSource The {@link ElectricalEvents} topic or electricity logic element to which to connect the
         * system's power.
         */
        constructor(index, bus, powerSource) {
            super(index, bus, `aoa_state_${index}`);
            this.initializationTime = 15000;
            this.dataSourceSubscriber = this.bus.getSubscriber();
            this.dataSubs = [];
            this.isAoaSubbed = false;
            this.isStallAoaSubbed = false;
            this.isZeroLiftAoaSubbed = false;
            this.normAoaTopic = `aoa_norm_aoa_${this.index}`;
            if (powerSource !== undefined) {
                this.connectToPower(powerSource);
            }
            this.startDataPublish();
        }
        /**
         * Starts publishing angle of attack data on the event bus.
         */
        startDataPublish() {
            const topics = [
                `aoa_aoa_${this.index}`,
                `aoa_stall_aoa_${this.index}`,
                `aoa_zero_lift_aoa_${this.index}`,
                `aoa_norm_aoa_${this.index}`
            ];
            for (const topic of topics) {
                if (this.bus.getTopicSubscriberCount(topic) > 0) {
                    this.onTopicSubscribed(topic);
                }
            }
            this.bus.getSubscriber().on('event_bus_topic_first_sub').handle(topic => {
                if (topics.includes(topic)) {
                    this.onTopicSubscribed(topic);
                }
            });
        }
        /**
         * Responds to when someone first subscribes to one of this system's data topics on the event bus.
         * @param topic The topic that was subscribed to.
         */
        onTopicSubscribed(topic) {
            const paused = this.state === msfsSdk.AvionicsSystemState.Failed || this.state === msfsSdk.AvionicsSystemState.Off;
            let shouldSubAoa = false;
            let shouldSubStallAoa = false;
            let shouldSubZeroLiftAoa = false;
            switch (topic) {
                case `aoa_aoa_${this.index}`:
                    shouldSubAoa = true;
                    break;
                case `aoa_stall_aoa_${this.index}`:
                    shouldSubStallAoa = true;
                    break;
                case `aoa_zero_lift_aoa_${this.index}`:
                    shouldSubZeroLiftAoa = true;
                    break;
                case `aoa_norm_aoa_${this.index}`:
                    shouldSubAoa = true;
                    shouldSubStallAoa = true;
                    shouldSubZeroLiftAoa = true;
                    break;
            }
            if (shouldSubAoa && !this.isAoaSubbed) {
                this.isAoaSubbed = true;
                const pubTopic = `aoa_aoa_${this.index}`;
                this.dataSubs.push(this.dataSourceSubscriber.on('aoa').handle(val => {
                    this.aoa = val;
                    this.publisher.pub(pubTopic, val, false, true);
                }, paused));
            }
            if (shouldSubStallAoa && !this.isStallAoaSubbed) {
                this.isStallAoaSubbed = true;
                const pubTopic = `aoa_stall_aoa_${this.index}`;
                this.dataSubs.push(this.dataSourceSubscriber.on('stall_aoa').handle(val => {
                    this.stallAoa = val;
                    this.publisher.pub(pubTopic, val, false, true);
                }, paused));
            }
            if (shouldSubZeroLiftAoa && !this.isZeroLiftAoaSubbed) {
                this.isZeroLiftAoaSubbed = true;
                const pubTopic = `aoa_zero_lift_aoa_${this.index}`;
                this.dataSubs.push(this.dataSourceSubscriber.on('zero_lift_aoa').handle(val => {
                    this.zeroLiftAoa = val;
                    this.publisher.pub(pubTopic, val, false, true);
                }, paused));
            }
        }
        /** @inheritdoc */
        onStateChanged(previousState, currentState) {
            if (currentState === msfsSdk.AvionicsSystemState.Failed || currentState === msfsSdk.AvionicsSystemState.Off) {
                for (const sub of this.dataSubs) {
                    sub.pause();
                }
            }
            else {
                for (const sub of this.dataSubs) {
                    sub.resume(true);
                }
            }
        }
        /** @inheritdoc */
        onUpdate() {
            super.onUpdate();
            if (this._state === msfsSdk.AvionicsSystemState.Failed
                || this._state === msfsSdk.AvionicsSystemState.Off
                || this.aoa === undefined
                || this.stallAoa === undefined
                || this.zeroLiftAoa === undefined) {
                return;
            }
            this.publisher.pub(this.normAoaTopic, (this.aoa - this.zeroLiftAoa) / (this.stallAoa - this.zeroLiftAoa), false, true);
        }
    }

    /**
     * A Garmin GPS receiver system.
     */
    class GpsReceiverSystem extends msfsSdk.BasicAvionicsSystem {
        /**
         * Creates an instance of a GPS receiver system.
         * @param index The index of the GPS receiver.
         * @param bus An instance of the event bus.
         * @param gpsSatComputer This system's GPS computer system.
         * @param powerSource The {@link ElectricalEvents} topic or electricity logic element to which to connect the
         * system's power.
         * @param options Options with which to configure the system.
         */
        constructor(index, bus, gpsSatComputer, powerSource, options) {
            var _a;
            super(index, bus, `gps_rec_state_${index}`);
            this.gpsSatComputer = gpsSatComputer;
            this.initializationTime = 0;
            this.cachedDataSourceTopicMap = {
                [`gps_rec_gps_system_nominal_channel_count_${this.index}`]: `gps_system_nominal_channel_count_${this.gpsSatComputer.index}`,
                [`gps_rec_gps_system_state_changed_${this.index}`]: `gps_system_state_changed_${this.gpsSatComputer.index}`,
                [`gps_rec_gps_system_sbas_state_changed_${this.index}`]: `gps_system_sbas_state_changed_${this.gpsSatComputer.index}`
            };
            this.uncachedDataSourceTopicMap = {
                [`gps_rec_gps_sat_state_changed_${this.index}`]: `gps_sat_state_changed_${this.gpsSatComputer.index}`,
                [`gps_rec_gps_sat_pos_calculated_${this.index}`]: `gps_sat_pos_calculated_${this.gpsSatComputer.index}`
            };
            this.dopDataSourceTopicMap = {
                [`gps_rec_gps_system_pdop_${this.index}`]: `gps_system_pdop_${this.gpsSatComputer.index}`,
                [`gps_rec_gps_system_hdop_${this.index}`]: `gps_system_hdop_${this.gpsSatComputer.index}`,
                [`gps_rec_gps_system_vdop_${this.index}`]: `gps_system_vdop_${this.gpsSatComputer.index}`
            };
            this.dataSourceSubscriber = this.bus.getSubscriber();
            this.dataSubs = [];
            this.dopSources = [];
            this.sbasState = msfsSdk.ConsumerSubject.create(null, msfsSdk.GPSSystemSBASState.Disabled);
            this.warmStartOnInit = (_a = options === null || options === void 0 ? void 0 : options.warmStartOnInit) !== null && _a !== void 0 ? _a : false;
            gpsSatComputer.init();
            if (powerSource !== undefined) {
                this.connectToPower(powerSource);
            }
            else if (gpsSatComputer.syncRole !== 'replica') {
                // If our power source is undefined, then the system is always considered to be in the on state. Therefore we
                // will force the GPS to immediately acquire and use all the satellites it can since a system that is always on
                // never needs to initialize.
                gpsSatComputer.acquireAndUseSatellites();
            }
            this.startDataPublish();
        }
        /**
         * Starts publishing data on the event bus.
         */
        startDataPublish() {
            for (const topic of Object.keys(this.cachedDataSourceTopicMap)) {
                this.dataSubs.push(this.dataSourceSubscriber.on(this.cachedDataSourceTopicMap[topic]).handle(val => {
                    this.publisher.pub(topic, val, false, true);
                }));
            }
            for (const topic of Object.keys(this.uncachedDataSourceTopicMap)) {
                this.dataSubs.push(this.dataSourceSubscriber.on(this.uncachedDataSourceTopicMap[topic]).handle(val => {
                    this.publisher.pub(topic, val, false, false);
                }));
            }
            // Garmin seems to halve DOP values when SBAS is active. It could be a trainer-specific behavior, but in the
            // absence of any other information, we will implement it here.
            this.sbasState.setConsumer(this.dataSourceSubscriber.on(`gps_system_sbas_state_changed_${this.gpsSatComputer.index}`));
            for (const topic of Object.keys(this.dopDataSourceTopicMap)) {
                const dopSource = msfsSdk.ConsumerSubject.create(this.dataSourceSubscriber.on(this.dopDataSourceTopicMap[topic]), -1);
                this.dopSources.push(dopSource);
                const processedDop = msfsSdk.MappedSubject.create(([dop, sbasState]) => dop <= 0 ? dop : dop * (sbasState === msfsSdk.GPSSystemSBASState.Active ? 0.5 : 1), dopSource, this.sbasState);
                this.dataSubs.push(processedDop.sub(dop => {
                    this.publisher.pub(topic, dop, false, true);
                }, true));
            }
        }
        /** @inheritdoc */
        onStateChanged(previousState, currentState) {
            if (previousState === undefined && this.gpsSatComputer.syncRole !== 'replica') {
                if (currentState === msfsSdk.AvionicsSystemState.On) {
                    // If this is the first time we are setting our state and the state is on, then we assume that the system was on at
                    // flight load, in which case we will force the GPS to immediately acquire and use all the satellites it can so
                    // that we don't force people to wait for satellite acquisition when loading onto the runway/in the air.
                    this.gpsSatComputer.acquireAndUseSatellites();
                }
                else if (this.warmStartOnInit) {
                    // If this is the first time we are setting our state and the state is not on and this system is configured for
                    // warm starts on initial power-on, then sync the GPS's last known position with the plane's current position
                    // and force a download of the almanac.
                    this.gpsSatComputer.syncLastKnownPosition();
                    this.gpsSatComputer.downloadAlamanac();
                }
            }
            // Reset the GPS sat computer if the system is not operating and its receiver is not a replica (a replica receiver
            // will get the reset command from its primary).
            if ((currentState === msfsSdk.AvionicsSystemState.Failed || currentState === msfsSdk.AvionicsSystemState.Off) && this.gpsSatComputer.syncRole !== 'replica') {
                this.gpsSatComputer.reset();
            }
        }
        /** @inheritdoc */
        onUpdate() {
            super.onUpdate();
            if (this._state === msfsSdk.AvionicsSystemState.On || this._state === undefined) {
                this.gpsSatComputer.onUpdate();
            }
        }
    }

    /**
     * The GMU44 magnetometer system.
     */
    class MagnetometerSystem extends msfsSdk.BasicAvionicsSystem {
        /**
         * Creates an instance of the MagnetometerSystem.
         * @param index The index of the system.
         * @param bus The instance of the event bus for the system to use.
         * @param powerSource The {@link ElectricalEvents} topic or electricity logic element to which to connect the
         * system's power.
         */
        constructor(index, bus, powerSource) {
            super(index, bus, `magnetometer_state_${index}`);
            this.index = index;
            this.bus = bus;
            this.initializationTime = 5000;
            if (powerSource !== undefined) {
                this.connectToPower(powerSource);
            }
        }
    }

    /**
     * A Garmin marker beacon receiver system.
     */
    class MarkerBeaconSystem extends msfsSdk.BasicAvionicsSystem {
        /**
         * Creates an instance of a marker beacon system.
         * @param index The index of the marker beacon system.
         * @param bus An instance of the event bus.
         * @param powerSource The {@link ElectricalEvents} topic or electricity logic element to which to connect the
         * system's power.
         */
        constructor(index, bus, powerSource) {
            super(index, bus, `marker_state_${index}`);
            const markerBeaconStateTopic = `marker_mkr_bcn_state_${this.index}`;
            if (this.bus.getTopicSubscriberCount(markerBeaconStateTopic) > 0) {
                this.onMarkerBeaconStateTopicSubscribed();
            }
            this.bus.getSubscriber().on('event_bus_topic_first_sub').handle(topic => {
                if (topic === markerBeaconStateTopic) {
                    this.onMarkerBeaconStateTopicSubscribed();
                }
            });
            if (powerSource !== undefined) {
                this.connectToPower(powerSource);
            }
        }
        /**
         * Responds to when someone first subscribes to this system's marker beacon state data topic on the event bus.
         */
        onMarkerBeaconStateTopicSubscribed() {
            const topic = `marker_mkr_bcn_state_${this.index}`;
            const paused = this.state === msfsSdk.AvionicsSystemState.Failed || this.state === msfsSdk.AvionicsSystemState.Off;
            this.beaconStateSub = this.bus.getSubscriber().on('marker_beacon_state').handle(val => {
                this.publisher.pub(topic, val);
            }, paused);
        }
        /** @inheritdoc */
        onStateChanged(previousState, currentState) {
            var _a, _b;
            if (currentState === msfsSdk.AvionicsSystemState.Failed || currentState === msfsSdk.AvionicsSystemState.Off) {
                (_a = this.beaconStateSub) === null || _a === void 0 ? void 0 : _a.pause();
            }
            else {
                (_b = this.beaconStateSub) === null || _b === void 0 ? void 0 : _b.resume(true);
            }
        }
    }

    /**
     * A Garmin GRA 55(00) radar altimeter system.
     */
    class RadarAltimeterSystem extends msfsSdk.BasicAvionicsSystem {
        /**
         * Creates an instance of a radar altimeter system.
         * @param index The index of the radar altimeter.
         * @param bus An instance of the event bus.
         * @param powerSource The {@link ElectricalEvents} topic or electricity logic element to which to connect the
         * system's power.
         */
        constructor(index, bus, powerSource) {
            super(index, bus, `radaralt_state_${index}`);
            const radioAltTopic = `radaralt_radio_alt_${this.index}`;
            if (this.bus.getTopicSubscriberCount(radioAltTopic) > 0) {
                this.onRadioAltTopicSubscribed();
            }
            this.bus.getSubscriber().on('event_bus_topic_first_sub').handle(topic => {
                if (topic === radioAltTopic) {
                    this.onRadioAltTopicSubscribed();
                }
            });
            if (powerSource !== undefined) {
                this.connectToPower(powerSource);
            }
        }
        /**
         * Responds to when someone first subscribes to this system's radar altitude data topic on the event bus.
         */
        onRadioAltTopicSubscribed() {
            const topic = `radaralt_radio_alt_${this.index}`;
            const paused = this.state === msfsSdk.AvionicsSystemState.Failed || this.state === msfsSdk.AvionicsSystemState.Off;
            this.radioAltSub = this.bus.getSubscriber().on('radio_alt').handle(val => {
                this.publisher.pub(topic, val);
            }, paused);
        }
        /** @inheritdoc */
        onStateChanged(previousState, currentState) {
            var _a, _b;
            if (currentState === msfsSdk.AvionicsSystemState.Failed || currentState === msfsSdk.AvionicsSystemState.Off) {
                (_a = this.radioAltSub) === null || _a === void 0 ? void 0 : _a.pause();
            }
            else {
                (_b = this.radioAltSub) === null || _b === void 0 ? void 0 : _b.resume(true);
            }
        }
    }

    /**
     * A Garmin traffic avionics system.
     */
    class TrafficAvionicsSystem extends msfsSdk.BasicAvionicsSystem {
        /**
         * Creates an instance of a traffic avionics system.
         * @param bus An instance of the event bus.
         * @param trafficSystem This system's traffic system.
         * @param powerSource The {@link ElectricalEvents} topic or electricity logic element to which to connect the
         * system's power.
         * @param initializationTime The time required for the system to initialize, in milliseconds. Defaults to 0.
         */
        constructor(bus, trafficSystem, powerSource, initializationTime = 0) {
            super(1, bus, 'traffic_avionics_state');
            this.trafficSystem = trafficSystem;
            this.initializationTime = initializationTime;
            if (powerSource !== undefined) {
                this.connectToPower(powerSource);
            }
        }
        /** @inheritdoc */
        onStateChanged(previousState, currentState) {
            this.trafficSystem.setPowered(currentState === msfsSdk.AvionicsSystemState.On);
        }
    }

    /**
     * A Garmin weather radar avionics system.
     */
    class WeatherRadarAvionicsSystem extends msfsSdk.BasicAvionicsSystem {
        /**
         * Creates an instance of a weather radar avionics system.
         * @param bus An instance of the event bus.
         * @param powerSource The {@link AvionicsSystemPowerEvents} topic or electricity logic element to which to connect
         * the system's power.
         * @param activePowerSource The {@link AvionicsSystemPowerEvents} topic or electricity logic element to which to
         * connect the system's power when radar is actively scanning. If defined, then the system will enter the failed
         * state if the radar is actively scanning and the active radar power source is unpowered.
         * @param isRadarScanActive Whether the weather radar is actively scanning. Ignored if {@linkcode activePowerSource}
         * is not defined.
         * @param initializationTime The time required for the system to initialize, in milliseconds. Defaults to 0.
         */
        constructor(bus, powerSource, activePowerSource, isRadarScanActive, initializationTime = 0) {
            super(1, bus, 'wx_radar_state');
            this.isRadarScanActive = isRadarScanActive;
            this.initializationTime = initializationTime;
            if (powerSource !== undefined) {
                this.connectToPower(powerSource);
            }
            if (activePowerSource !== undefined) {
                this.connectToActivePower(activePowerSource);
            }
        }
        /** @inheritdoc */
        onPowerValid() {
            var _a, _b;
            super.onPowerValid();
            (_a = this.electricalActivePowerSub) === null || _a === void 0 ? void 0 : _a.resume(true);
            (_b = this.isRadarScanActive) === null || _b === void 0 ? void 0 : _b.sub(this.onRadarScanActiveChanged.bind(this));
        }
        /**
         * Connects this system's active radar power state to an {@link AvionicsSystemPowerEvents} topic or electricity logic
         * element.
         * @param source The source to which to connect this system's active radar power state.
         */
        connectToActivePower(source) {
            var _a;
            (_a = this.electricalActivePowerSub) === null || _a === void 0 ? void 0 : _a.destroy();
            this.electricalActivePowerSub = undefined;
            this.electricalActivePowerLogic = undefined;
            if (typeof source === 'string') {
                this.electricalActivePowerSub = this.bus.getSubscriber()
                    .on(source)
                    .whenChanged()
                    .handle(this.onActivePowerChanged.bind(this), !this.isPowerValid);
            }
            else {
                this.electricalActivePowerLogic = source;
                this.updatePowerFromLogic();
            }
        }
        /** @inheritdoc */
        onPowerChanged(isPowered) {
            const wasPowered = this.isPowered;
            this.isPowered = isPowered;
            if (wasPowered === undefined) {
                this.initializationTimer.clear();
                if (isPowered) {
                    this.updateStateFromActivePower();
                }
                else {
                    this.setState(msfsSdk.AvionicsSystemState.Off);
                }
            }
            else {
                if (isPowered) {
                    this.setState(msfsSdk.AvionicsSystemState.Initializing);
                    this.initializationTimer.schedule(this.updateStateFromActivePower.bind(this), this.initializationTime);
                }
                else {
                    this.initializationTimer.clear();
                    this.setState(msfsSdk.AvionicsSystemState.Off);
                }
            }
        }
        /**
         * A callback called when the connected active radar power state of the avionics system changes.
         * @param isPowered Whether or not the system is powered.
         */
        onActivePowerChanged(isPowered) {
            this.isActivePowered = isPowered;
            if (this._state !== msfsSdk.AvionicsSystemState.Off && this._state !== msfsSdk.AvionicsSystemState.Initializing) {
                this.updateStateFromActivePower();
            }
        }
        /**
         * Responds to when whether the radar is actively scanning changes.
         */
        onRadarScanActiveChanged() {
            if (this._state !== msfsSdk.AvionicsSystemState.Off && this._state !== msfsSdk.AvionicsSystemState.Initializing) {
                this.updateStateFromActivePower();
            }
        }
        /**
         * Updates this system's state from its active radar power state and whether the radar is actively scanning.
         */
        updateStateFromActivePower() {
            if (!this.isRadarScanActive || !this.isRadarScanActive.get() || this.isActivePowered === undefined || this.isActivePowered) {
                this.setState(msfsSdk.AvionicsSystemState.On);
            }
            else {
                this.setState(msfsSdk.AvionicsSystemState.Failed);
            }
        }
        /** @inheritdoc */
        onUpdate() {
            super.onUpdate();
            this.updateActivePowerFromLogic();
        }
        /**
         * Updates this system's active radar power state from an electricity logic element.
         */
        updateActivePowerFromLogic() {
            if (!this.isPowerValid || this.electricalActivePowerLogic === undefined) {
                return;
            }
            const isPowered = this.electricalActivePowerLogic.getValue() !== 0;
            if (isPowered !== this.isActivePowered) {
                this.onActivePowerChanged(isPowered);
            }
        }
    }

    /**
     * A Garmin terrain alerting system module that handles excessive closure rate (ECR) alerts.
     */
    class GarminExcessiveClosureRateModule {
        /**
         * Creates a new instance of GarminExcessiveClosureRateModule.
         * @param options Options with which to configure the module.
         */
        constructor(options) {
            var _a, _b, _c, _d, _e, _f, _g, _h, _j;
            this.isReset = true;
            this.activeAlert = null;
            this.lastAgl = undefined;
            this.lastUpdateTime = undefined;
            this.functionAsGpws = (_a = options === null || options === void 0 ? void 0 : options.functionAsGpws) !== null && _a !== void 0 ? _a : false;
            this.forceNoFlta = (_b = options === null || options === void 0 ? void 0 : options.forceNoFlta) !== null && _b !== void 0 ? _b : false;
            this.flapsLandingAngle = (_c = options === null || options === void 0 ? void 0 : options.flapsLandingAngle) !== null && _c !== void 0 ? _c : [Infinity, Infinity];
            this.inhibitFlags = (options === null || options === void 0 ? void 0 : options.inhibitFlags) ? Array.from(options.inhibitFlags) : [];
            this.closureRateSmoother = new msfsSdk.MultiExpSmoother((_e = (_d = options === null || options === void 0 ? void 0 : options.closureRateSmootherParams) === null || _d === void 0 ? void 0 : _d.tau) !== null && _e !== void 0 ? _e : 1000 / Math.LN2, (_f = options === null || options === void 0 ? void 0 : options.closureRateSmootherParams) === null || _f === void 0 ? void 0 : _f.tauVelocity, (_g = options === null || options === void 0 ? void 0 : options.closureRateSmootherParams) === null || _g === void 0 ? void 0 : _g.tauAccel, null, null, null, (_j = (_h = options === null || options === void 0 ? void 0 : options.closureRateSmootherParams) === null || _h === void 0 ? void 0 : _h.dtThreshold) !== null && _j !== void 0 ? _j : 10000);
        }
        /** @inheritDoc */
        onInit() {
            // noop
        }
        /** @inheritDoc */
        onUpdate(operatingMode, statuses, inhibits, data, alertController) {
            const dt = this.lastUpdateTime === undefined ? 0 : Math.max(data.realTime - this.lastUpdateTime, 0) * data.simRate;
            this.lastUpdateTime = data.realTime;
            if (operatingMode !== exports.TerrainSystemOperatingMode.Operating
                || (this.functionAsGpws
                    ? statuses.has(exports.GarminTawsStatus.GpwsFailed)
                    : statuses.has(exports.GarminTawsStatus.TawsFailed) || statuses.has(exports.GarminTawsStatus.TawsNotAvailable))
                || data.isOnGround) {
                this.reset(alertController);
                return;
            }
            // Check if one of our inhibit flags is active.
            for (let i = 0; i < this.inhibitFlags.length; i++) {
                if (inhibits.has(this.inhibitFlags[i])) {
                    this.reset(alertController);
                    return;
                }
            }
            const noFlta = this.forceNoFlta || statuses.has(exports.GarminTawsStatus.TawsFailed) || statuses.has(exports.GarminTawsStatus.TawsNotAvailable);
            // Alerting is disabled when the distance to the nearest airport is less than or equal to 5 nautical miles and
            // FLTA alerting is available.
            if (!noFlta && data.nearestAirport) {
                this.reset(alertController);
                return;
            }
            if (this.functionAsGpws) {
                if (data.isRadarAltitudeValid && (data.isGpsPosValid || data.isBaroAltitudeValid) && data.radarAltitude <= 2500) {
                    this.isReset = false;
                    this.updateAlerts(data, noFlta, data.radarAltitude, dt, alertController);
                }
                else {
                    this.reset(alertController);
                }
            }
            else {
                if (data.isGpsPosValid) {
                    this.isReset = false;
                    this.updateAlerts(data, noFlta, data.gpsAgl, dt, alertController);
                }
                else {
                    this.reset(alertController);
                }
            }
        }
        /**
         * Updates whether to issue an excessive closure rate alert.
         * @param data Terrain system data.
         * @param noFlta Whether FLTA is not available.
         * @param agl The airplane's current height above ground level, in feet.
         * @param dt The elapsed simulation time, in milliseconds, since the last update cycle.
         * @param alertController A controller for alerts tracked by this module's parent system.
         */
        updateAlerts(data, noFlta, agl, dt, alertController) {
            let activeAlert = null;
            if (this.lastAgl === undefined) {
                this.closureRateSmoother.reset();
            }
            else {
                const closureRate = this.closureRateSmoother.next((this.lastAgl - agl) / dt * 60000, dt);
                const isGearDown = data.gearPosition[0] > 0 || data.gearPosition[1] > 0 || data.gearPosition[2] > 0;
                const isFlapsLanding = data.flapsAngle[0] >= this.flapsLandingAngle[0] && data.flapsAngle[0] <= this.flapsLandingAngle[1]
                    && data.flapsAngle[1] >= this.flapsLandingAngle[0] && data.flapsAngle[1] <= this.flapsLandingAngle[1];
                // Set up hysteresis offsets.
                const warningThresholdOffset = this.activeAlert === exports.GarminTawsAlert.EcrWarning ? -100 : 0;
                const cautionThresholdOffset = this.activeAlert === exports.GarminTawsAlert.EcrCaution ? -100 : 0;
                if (GarminExcessiveClosureRateModule.isWarning(agl, closureRate, noFlta, isFlapsLanding, isGearDown, warningThresholdOffset)) {
                    activeAlert = exports.GarminTawsAlert.EcrWarning;
                }
                else if (GarminExcessiveClosureRateModule.isCaution(agl, closureRate, noFlta, isFlapsLanding, isGearDown, cautionThresholdOffset)) {
                    activeAlert = exports.GarminTawsAlert.EcrCaution;
                }
            }
            if (activeAlert !== this.activeAlert) {
                activeAlert !== null && alertController.activateAlert(activeAlert);
                this.activeAlert !== null && alertController.deactivateAlert(this.activeAlert);
                this.activeAlert = activeAlert;
            }
            this.lastAgl = agl;
        }
        /**
         * Deactivates all excessive closure rate alerts.
         * @param alertController A controller for alerts tracked by this module's parent system.
         */
        reset(alertController) {
            if (this.isReset) {
                return;
            }
            this.lastAgl = undefined;
            this.activeAlert = null;
            alertController.deactivateAlert(exports.GarminTawsAlert.EcrWarning);
            alertController.deactivateAlert(exports.GarminTawsAlert.EcrCaution);
            this.isReset = true;
        }
        /** @inheritDoc */
        onDestroy() {
            // noop
        }
        /**
         * Checks whether a closure rate meets the threshold for an excessive closure rate warning alert for a given height
         * above ground level.
         * @param agl The height above ground level, in feet.
         * @param closureRate The closure rate, in feet per minute.
         * @param noFlta Whether FLTA is not available.
         * @param isFlapsLanding Whether flaps are in the landing configuration.
         * @param isGearDown Whether landing gear are extended.
         * @param thresholdOffset An offset to apply to the closure rate threshold for alerting, in feet per minute.
         * @returns Whether the specified closure rate meets the threshold for an excessive closure rate warning alert.
         */
        static isWarning(agl, closureRate, noFlta, isFlapsLanding, isGearDown, thresholdOffset) {
            let breakpoints;
            if (isFlapsLanding) {
                breakpoints = noFlta
                    ? GarminExcessiveClosureRateModule.WARNING_FLAPS_LANDING_NO_FLTA_BREAKPOINTS
                    : GarminExcessiveClosureRateModule.WARNING_FLAPS_LANDING_BREAKPOINTS;
            }
            else {
                breakpoints = noFlta
                    ? GarminExcessiveClosureRateModule.WARNING_NO_FLTA_BREAKPOINTS
                    : isGearDown
                        ? GarminExcessiveClosureRateModule.WARNING_GEAR_DOWN_BREAKPOINTS
                        : GarminExcessiveClosureRateModule.WARNING_GEAR_UP_BREAKPOINTS;
            }
            return GarminExcessiveClosureRateModule.isAlert(breakpoints, agl, closureRate, thresholdOffset);
        }
        /**
         * Checks whether a closure rate meets the threshold for an excessive closure rate caution alert for a given height
         * above ground level.
         * @param agl The height above ground level, in feet.
         * @param closureRate The closure rate, in feet per minute.
         * @param noFlta Whether FLTA is not available.
         * @param isFlapsLanding Whether flaps are in the landing configuration.
         * @param isGearDown Whether landing gear are extended.
         * @param thresholdOffset An offset to apply to the closure rate threshold for alerting, in feet per minute.
         * @returns Whether the specified closure rate meets the threshold for an excessive closure rate caution alert.
         */
        static isCaution(agl, closureRate, noFlta, isFlapsLanding, isGearDown, thresholdOffset) {
            let breakpoints;
            if (isFlapsLanding) {
                breakpoints = noFlta
                    ? GarminExcessiveClosureRateModule.CAUTION_FLAPS_LANDING_NO_FLTA_BREAKPOINTS
                    : GarminExcessiveClosureRateModule.CAUTION_FLAPS_LANDING_BREAKPOINTS;
            }
            else {
                breakpoints = noFlta
                    ? GarminExcessiveClosureRateModule.CAUTION_NO_FLTA_BREAKPOINTS
                    : isGearDown
                        ? GarminExcessiveClosureRateModule.CAUTION_GEAR_DOWN_BREAKPOINTS
                        : GarminExcessiveClosureRateModule.CAUTION_GEAR_UP_BREAKPOINTS;
            }
            return GarminExcessiveClosureRateModule.isAlert(breakpoints, agl, closureRate, thresholdOffset);
        }
        /**
         * Checks whether a closure rate meets the threshold for an excessive closure rate alert for a given height above
         * ground level.
         * @param breakpoints The closure rate threshold vs. height above ground level breakpoints to use.
         * @param agl The height above ground level, in feet.
         * @param closureRate The closure rate, in feet per minute.
         * @param thresholdOffset An offset to apply to the closure rate threshold for alerting, in feet per minute.
         * @returns Whether the specified closure rate meets the threshold for an excessive closure rate caution alert.
         */
        static isAlert(breakpoints, agl, closureRate, thresholdOffset) {
            let threshold = Infinity;
            if (agl >= breakpoints[0][0]) {
                for (let i = 1; i < breakpoints.length; i++) {
                    const prevBreakpoint = breakpoints[i - 1];
                    const breakpoint = breakpoints[i];
                    if (agl <= breakpoint[0]) {
                        threshold = msfsSdk.MathUtils.lerp(agl, prevBreakpoint[0], breakpoint[0], prevBreakpoint[1], breakpoint[1], true, true);
                    }
                }
            }
            return closureRate >= threshold + thresholdOffset;
        }
    }
    GarminExcessiveClosureRateModule.WARNING_NO_FLTA_BREAKPOINTS = [
        [50, 2060],
        [75, 2100],
        [1500, 3900],
        [1800, 6000]
    ];
    GarminExcessiveClosureRateModule.CAUTION_NO_FLTA_BREAKPOINTS = [
        [50, 2060],
        [100, 2100],
        [1875, 3900],
        [2250, 6000]
    ];
    GarminExcessiveClosureRateModule.WARNING_GEAR_UP_BREAKPOINTS = [
        [50, 2550],
        [975, 4000]
    ];
    GarminExcessiveClosureRateModule.CAUTION_GEAR_UP_BREAKPOINTS = [
        [50, 2550],
        [1220, 4000]
    ];
    GarminExcessiveClosureRateModule.WARNING_GEAR_DOWN_BREAKPOINTS = [
        [50, 2910],
        [650, 4000]
    ];
    GarminExcessiveClosureRateModule.CAUTION_GEAR_DOWN_BREAKPOINTS = [
        [50, 2890],
        [815, 4000]
    ];
    GarminExcessiveClosureRateModule.WARNING_FLAPS_LANDING_BREAKPOINTS = [
        [220, 2705],
        [650, 3250]
    ];
    GarminExcessiveClosureRateModule.CAUTION_FLAPS_LANDING_BREAKPOINTS = [
        [220, 2666.666666666666],
        [275, 2700],
        [810, 3250]
    ];
    GarminExcessiveClosureRateModule.WARNING_FLAPS_LANDING_NO_FLTA_BREAKPOINTS = [
        [220, 2250],
        [795, 3000]
    ];
    GarminExcessiveClosureRateModule.CAUTION_FLAPS_LANDING_NO_FLTA_BREAKPOINTS = [
        [220, 2210],
        [270, 2250],
        [990, 3000]
    ];

    /**
     * A Garmin terrain alerting system module that handles excessive descent rate (EDR) alerts.
     */
    class GarminExcessiveDescentRateModule {
        /**
         * Creates a new instance of GarminExcessiveDescentRateModule.
         * @param options Options with which to configure the module.
         */
        constructor(options) {
            var _a;
            this.isReset = true;
            this.activeAlert = null;
            this.functionAsGpws = (_a = options === null || options === void 0 ? void 0 : options.functionAsGpws) !== null && _a !== void 0 ? _a : false;
            this.inhibitFlags = (options === null || options === void 0 ? void 0 : options.inhibitFlags) ? Array.from(options.inhibitFlags) : [];
        }
        /** @inheritDoc */
        onInit() {
            // noop
        }
        /** @inheritDoc */
        onUpdate(operatingMode, statuses, inhibits, data, alertController) {
            if (operatingMode !== exports.TerrainSystemOperatingMode.Operating
                || (this.functionAsGpws
                    ? statuses.has(exports.GarminTawsStatus.GpwsFailed)
                    : statuses.has(exports.GarminTawsStatus.TawsFailed) || statuses.has(exports.GarminTawsStatus.TawsNotAvailable))
                || data.isOnGround) {
                this.reset(alertController);
                return;
            }
            // Check if one of our inhibit flags is active.
            for (let i = 0; i < this.inhibitFlags.length; i++) {
                if (inhibits.has(this.inhibitFlags[i])) {
                    this.reset(alertController);
                    return;
                }
            }
            if (this.functionAsGpws) {
                if (data.isRadarAltitudeValid && (data.isGpsPosValid || data.isBaroAltitudeValid) && data.radarAltitude <= 2500) {
                    this.isReset = false;
                    this.updateAlerts(data.radarAltitude, data.isGpsPosValid ? data.gpsVerticalSpeed : data.baroVerticalSpeed, alertController);
                }
                else {
                    this.reset(alertController);
                }
            }
            else {
                if (data.isGpsPosValid) {
                    this.isReset = false;
                    this.updateAlerts(data.gpsAgl, data.gpsVerticalSpeed, alertController);
                }
                else {
                    this.reset(alertController);
                }
            }
        }
        /**
         * Updates whether to issue an excessive descent rate alert.
         * @param agl The airplane's current height above ground level, in feet.
         * @param verticalSpeed The airplane's current vertical speed, in feet per minute.
         * @param alertController A controller for alerts tracked by this module's parent system.
         */
        updateAlerts(agl, verticalSpeed, alertController) {
            // Set up hysteresis offsets.
            const warningThresholdOffset = this.activeAlert === exports.GarminTawsAlert.EdrWarning ? -100 : 0;
            const cautionThresholdOffset = this.activeAlert === exports.GarminTawsAlert.EdrCaution ? -100 : 0;
            let activeAlert = null;
            if (GarminExcessiveDescentRateModule.isWarning(agl, -verticalSpeed, warningThresholdOffset)) {
                activeAlert = exports.GarminTawsAlert.EdrWarning;
            }
            else if (GarminExcessiveDescentRateModule.isCaution(agl, -verticalSpeed, cautionThresholdOffset)) {
                activeAlert = exports.GarminTawsAlert.EdrCaution;
            }
            if (activeAlert !== this.activeAlert) {
                activeAlert !== null && alertController.activateAlert(activeAlert);
                this.activeAlert !== null && alertController.deactivateAlert(this.activeAlert);
                this.activeAlert = activeAlert;
            }
        }
        /**
         * Deactivates all excessive descent rate alerts.
         * @param alertController A controller for alerts tracked by this module's parent system.
         */
        reset(alertController) {
            if (this.isReset) {
                return;
            }
            this.activeAlert = null;
            alertController.deactivateAlert(exports.GarminTawsAlert.EdrWarning);
            alertController.deactivateAlert(exports.GarminTawsAlert.EdrCaution);
            this.isReset = true;
        }
        /** @inheritDoc */
        onDestroy() {
            // noop
        }
        /**
         * Checks whether a descent rate meets the threshold for an excessive descent rate warning alert for a given height
         * above ground level.
         * @param agl The height above ground level, in feet.
         * @param descentRate The descent rate, in feet per minute.
         * @param thresholdOffset An offset to apply to the descent rate threshold for alerting, in feet per minute.
         * @returns Whether the specified descent rate meets the threshold for an excessive descent rate warning alert.
         */
        static isWarning(agl, descentRate, thresholdOffset) {
            let threshold;
            if (agl <= 50) {
                threshold = Infinity;
            }
            else if (agl <= 100) {
                threshold = msfsSdk.MathUtils.lerp(agl, 50, 100, 1300, 1400);
            }
            else if (agl <= 1100) {
                threshold = msfsSdk.MathUtils.lerp(agl, 100, 1100, 1400, 2275);
            }
            else if (agl <= 1200) {
                threshold = msfsSdk.MathUtils.lerp(agl, 1100, 1200, 2275, 2400);
            }
            else if (agl <= 2450) {
                threshold = msfsSdk.MathUtils.lerp(agl, 1200, 2450, 2400, 4900);
            }
            else {
                threshold = msfsSdk.MathUtils.lerp(agl, 2450, 4400, 4900, 12000);
            }
            return descentRate >= threshold + thresholdOffset;
        }
        /**
         * Checks whether a descent rate meets the threshold for an excessive descent rate caution alert for a given height
         * above ground level.
         * @param agl The height above ground level, in feet.
         * @param descentRate The descent rate, in feet per minute.
         * @param thresholdOffset An offset to apply to the descent rate threshold for alerting, in feet per minute.
         * @returns Whether the specified descent rate meets the threshold for an excessive descent rate caution alert.
         */
        static isCaution(agl, descentRate, thresholdOffset) {
            let threshold;
            if (agl <= 50) {
                threshold = Infinity;
            }
            else if (agl <= 150) {
                threshold = msfsSdk.MathUtils.lerp(agl, 50, 150, 1300, 1400);
            }
            else if (agl <= 1400) {
                threshold = msfsSdk.MathUtils.lerp(agl, 150, 1400, 1400, 2275);
            }
            else if (agl <= 1500) {
                threshold = msfsSdk.MathUtils.lerp(agl, 1400, 1500, 2275, 2400);
            }
            else if (agl <= 3075) {
                threshold = msfsSdk.MathUtils.lerp(agl, 1500, 3075, 2400, 4900);
            }
            else {
                threshold = msfsSdk.MathUtils.lerp(agl, 3075, 5500, 4900, 12000);
            }
            return descentRate >= threshold + thresholdOffset;
        }
    }

    /**
     * A Garmin terrain alerting system module that handles negative climb rate after takeoff (NCR) alerts.
     */
    class GarminNegativeClimbRateModule {
        /**
         * Creates a new instance of GarminNegativeClimbRateModule.
         * @param options Options with which to configure the module.
         */
        constructor(options) {
            var _a;
            this.isReset = true;
            this.altitudeLossReference = undefined;
            this.isAltitudeLossAlertActive = false;
            this.isSinkRateAlertActive = false;
            this.functionAsGpws = (_a = options === null || options === void 0 ? void 0 : options.functionAsGpws) !== null && _a !== void 0 ? _a : false;
            this.inhibitFlags = (options === null || options === void 0 ? void 0 : options.inhibitFlags) ? Array.from(options.inhibitFlags) : [];
        }
        /** @inheritDoc */
        onInit() {
            // noop
        }
        /** @inheritDoc */
        onUpdate(operatingMode, statuses, inhibits, data, alertController) {
            if (operatingMode !== exports.TerrainSystemOperatingMode.Operating
                || (this.functionAsGpws
                    ? statuses.has(exports.GarminTawsStatus.GpwsFailed)
                    : statuses.has(exports.GarminTawsStatus.TawsFailed) || statuses.has(exports.GarminTawsStatus.TawsNotAvailable))
                || data.isOnGround) {
                this.reset(alertController);
                return;
            }
            // Check if one of our inhibit flags is active.
            for (let i = 0; i < this.inhibitFlags.length; i++) {
                if (inhibits.has(this.inhibitFlags[i])) {
                    this.reset(alertController);
                    return;
                }
            }
            if (!data.departureAirport
                || !data.departureRunway
                || !data.isHeadingValid
                || msfsSdk.MathUtils.diffAngleDeg(data.departureRunway.course, data.headingTrue, false) > 110
                || data.gpsPos.distance(data.departureAirport) > GarminNegativeClimbRateModule.MAX_DISTANCE_FROM_AIRPORT) {
                this.reset(alertController);
                return;
            }
            if (this.functionAsGpws) {
                if (data.isRadarAltitudeValid && (data.isGpsPosValid || data.isBaroAltitudeValid) && data.radarAltitude <= 2500) {
                    this.isReset = false;
                    let altitude;
                    let verticalSpeed;
                    if (data.isGpsPosValid) {
                        altitude = data.gpsAltitude;
                        verticalSpeed = data.gpsVerticalSpeed;
                    }
                    else {
                        altitude = data.baroAltitude;
                        verticalSpeed = data.baroVerticalSpeed;
                    }
                    this.updateAlerts(data.radarAltitude, altitude, verticalSpeed, alertController);
                }
                else {
                    this.reset(alertController);
                }
            }
            else {
                if (data.isGpsPosValid) {
                    this.isReset = false;
                    const agl = data.gpsAltitude - msfsSdk.UnitType.METER.convertTo(data.departureRunway.elevationEnd, msfsSdk.UnitType.FOOT);
                    this.updateAlerts(agl, data.gpsAltitude, data.gpsVerticalSpeed, alertController);
                }
                else {
                    this.reset(alertController);
                }
            }
        }
        /**
         * Updates whether to issue a negative climb rate after takeoff alert.
         * @param agl The airplane's current height above ground level, in feet.
         * @param altitude The airplane's current altitude, in feet.
         * @param verticalSpeed The airplane's current vertical speed, in feet per minute.
         * @param alertController A controller for alerts tracked by this module's parent system.
         */
        updateAlerts(agl, altitude, verticalSpeed, alertController) {
            if (agl > 700) {
                this.reset(alertController);
                return;
            }
            let altitudeLoss = 0;
            if (this.altitudeLossReference === undefined || altitude > this.altitudeLossReference) {
                this.altitudeLossReference = altitude;
            }
            else {
                altitudeLoss = this.altitudeLossReference - altitude;
            }
            // Set up hysteresis offsets.
            const altitudeLossThresholdOffset = this.isAltitudeLossAlertActive ? -5 : 0;
            const sinkRateThresholdOffset = this.isSinkRateAlertActive ? -50 : 0;
            const isAltitudeLossAlertActive = GarminNegativeClimbRateModule.isAltitudeLoss(agl, altitudeLoss, altitudeLossThresholdOffset);
            const isSinkRateAlertActive = GarminNegativeClimbRateModule.isSinkRate(agl, -verticalSpeed, sinkRateThresholdOffset);
            const isAlertActive = isAltitudeLossAlertActive || isSinkRateAlertActive;
            if (isAlertActive !== (this.isAltitudeLossAlertActive || this.isSinkRateAlertActive)) {
                isAlertActive
                    ? alertController.activateAlert(exports.GarminTawsAlert.NcrCaution)
                    : alertController.deactivateAlert(exports.GarminTawsAlert.NcrCaution);
            }
            this.isAltitudeLossAlertActive = isAltitudeLossAlertActive;
            this.isSinkRateAlertActive = isSinkRateAlertActive;
        }
        /**
         * Deactivates all negative climb rate after takeoff alerts.
         * @param alertController A controller for alerts tracked by this module's parent system.
         */
        reset(alertController) {
            if (this.isReset) {
                return;
            }
            this.altitudeLossReference = undefined;
            this.isAltitudeLossAlertActive = false;
            this.isSinkRateAlertActive = false;
            alertController.deactivateAlert(exports.GarminTawsAlert.NcrCaution);
            this.isReset = true;
        }
        /** @inheritDoc */
        onDestroy() {
            // noop
        }
        /**
         * Checks whether a descent rate meets the threshold for an altitude loss alert for a given height above ground
         * level.
         * @param agl The height above ground level, in feet.
         * @param altitudeLoss The altitude loss, in feet.
         * @param thresholdOffset An offset to apply to the altitude loss threshold for alerting, in feet.
         * @returns Whether the specified descent rate meets the threshold for an altitude loss alert.
         */
        static isAltitudeLoss(agl, altitudeLoss, thresholdOffset) {
            let threshold;
            if (agl <= 50) {
                threshold = Infinity;
            }
            else if (agl <= 700) {
                threshold = msfsSdk.MathUtils.lerp(agl, 50, 700, 10, 70);
            }
            else {
                threshold = Infinity;
            }
            return altitudeLoss >= threshold + thresholdOffset;
        }
        /**
         * Checks whether a descent rate meets the threshold for a sink rate alert for a given height above ground level.
         * @param agl The height above ground level, in feet.
         * @param descentRate The descent rate, in feet per minute.
         * @param thresholdOffset An offset to apply to the descent rate threshold for alerting, in feet per minute.
         * @returns Whether the specified descent rate meets the threshold for a sink rate alert.
         */
        static isSinkRate(agl, descentRate, thresholdOffset) {
            let threshold;
            if (agl <= 50) {
                threshold = Infinity;
            }
            else if (agl <= 700) {
                threshold = msfsSdk.MathUtils.lerp(agl, 50, 700, 66.66666666666666, 500);
            }
            else {
                threshold = Infinity;
            }
            return descentRate >= threshold + thresholdOffset;
        }
    }
    GarminNegativeClimbRateModule.MAX_DISTANCE_FROM_AIRPORT = msfsSdk.UnitType.NMILE.convertTo(5, msfsSdk.UnitType.GA_RADIAN);

    /**
     * A Garmin terrain alerting system module that handles premature descent (PDA) alerts.
     */
    class GarminPrematureDescentModule {
        /**
         * Creates a new instance of GarminPrematureDescentModule.
         * @param options Options with which to configure the module.
         */
        constructor(options) {
            this.isReset = true;
            this.isAlertActive = false;
            this.inhibitFlags = (options === null || options === void 0 ? void 0 : options.inhibitFlags) ? Array.from(options.inhibitFlags) : [];
        }
        /** @inheritDoc */
        onInit() {
            // noop
        }
        /** @inheritDoc */
        onUpdate(operatingMode, statuses, inhibits, data, alertController) {
            if (operatingMode !== exports.TerrainSystemOperatingMode.Operating
                || statuses.has(exports.GarminTawsStatus.TawsFailed)
                || statuses.has(exports.GarminTawsStatus.TawsNotAvailable)
                || data.isOnGround) {
                this.reset(alertController);
                return;
            }
            // Check if one of our inhibit flags is active.
            for (let i = 0; i < this.inhibitFlags.length; i++) {
                if (inhibits.has(this.inhibitFlags[i])) {
                    this.reset(alertController);
                    return;
                }
            }
            let distanceToRunway;
            if (!data.destinationRunway
                || !data.isHeadingValid
                || (distanceToRunway = data.gpsPos.distance(data.destinationRunway.latitude, data.destinationRunway.longitude)) > GarminPrematureDescentModule.MAX_DISTANCE_FROM_RUNWAY
                || distanceToRunway <= GarminPrematureDescentModule.MIN_DISTANCE_FROM_RUNWAY) {
                this.reset(alertController);
                return;
            }
            if (data.isGpsPosValid) {
                this.isReset = false;
                const agl = data.gpsAltitude - msfsSdk.UnitType.METER.convertTo(data.destinationRunway.elevation, msfsSdk.UnitType.FOOT);
                this.updateAlerts(msfsSdk.UnitType.GA_RADIAN.convertTo(distanceToRunway, msfsSdk.UnitType.NMILE), agl, alertController);
            }
            else {
                this.reset(alertController);
            }
        }
        /**
         * Updates whether to issue a premature descent alert.
         * @param distanceToRunway The airplane's current distance from the destination runway, in nautical miles.
         * @param agl The airplane's current height above destination, in feet.
         * @param alertController A controller for alerts tracked by this module's parent system.
         */
        updateAlerts(distanceToRunway, agl, alertController) {
            // Set up hysteresis offset.
            const thresholdOffset = this.isAlertActive ? -20 : 0;
            const isAlertActive = GarminPrematureDescentModule.isAlert(distanceToRunway, agl, thresholdOffset);
            if (isAlertActive !== this.isAlertActive) {
                isAlertActive
                    ? alertController.activateAlert(exports.GarminTawsAlert.PdaCaution)
                    : alertController.deactivateAlert(exports.GarminTawsAlert.PdaCaution);
                this.isAlertActive = isAlertActive;
            }
        }
        /**
         * Deactivates all premature descent alerts.
         * @param alertController A controller for alerts tracked by this module's parent system.
         */
        reset(alertController) {
            if (this.isReset) {
                return;
            }
            this.isAlertActive = false;
            alertController.deactivateAlert(exports.GarminTawsAlert.PdaCaution);
            this.isReset = true;
        }
        /** @inheritDoc */
        onDestroy() {
            // noop
        }
        /**
         * Checks whether a height above destination meets the threshold for a premature descent alert for a given distance
         * to the destination runway.
         * @param distanceToRunway The distance to the destination runway, in nautical miles.
         * @param agl The height above destination, in feet.
         * @param thresholdOffset An offset to apply to the height above destination threshold for alerting, in feet.
         * @returns Whether the specified height above destination meets the threshold for a premature descent alert.
         */
        static isAlert(distanceToRunway, agl, thresholdOffset) {
            let threshold;
            if (distanceToRunway <= 0.5) {
                threshold = -Infinity;
            }
            else if (distanceToRunway <= 5) {
                threshold = Math.sqrt(27222.22222222222 * (distanceToRunway - 0.5));
            }
            else if (distanceToRunway <= 15) {
                threshold = 700 - Math.pow(4287500 * (15 - distanceToRunway), 1 / 3);
            }
            else {
                threshold = -Infinity;
            }
            return agl <= Math.max(threshold + thresholdOffset, 0);
        }
    }
    GarminPrematureDescentModule.MAX_DISTANCE_FROM_RUNWAY = msfsSdk.UnitType.NMILE.convertTo(15, msfsSdk.UnitType.GA_RADIAN);
    GarminPrematureDescentModule.MIN_DISTANCE_FROM_RUNWAY = msfsSdk.UnitType.NMILE.convertTo(0.5, msfsSdk.UnitType.GA_RADIAN);

    /**
     * A Garmin terrain alerting system module that handles touchdown voice callouts.
     */
    class GarminVoiceCalloutModule {
        /**
         * Creates a new instance of GarminVoiceCalloutModule.
         * @param options Options with which to configure the module.
         */
        constructor(options) {
            var _a, _b;
            this.nearestAirport = undefined;
            this.nearestAirportRunways = undefined;
            this.nearestRunwayAltitude = undefined;
            this.lastNearestRunwayRefreshTime = undefined;
            this.isReset = true;
            this.inhibit500WhenGsGpActive = (_a = options === null || options === void 0 ? void 0 : options.inhibit500WhenGsGpActive) !== null && _a !== void 0 ? _a : false;
            this.nearestRunwayRefreshInterval = (_b = options === null || options === void 0 ? void 0 : options.nearestRunwayUpdateInterval) !== null && _b !== void 0 ? _b : 3000;
            this.entries = Array.from([500, 450, 400, 350, 300, 250, 200, 150, 100, 50, 40, 30, 20, 10], altitude => {
                return {
                    activateAltitude: altitude,
                    armAltitude: Math.max(altitude * 1.1, altitude + 10),
                    isArmed: false,
                    isActivated: false,
                    alert: exports.GarminTawsAlert[`Vco${altitude}`]
                };
            });
        }
        /** @inheritDoc */
        onInit() {
            // noop
        }
        /** @inheritDoc */
        onUpdate(operatingMode, statuses, inhibits, data, alertController) {
            if (operatingMode !== exports.TerrainSystemOperatingMode.Operating || data.isOnGround) {
                this.reset(alertController);
                return;
            }
            this.isReset = false;
            if (data.isGpsPosValid) {
                if (data.nearestAirport) {
                    this.updateNearestRunway(data.nearestAirport, data);
                }
                else {
                    this.nearestAirport = undefined;
                    this.nearestAirportRunways = undefined;
                    this.nearestRunwayAltitude = undefined;
                }
            }
            if (data.isGpsPosValid && this.nearestRunwayAltitude !== undefined) {
                this.updateCallouts(data.gpsAltitude - this.nearestRunwayAltitude, data.isGsGpActive, alertController);
            }
            else if (data.isRadarAltitudeValid) {
                this.updateCallouts(data.radarAltitude, data.isGsGpActive, alertController);
            }
        }
        /**
         * Updates the nearest runway to the airplane.
         * @param nearestAirport The nearest airport to the airplane.
         * @param data The current terrain system data.
         */
        updateNearestRunway(nearestAirport, data) {
            var _a;
            if (nearestAirport.icao !== ((_a = this.nearestAirport) === null || _a === void 0 ? void 0 : _a.icao)) {
                this.nearestAirport = nearestAirport;
                this.nearestAirportRunways = msfsSdk.RunwayUtils.getOneWayRunwaysFromAirport(nearestAirport);
                this.lastNearestRunwayRefreshTime = undefined;
            }
            if (this.lastNearestRunwayRefreshTime === undefined || data.realTime - this.lastNearestRunwayRefreshTime >= this.nearestRunwayRefreshInterval) {
                this.nearestRunwayAltitude = undefined;
                this.lastNearestRunwayRefreshTime = data.realTime;
                let nearestDistance = Infinity;
                let nearestRunway = undefined;
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                const runways = this.nearestAirportRunways;
                for (let i = 0; i < runways.length; i++) {
                    const runway = runways[i];
                    const distance = data.gpsPos.distance(runway.latitude, runway.longitude);
                    if (distance < nearestDistance) {
                        nearestDistance = distance;
                        nearestRunway = runway;
                    }
                }
                if (nearestRunway) {
                    this.nearestRunwayAltitude = msfsSdk.UnitType.METER.convertTo(nearestRunway.elevation, msfsSdk.UnitType.FOOT);
                }
            }
            else if (data.realTime < this.lastNearestRunwayRefreshTime) {
                this.lastNearestRunwayRefreshTime = data.realTime;
            }
        }
        /**
         * Updates the state of all callout alerts.
         * @param altitudeAbove The current altitude, in feet, of the airplane above the reference (either the nearest runway
         * threshold or the ground).
         * @param isGsGpActive Whether the autopilot's GS or GP mode is active.
         * @param alertController A controller for alerts tracked by this module's parent system.
         */
        updateCallouts(altitudeAbove, isGsGpActive, alertController) {
            for (let i = 0; i < this.entries.length; i++) {
                const entry = this.entries[i];
                if (entry.isArmed) {
                    if (altitudeAbove <= entry.activateAltitude) {
                        entry.isArmed = false;
                        if (entry.activateAltitude !== 500 || !this.inhibit500WhenGsGpActive || !isGsGpActive) {
                            entry.isActivated = true;
                            alertController.activateAlert(entry.alert);
                        }
                    }
                }
                else if (altitudeAbove >= entry.armAltitude) {
                    entry.isArmed = true;
                    if (entry.isActivated) {
                        entry.isActivated = false;
                        alertController.deactivateAlert(entry.alert);
                    }
                }
            }
        }
        /**
         * Disarms and deactivates all touchdown callout alerts.
         * @param alertController A controller for alerts tracked by this module's parent system.
         */
        reset(alertController) {
            if (this.isReset) {
                return;
            }
            for (let i = 0; i < this.entries.length; i++) {
                const entry = this.entries[i];
                entry.isArmed = false;
                if (entry.isActivated) {
                    entry.isActivated = false;
                    alertController.deactivateAlert(entry.alert);
                }
            }
            this.isReset = true;
        }
        /** @inheritDoc */
        onDestroy() {
            // noop
        }
    }

    /**
     * A utility class for working with Garmin terrain alerting systems.
     */
    class TerrainSystemUtils {
        /**
         * Gets the event bus topic suffix for a terrain system ID.
         * @param id The ID for which to get the suffix.
         * @returns The event bus topic suffix for the specified terrain system ID.
         */
        static getIdSuffix(id) {
            return (id === '' ? '' : `_${id}`);
        }
        // eslint-disable-next-line jsdoc/require-jsdoc
        static onEvent(id, arg2, baseTopic) {
            return (arg2 instanceof msfsSdk.EventBus ? arg2.getSubscriber() : arg2).on(`${baseTopic}${TerrainSystemUtils.getIdSuffix(id)}`);
        }
    }

    /**
     * An abstract implementation of `TerrainSystem`. This class handles adding, initializing, updating, and destroying
     * modules. It also handles publishing topics to the event bus in responses to changes in state. Finally, it handles
     * listening to and responding to control events published to the event bus.
     */
    class AbstractTerrainSystem {
        /**
         * Creates a new instance of AbstractTerrainSystem.
         * @param id This terrain system's ID.
         * @param type This terrain system's type.
         * @param bus The event bus.
         * @param dataProvider A provider of terrain system data.
         * @param prioritizedAlertSelector A function that this system uses to select a prioritized alert from an iterable of
         * active alerts each time the set of active alerts changes.
         */
        constructor(id, type, bus, dataProvider, prioritizedAlertSelector) {
            this.id = id;
            this.type = type;
            this.bus = bus;
            this.dataProvider = dataProvider;
            this.prioritizedAlertSelector = prioritizedAlertSelector;
            this.publisher = this.bus.getPublisher();
            this.operatingMode = msfsSdk.Subject.create(exports.TerrainSystemOperatingMode.Off);
            this.statuses = msfsSdk.SetSubject.create();
            this.inhibits = msfsSdk.SetSubject.create();
            this.alerts = msfsSdk.SetSubject.create();
            this.prioritizedAlert = msfsSdk.Subject.create(null);
            this.modules = [];
            this.alertController = {
                activateAlert: this.activateAlert.bind(this),
                deactivateAlert: this.deactivateAlert.bind(this),
            };
            this.isAlive = true;
            this.isInit = false;
            this.subscriptions = [];
            this.idSuffix = TerrainSystemUtils.getIdSuffix(id);
            this.topicMap = {
                'terrainsys_type': `terrainsys_type${this.idSuffix}`,
                'terrainsys_operating_mode': `terrainsys_operating_mode${this.idSuffix}`,
                'terrainsys_status_flags': `terrainsys_status_flags${this.idSuffix}`,
                'terrainsys_status_added': `terrainsys_status_added${this.idSuffix}`,
                'terrainsys_status_removed': `terrainsys_status_removed${this.idSuffix}`,
                'terrainsys_inhibit_flags': `terrainsys_inhibit_flags${this.idSuffix}`,
                'terrainsys_inhibit_added': `terrainsys_inhibit_added${this.idSuffix}`,
                'terrainsys_inhibit_removed': `terrainsys_inhibit_removed${this.idSuffix}`,
                'terrainsys_active_alerts': `terrainsys_active_alerts${this.idSuffix}`,
                'terrainsys_alert_activated': `terrainsys_alert_activated${this.idSuffix}`,
                'terrainsys_alert_deactivated': `terrainsys_alert_deactivated${this.idSuffix}`,
                'terrainsys_prioritized_alert': `terrainsys_prioritized_alert${this.idSuffix}`,
            };
            this.publisher.pub(this.topicMap['terrainsys_type'], this.type, true, true);
            this.operatingMode.sub(this.onOperatingModeChanged.bind(this), true);
            this.alerts.sub(this.onAlertsChanged.bind(this));
        }
        /** @inheritDoc */
        addModule(module) {
            if (this.modules.includes(module)) {
                return;
            }
            this.modules.push(module);
            if (this.isInit) {
                module.onInit();
            }
        }
        /** @inheritDoc */
        init() {
            if (!this.isAlive) {
                throw new Error('AbstractTerrainSystem: cannot initialize a dead system');
            }
            if (this.isInit) {
                return;
            }
            this.isInit = true;
            this.onInit();
        }
        /**
         * A method that is called when this system is initialized.
         */
        onInit() {
            this.initOperatingModePublishing();
            this.initStatusPublishing();
            this.initInhibitPublishing();
            this.initAlertPublishing();
            this.initControlEventListeners();
            this.initModules();
        }
        /**
         * Initializes publishing of this system's operating mode to the event bus.
         */
        initOperatingModePublishing() {
            this.operatingMode.sub(mode => { this.publisher.pub(this.topicMap['terrainsys_operating_mode'], mode, true, true); }, true);
        }
        /**
         * Initializes publishing of this system's status flags to the event bus.
         */
        initStatusPublishing() {
            this.statuses.sub((set, type, status) => {
                this.publisher.pub(this.topicMap['terrainsys_status_flags'], Array.from(set), true, true);
                if (type === msfsSdk.SubscribableSetEventType.Added) {
                    this.publisher.pub(this.topicMap['terrainsys_status_added'], status, true, false);
                }
                else {
                    this.publisher.pub(this.topicMap['terrainsys_status_removed'], status, true, false);
                }
            }, true);
        }
        /**
         * Initializes publishing of this system's inhibit flags to the event bus.
         */
        initInhibitPublishing() {
            this.inhibits.sub((set, type, inhibit) => {
                this.publisher.pub(this.topicMap['terrainsys_inhibit_flags'], Array.from(set), true, true);
                if (type === msfsSdk.SubscribableSetEventType.Added) {
                    this.publisher.pub(this.topicMap['terrainsys_inhibit_added'], inhibit, true, false);
                }
                else {
                    this.publisher.pub(this.topicMap['terrainsys_inhibit_removed'], inhibit, true, false);
                }
            }, true);
        }
        /**
         * Initializes publishing of this system's active alerts to the event bus.
         */
        initAlertPublishing() {
            this.publisher.pub(this.topicMap['terrainsys_active_alerts'], Array.from(this.alerts.get()), true, true);
            for (const alert of this.alerts.get()) {
                this.publisher.pub(this.topicMap['terrainsys_alert_activated'], alert, true, false);
            }
            this.prioritizedAlert.sub(alert => {
                this.publisher.pub(this.topicMap['terrainsys_prioritized_alert'], alert, true, true);
            }, true);
        }
        /**
         * Initializes listeners for control events published to the event bus.
         */
        initControlEventListeners() {
            const sub = this.bus.getSubscriber();
            this.subscriptions.push(sub.on(`terrainsys_turn_on${this.idSuffix}`).handle(this.onStartTest.bind(this)), sub.on(`terrainsys_turn_off${this.idSuffix}`).handle(this.onStartTest.bind(this)), sub.on(`terrainsys_start_test${this.idSuffix}`).handle(this.onStartTest.bind(this)), sub.on(`terrainsys_add_inhibit${this.idSuffix}`).handle(this.onAddInhibit.bind(this)), sub.on(`terrainsys_remove_inhibit${this.idSuffix}`).handle(this.onRemoveInhibit.bind(this)), sub.on(`terrainsys_remove_all_inhibits${this.idSuffix}`).handle(this.onRemoveAllInhibits.bind(this)));
        }
        /**
         * Initializes this system's modules.
         */
        initModules() {
            for (const module of this.modules) {
                module.onInit();
            }
        }
        /** @inheritDoc */
        turnOn() {
            if (!this.isAlive) {
                throw new Error('AbstractTerrainSystem: cannot manipulate a dead system');
            }
            if (!this.isInit) {
                return;
            }
            this.onTurnOn();
        }
        /** @inheritDoc */
        turnOff() {
            if (!this.isAlive) {
                throw new Error('AbstractTerrainSystem: cannot manipulate a dead system');
            }
            if (!this.isInit) {
                return;
            }
            this.onTurnOff();
        }
        /** @inheritDoc */
        startTest() {
            if (!this.isAlive) {
                throw new Error('AbstractTerrainSystem: cannot manipulate a dead system');
            }
            if (!this.isInit) {
                return;
            }
            this.onStartTest();
        }
        /** @inheritDoc */
        addInhibit(inhibit) {
            if (!this.isAlive) {
                throw new Error('AbstractTerrainSystem: cannot manipulate a dead system');
            }
            if (!this.isInit) {
                return;
            }
            this.onAddInhibit(inhibit);
        }
        /** @inheritDoc */
        removeInhibit(inhibit) {
            if (!this.isAlive) {
                throw new Error('AbstractTerrainSystem: cannot manipulate a dead system');
            }
            if (!this.isInit) {
                return;
            }
            this.onRemoveInhibit(inhibit);
        }
        /** @inheritDoc */
        removeAllInhibits() {
            if (!this.isAlive) {
                throw new Error('AbstractTerrainSystem: cannot manipulate a dead system');
            }
            if (!this.isInit) {
                return;
            }
            this.onRemoveAllInhibits();
        }
        /**
         * Responds to when this system's operating mode changes.
         * @param mode The new operating mode.
         */
        onOperatingModeChanged(mode) {
            if (mode !== exports.TerrainSystemOperatingMode.Operating) {
                if (mode === exports.TerrainSystemOperatingMode.Off) {
                    this.statuses.clear();
                }
                this.alerts.clear();
            }
        }
        /**
         * Responds to when the set of this system's active alerts changes.
         * @param alerts The set of active alerts.
         * @param type The type of change that occurred.
         * @param alert The alert that was changed.
         */
        onAlertsChanged(alerts, type, alert) {
            if (this.isInit) {
                this.publishAlert(alerts, type, alert);
            }
            this.prioritizedAlert.set(this.prioritizedAlertSelector(alerts));
        }
        /**
         * Publishes data to event bus alert topics based on a change to this system's active alerts.
         * @param alerts The set of active alerts.
         * @param type The type of change that occurred.
         * @param alert The alert that was changed.
         */
        publishAlert(alerts, type, alert) {
            this.publisher.pub(this.topicMap['terrainsys_active_alerts'], Array.from(alerts), true, true);
            if (type === msfsSdk.SubscribableSetEventType.Added) {
                this.publisher.pub(this.topicMap['terrainsys_alert_activated'], alert, true, false);
            }
            else {
                this.publisher.pub(this.topicMap['terrainsys_alert_deactivated'], alert, true, false);
            }
        }
        /**
         * A method that is called when this system receives a command to turn on.
         */
        onTurnOn() {
            // noop
        }
        /**
         * A method that is called when this system receives a command to turn off.
         */
        onTurnOff() {
            // noop
        }
        /**
         * A method that is called when this system receives a command to start a self-test.
         */
        onStartTest() {
            // noop
        }
        /**
         * A method that is called when this system receives a command to add an inhibit flag.
         * @param inhibit The flag to add.
         */
        onAddInhibit(inhibit) {
            this.inhibits.add(inhibit);
        }
        /**
         * A method that is called when this system receives a command to remove an inhibit flag.
         * @param inhibit The flag to remove.
         */
        onRemoveInhibit(inhibit) {
            this.inhibits.delete(inhibit);
        }
        /**
         * A method that is called when this system receives a command to remove all inhibit flags.
         */
        onRemoveAllInhibits() {
            this.inhibits.clear();
        }
        /**
         * Activates an alert.
         * @param alert The alert to activate.
         */
        activateAlert(alert) {
            if (this.operatingMode.get() === exports.TerrainSystemOperatingMode.Operating) {
                this.alerts.add(alert);
            }
        }
        /**
         * Deactivates an alert.
         * @param alert The alert to deactivate.
         */
        deactivateAlert(alert) {
            this.alerts.delete(alert);
        }
        /** @inheritDoc */
        update() {
            if (!this.isAlive) {
                throw new Error('AbstractTerrainSystem: cannot update a dead system');
            }
            if (!this.isInit) {
                return;
            }
            this.onUpdate();
        }
        /**
         * A method that is called when this system is updated.
         */
        onUpdate() {
            this.updateModules();
        }
        /**
         * Updates this system's modules.
         */
        updateModules() {
            for (let i = 0; i < this.modules.length; i++) {
                this.modules[i].onUpdate(this.operatingMode.get(), this.statuses.get(), this.inhibits.get(), this.dataProvider.data, this.alertController);
            }
        }
        /** @inheritDoc */
        destroy() {
            this.isAlive = false;
            for (const module of this.modules) {
                module.onDestroy();
            }
            for (const sub of this.subscriptions) {
                sub.destroy();
            }
        }
    }

    /**
     * A default provider of Garmin terrain alerting system data which sources data from FMS geo-positioning, radar
     * altimeter, ADC, and AHRS systems.
     */
    class DefaultTerrainSystemDataProvider {
        /**
         * Creates a new instance of DefaultTerrainSystemDataProvider.
         * @param bus The event bus.
         * @param fms The FMS instance.
         * @param options Options with which to configure the data provider.
         */
        constructor(bus, fms, options) {
            var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3;
            this.bus = bus;
            this.fms = fms;
            this.simTime = msfsSdk.ConsumerValue.create(null, 0);
            this.simRate = msfsSdk.ConsumerValue.create(null, 1);
            this.isOnGround = msfsSdk.ConsumerValue.create(null, false);
            this.gearPosition = [
                msfsSdk.ConsumerValue.create(null, 0),
                msfsSdk.ConsumerValue.create(null, 0),
                msfsSdk.ConsumerValue.create(null, 0)
            ];
            this.flapsAngle = [
                msfsSdk.ConsumerValue.create(null, 0),
                msfsSdk.ConsumerValue.create(null, 0)
            ];
            this.isFmsPosIndexValid = false;
            this.fmsPosMode = msfsSdk.ConsumerValue.create(null, exports.FmsPositionMode.None);
            this.gpsPosSource = msfsSdk.ConsumerValue.create(null, new LatLongAlt(0, 0));
            this.gpsVerticalSpeedSource = msfsSdk.ConsumerValue.create(null, 0);
            this.gpsGroundSpeed = msfsSdk.ConsumerValue.create(null, 0);
            this.groundElevationSource = msfsSdk.ConsumerValue.create(null, 0);
            this.gpsAglSource = msfsSdk.ConsumerValue.create(null, 0);
            this.gpsPos = new msfsSdk.GeoPoint(NaN, NaN);
            this.isRadarAltIndexValid = false;
            this.radarAltimeterState = msfsSdk.ConsumerSubject.create(null, undefined);
            this.radarAltitudeSource = msfsSdk.ConsumerValue.create(null, 0);
            this.isAdcIndexValid = false;
            this.isAltitudeDataValid = msfsSdk.ConsumerValue.create(null, false);
            this.baroAltitude = msfsSdk.ConsumerValue.create(null, 0);
            this.baroVerticalSpeed = msfsSdk.ConsumerValue.create(null, 0);
            this.isAhrsIndexValid = false;
            this.isAttitudeDataValid = msfsSdk.ConsumerValue.create(null, false);
            this.isHeadingDataValid = msfsSdk.ConsumerValue.create(null, false);
            this.headingTrue = msfsSdk.ConsumerValue.create(null, 0);
            this.fmaData = msfsSdk.ConsumerValue.create(null, undefined);
            this.lastNearestSubscriptionUpdateTime = undefined;
            this.departureAirportIcao = null;
            this.departureAirport = null;
            this.destinationAirportIcao = null;
            this.destinationAirport = null;
            this._data = {
                realTime: 0,
                simTime: 0,
                simRate: 0,
                isOnGround: false,
                gearPosition: [0, 0, 0],
                flapsAngle: [0, 0],
                isGpsPosValid: false,
                gpsPos: this.gpsPos.readonly,
                gpsAltitude: NaN,
                gpsVerticalSpeed: NaN,
                gpsGroundSpeed: NaN,
                groundElevation: NaN,
                gpsAgl: NaN,
                isRadarAltitudeValid: false,
                radarAltitude: NaN,
                isBaroAltitudeValid: false,
                baroAltitude: NaN,
                baroVerticalSpeed: NaN,
                baroAgl: NaN,
                isAttitudeValid: false,
                isHeadingValid: false,
                headingTrue: NaN,
                isGsGpActive: false,
                departureAirport: null,
                departureRunway: null,
                destinationAirport: null,
                destinationRunway: null,
                nearestAirport: null
            };
            /** @inheritDoc */
            this.data = this._data;
            this.lastUpdateRealTime = undefined;
            this.isAlive = true;
            this.isInit = false;
            this.subscriptions = [
                this.simTime,
                this.simRate,
                this.isOnGround,
                ...this.gearPosition,
                ...this.flapsAngle,
                this.fmsPosMode,
                this.gpsPosSource,
                this.gpsVerticalSpeedSource,
                this.gpsGroundSpeed,
                this.groundElevationSource,
                this.gpsAglSource,
                this.radarAltimeterState,
                this.radarAltitudeSource,
                this.isAltitudeDataValid,
                this.baroAltitude,
                this.baroVerticalSpeed,
                this.fmaData
            ];
            this.fmsPosIndex = msfsSdk.SubscribableUtils.toSubscribable(options.fmsPosIndex, true);
            this.radarAltIndex = msfsSdk.SubscribableUtils.toSubscribable(options.radarAltIndex, true);
            this.adcIndex = msfsSdk.SubscribableUtils.toSubscribable(options.adcIndex, true);
            this.ahrsIndex = msfsSdk.SubscribableUtils.toSubscribable(options.ahrsIndex, true);
            this.gpsVerticalSpeedSmoother = new msfsSdk.MultiExpSmoother((_b = (_a = options.gpsVerticalSpeedSmootherParams) === null || _a === void 0 ? void 0 : _a.tau) !== null && _b !== void 0 ? _b : 1000 / Math.LN2, (_c = options.gpsVerticalSpeedSmootherParams) === null || _c === void 0 ? void 0 : _c.tauVelocity, (_d = options.gpsVerticalSpeedSmootherParams) === null || _d === void 0 ? void 0 : _d.tauAccel, null, null, null, (_f = (_e = options.gpsVerticalSpeedSmootherParams) === null || _e === void 0 ? void 0 : _e.dtThreshold) !== null && _f !== void 0 ? _f : 10000);
            this.gpsAglSmoother = new msfsSdk.MultiExpSmoother((_h = (_g = options.gpsAglSmootherParams) === null || _g === void 0 ? void 0 : _g.tau) !== null && _h !== void 0 ? _h : 2000 / Math.LN2, (_k = (_j = options.gpsAglSmootherParams) === null || _j === void 0 ? void 0 : _j.tauVelocity) !== null && _k !== void 0 ? _k : 1000 / Math.LN2, (_l = options.gpsAglSmootherParams) === null || _l === void 0 ? void 0 : _l.tauAccel, null, null, null, (_o = (_m = options.gpsAglSmootherParams) === null || _m === void 0 ? void 0 : _m.dtThreshold) !== null && _o !== void 0 ? _o : 10000);
            this.radarAltitudeSmoother = new msfsSdk.MultiExpSmoother((_q = (_p = options.radarAltitudeSmootherParams) === null || _p === void 0 ? void 0 : _p.tau) !== null && _q !== void 0 ? _q : 2000 / Math.LN2, (_s = (_r = options.radarAltitudeSmootherParams) === null || _r === void 0 ? void 0 : _r.tauVelocity) !== null && _s !== void 0 ? _s : 1000 / Math.LN2, (_t = options.radarAltitudeSmootherParams) === null || _t === void 0 ? void 0 : _t.tauAccel, null, null, null, (_v = (_u = options.radarAltitudeSmootherParams) === null || _u === void 0 ? void 0 : _u.dtThreshold) !== null && _v !== void 0 ? _v : 10000);
            this.baroAglSmoother = new msfsSdk.MultiExpSmoother((_x = (_w = options.radarAltitudeSmootherParams) === null || _w === void 0 ? void 0 : _w.tau) !== null && _x !== void 0 ? _x : 2000 / Math.LN2, (_z = (_y = options.radarAltitudeSmootherParams) === null || _y === void 0 ? void 0 : _y.tauVelocity) !== null && _z !== void 0 ? _z : 1000 / Math.LN2, (_0 = options.radarAltitudeSmootherParams) === null || _0 === void 0 ? void 0 : _0.tauAccel, null, null, null, (_2 = (_1 = options.radarAltitudeSmootherParams) === null || _1 === void 0 ? void 0 : _1.dtThreshold) !== null && _2 !== void 0 ? _2 : 10000);
            this.nearestSubscriptionUpdateInterval = (_3 = options === null || options === void 0 ? void 0 : options.nearestAirportUpdateInterval) !== null && _3 !== void 0 ? _3 : 3000;
            this.nearestSubscription = new msfsSdk.NearestAirportSubscription(this.fms.facLoader);
            this.nearestSubscription.setExtendedFilters(DefaultTerrainSystemDataProvider.RUNWAY_NO_WATER_MASK, ~0, ~0, 0);
            this.nearestSubscription.start();
        }
        /**
         * Initializes this system. Once this system is initialized, it will begin collecting data and updating its modules.
         * @throws Error if this data provider has been destroyed.
         */
        init() {
            if (!this.isAlive) {
                throw new Error('DefaultTerrainSystemDataProvider: cannot initialize a dead data provider');
            }
            if (this.isInit) {
                return;
            }
            this.isInit = true;
            const sub = this.bus.getSubscriber();
            this.simTime.setConsumer(sub.on('simTime'));
            this.simRate.setConsumer(sub.on('simRate'));
            this.isOnGround.setConsumer(sub.on('on_ground'));
            this.gearPosition[0].setConsumer(sub.on('gear_position_0'));
            this.gearPosition[1].setConsumer(sub.on('gear_position_1'));
            this.gearPosition[2].setConsumer(sub.on('gear_position_2'));
            this.flapsAngle[0].setConsumer(sub.on('flaps_left_angle'));
            this.flapsAngle[1].setConsumer(sub.on('flaps_right_angle'));
            this.gpsVerticalSpeedSource.setConsumer(sub.on('inertial_vertical_speed'));
            this.groundElevationSource.setConsumer(sub.on('ground_altitude'));
            this.gpsAglSource.setConsumer(sub.on('above_ground_height'));
            this.fmaData.setConsumer(sub.on('fma_data'));
            this.subscriptions.push(this.fmsPosIndex.sub(index => {
                if (index <= 0) {
                    this.isFmsPosIndexValid = false;
                    this.fmsPosMode.setConsumer(null);
                    this.gpsPosSource.setConsumer(null);
                    this.gpsGroundSpeed.setConsumer(null);
                }
                else {
                    this.isFmsPosIndexValid = true;
                    this.fmsPosMode.setConsumer(sub.on(`fms_pos_mode_${index}`));
                    this.gpsPosSource.setConsumer(sub.on(`fms_pos_gps-position_${index}`));
                    this.gpsGroundSpeed.setConsumer(sub.on(`fms_pos_ground_speed_${index}`));
                }
            }, true), this.radarAltIndex.sub(index => {
                if (index <= 0) {
                    this.isRadarAltIndexValid = false;
                    this.radarAltimeterState.setConsumer(null);
                    this.radarAltitudeSource.setConsumer(null);
                }
                else {
                    this.isRadarAltIndexValid = true;
                    this.radarAltimeterState.setConsumer(sub.on(`radaralt_state_${index}`));
                    this.radarAltitudeSource.setConsumer(sub.on(`radaralt_radio_alt_${index}`));
                }
            }, true), this.adcIndex.sub(index => {
                if (index <= 0) {
                    this.isAdcIndexValid = false;
                    this.isAltitudeDataValid.setConsumer(null);
                    this.baroAltitude.setConsumer(null);
                    this.baroVerticalSpeed.setConsumer(null);
                }
                else {
                    this.isAdcIndexValid = true;
                    this.isAltitudeDataValid.setConsumer(sub.on(`adc_altitude_data_valid_${index}`));
                    this.baroAltitude.setConsumer(sub.on(`adc_indicated_alt_${index}`));
                    this.baroVerticalSpeed.setConsumer(sub.on(`adc_vertical_speed_${index}`));
                }
            }, true), this.ahrsIndex.sub(index => {
                if (index <= 0) {
                    this.isAhrsIndexValid = false;
                    this.isAttitudeDataValid.setConsumer(null);
                    this.isHeadingDataValid.setConsumer(null);
                    this.headingTrue.setConsumer(null);
                }
                else {
                    this.isAhrsIndexValid = true;
                    this.isAttitudeDataValid.setConsumer(sub.on(`ahrs_attitude_data_valid_${index}`));
                    this.isHeadingDataValid.setConsumer(sub.on(`ahrs_heading_data_valid_${index}`));
                    this.headingTrue.setConsumer(sub.on(`ahrs_hdg_deg_true_${index}`));
                }
            }, true));
            this.radarAltimeterState.sub(state => {
                this._data.isRadarAltitudeValid = state !== undefined && (state.current === undefined || state.current === msfsSdk.AvionicsSystemState.On);
            }, true);
        }
        /**
         * Updates this data provider.
         * @param realTime The current real (operating system) time, as a Javascript timestamp.
         * @throws Error if this data provider has been destroyed.
         */
        update(realTime) {
            if (!this.isAlive) {
                throw new Error('DefaultTerrainSystemDataProvider: cannot update a dead data provider');
            }
            if (!this.isInit) {
                return;
            }
            const simRate = this.simRate.get();
            const dt = this.lastUpdateRealTime === undefined
                ? 0
                : msfsSdk.MathUtils.clamp(realTime - this.lastUpdateRealTime, 0, 1000) * simRate;
            this._data.realTime = realTime;
            this._data.simTime = this.simTime.get();
            this._data.simRate = simRate;
            this._data.isOnGround = this.isOnGround.get();
            this.updateControlSurfaces();
            this.updateGps(realTime, dt);
            this.updateRadarAltitude(dt);
            this.updateBaroAltitude(dt);
            this.updateAttitudeHeading();
            this.updateGlideslope();
            this.updateFlightPlan();
            this.lastUpdateRealTime = realTime;
        }
        /**
         * Updates this provider's airplane control surfaces data.
         */
        updateControlSurfaces() {
            this._data.gearPosition[0] = this.gearPosition[0].get();
            this._data.gearPosition[1] = this.gearPosition[1].get();
            this._data.gearPosition[2] = this.gearPosition[2].get();
            this._data.flapsAngle[0] = this.flapsAngle[0].get();
            this._data.flapsAngle[1] = this.flapsAngle[1].get();
        }
        /**
         * Updates this provider's GPS data.
         * @param realTime The current real (operating system) time, as a Javascript timestamp.
         * @param dt The elapsed time, in milliseconds, since the last update.
         */
        updateGps(realTime, dt) {
            if (this.isFmsPosIndexValid) {
                const fmsPosMode = this.fmsPosMode.get();
                this._data.isGpsPosValid = fmsPosMode !== exports.FmsPositionMode.None
                    && fmsPosMode !== exports.FmsPositionMode.DeadReckoning
                    && fmsPosMode !== exports.FmsPositionMode.DeadReckoningExpired;
            }
            else {
                this._data.isGpsPosValid = false;
            }
            if (this._data.isGpsPosValid) {
                const lla = this.gpsPosSource.get();
                this.gpsPos.set(lla.lat, lla.long);
                this._data.gpsAltitude = msfsSdk.UnitType.METER.convertTo(lla.alt, msfsSdk.UnitType.FOOT);
                this._data.gpsVerticalSpeed = this.gpsVerticalSpeedSmoother.next(this.gpsVerticalSpeedSource.get(), dt);
                this._data.gpsGroundSpeed = this.gpsGroundSpeed.get();
                this._data.groundElevation = this.groundElevationSource.get();
                this._data.gpsAgl = this.gpsAglSmoother.next(this._data.gpsAltitude - this._data.groundElevation, dt);
                this.updateNearestAirportSubscription(realTime, this._data.gpsPos);
                const nearestAirport = this.nearestSubscription.tryGet(0);
                // Sometimes the nearest search retains airports that are outside the search radius, so we need to check the
                // distance to the airport ourselves.
                if (nearestAirport && this._data.gpsPos.distance(nearestAirport) <= DefaultTerrainSystemDataProvider.NEAREST_AIRPORT_RADIUS_GAR) {
                    this._data.nearestAirport = nearestAirport;
                }
                else {
                    this._data.nearestAirport = null;
                }
            }
            else {
                this.gpsPos.set(NaN, NaN);
                this._data.gpsAltitude = NaN;
                this._data.gpsVerticalSpeed = NaN;
                this._data.groundElevation = NaN;
                this._data.gpsAgl = NaN;
                this._data.nearestAirport = null;
                this.gpsVerticalSpeedSmoother.reset();
                this.gpsAglSmoother.reset();
            }
        }
        /**
         * Updates this module's nearest airport subscription, if necessary.
         * @param realTime The current real (operating system) time, as a UNIX timestamp in milliseconds.
         * @param position The current position of the airplane.
         */
        updateNearestAirportSubscription(realTime, position) {
            if (this.lastNearestSubscriptionUpdateTime === undefined
                || realTime - this.lastNearestSubscriptionUpdateTime >= this.nearestSubscriptionUpdateInterval) {
                this.nearestSubscription.update(position.lat, position.lon, DefaultTerrainSystemDataProvider.NEAREST_AIRPORT_RADIUS_METERS, 1);
                this.lastNearestSubscriptionUpdateTime = realTime;
            }
            else if (realTime < this.lastNearestSubscriptionUpdateTime) {
                this.lastNearestSubscriptionUpdateTime = realTime;
            }
        }
        /**
         * Updates this provider's radar altimeter data.
         * @param dt dt The elapsed time, in milliseconds, since the last update.
         */
        updateRadarAltitude(dt) {
            if (this.isRadarAltIndexValid) {
                const radarAltState = this.radarAltimeterState.get();
                this._data.isRadarAltitudeValid = radarAltState !== undefined && (radarAltState.current === undefined || radarAltState.current === msfsSdk.AvionicsSystemState.On);
            }
            else {
                this._data.isRadarAltitudeValid = false;
            }
            if (this._data.isRadarAltitudeValid) {
                this._data.radarAltitude = this.radarAltitudeSmoother.next(this.radarAltitudeSource.get(), dt);
            }
            else {
                this._data.radarAltitude = NaN;
                this.radarAltitudeSmoother.reset();
            }
        }
        /**
         * Updates this provider's barometric altitude data.
         * @param dt dt The elapsed time, in milliseconds, since the last update.
         */
        updateBaroAltitude(dt) {
            if (this.isAdcIndexValid) {
                this._data.isBaroAltitudeValid = this.isAltitudeDataValid.get();
            }
            else {
                this._data.isRadarAltitudeValid = false;
            }
            if (this._data.isBaroAltitudeValid) {
                this._data.baroVerticalSpeed = this.baroVerticalSpeed.get();
                this._data.baroAltitude = this.baroAltitude.get();
            }
            else {
                this._data.baroAltitude = NaN;
                this._data.baroVerticalSpeed = NaN;
            }
            if (!isNaN(this._data.baroAltitude) && !isNaN(this._data.groundElevation)) {
                this._data.baroAgl = this.baroAglSmoother.next(this._data.baroAltitude - this._data.groundElevation, dt);
            }
            else {
                this._data.baroAgl = NaN;
                this.baroAglSmoother.reset();
            }
        }
        /**
         * Updates this provider's attitude and heading data.
         */
        updateAttitudeHeading() {
            if (this.isAhrsIndexValid) {
                this._data.isAttitudeValid = this.isAttitudeDataValid.get();
                this._data.isHeadingValid = this.isHeadingDataValid.get();
            }
            else {
                this._data.isAttitudeValid = false;
                this._data.isHeadingValid = false;
            }
            if (this._data.isHeadingValid) {
                this._data.headingTrue = this.headingTrue.get();
            }
            else {
                this._data.headingTrue = NaN;
            }
        }
        /**
         * Updates this provider's autopilot glideslope/glidepath mode data.
         */
        updateGlideslope() {
            const fmaData = this.fmaData.get();
            this._data.isGsGpActive = fmaData !== undefined && (fmaData.verticalActive === msfsSdk.APVerticalModes.GS || fmaData.verticalActive === msfsSdk.APVerticalModes.GP);
        }
        /**
         * Updates this provider's flight plan data.
         */
        updateFlightPlan() {
            var _a, _b, _c, _d;
            if (!this.fms.hasPrimaryFlightPlan()) {
                this._data.departureAirport = null;
                this._data.departureRunway = null;
                this._data.destinationAirport = null;
                this._data.destinationRunway = null;
                return;
            }
            const flightPlan = this.fms.getPrimaryFlightPlan();
            const departureAirportIcao = (_a = flightPlan.originAirport) !== null && _a !== void 0 ? _a : null;
            if (departureAirportIcao !== this.departureAirportIcao) {
                this._data.departureAirport = null;
                this._data.departureRunway = null;
                this.departureAirportIcao = departureAirportIcao;
                departureAirportIcao !== null && this.retrieveDepartureAirport(departureAirportIcao);
            }
            else {
                this._data.departureAirport = this.departureAirport;
                if (this.departureAirport !== null) {
                    this._data.departureRunway = (_b = flightPlan.procedureDetails.originRunway) !== null && _b !== void 0 ? _b : null;
                }
                else {
                    this._data.departureRunway = null;
                }
            }
            const destinationAirportIcao = (_c = flightPlan.destinationAirport) !== null && _c !== void 0 ? _c : null;
            if (destinationAirportIcao !== this.destinationAirportIcao) {
                this._data.destinationAirport = null;
                this._data.destinationRunway = null;
                this.destinationAirportIcao = destinationAirportIcao;
                destinationAirportIcao !== null && this.retrieveDestinationAirport(destinationAirportIcao);
            }
            else {
                this._data.destinationAirport = this.destinationAirport;
                if (this.destinationAirport !== null) {
                    this._data.destinationRunway = (_d = flightPlan.procedureDetails.destinationRunway) !== null && _d !== void 0 ? _d : null;
                }
                else {
                    this._data.destinationRunway = null;
                }
            }
        }
        /**
         * Retrieves a departure airport.
         * @param icao The ICAO of the airport to retrieve.
         */
        async retrieveDepartureAirport(icao) {
            let airport = null;
            try {
                airport = await this.fms.facLoader.getFacility(msfsSdk.FacilityType.Airport, icao);
            }
            catch (_a) {
                // noop
            }
            if (icao === this.departureAirportIcao) {
                this.departureAirport = airport;
            }
        }
        /**
         * Retrieves a destination airport.
         * @param icao The ICAO of the airport to retrieve.
         */
        async retrieveDestinationAirport(icao) {
            let airport = null;
            try {
                airport = await this.fms.facLoader.getFacility(msfsSdk.FacilityType.Airport, icao);
            }
            catch (_a) {
                // noop
            }
            if (icao === this.destinationAirportIcao) {
                this.destinationAirport = airport;
            }
        }
        /**
         * Destroys this data provider.
         */
        destroy() {
            this.isAlive = false;
            for (const sub of this.subscriptions) {
                sub.destroy();
            }
        }
    }
    DefaultTerrainSystemDataProvider.NEAREST_AIRPORT_RADIUS_METERS = msfsSdk.UnitType.NMILE.convertTo(5, msfsSdk.UnitType.METER);
    DefaultTerrainSystemDataProvider.NEAREST_AIRPORT_RADIUS_GAR = msfsSdk.UnitType.NMILE.convertTo(5, msfsSdk.UnitType.GA_RADIAN);
    DefaultTerrainSystemDataProvider.RUNWAY_NO_WATER_MASK = ~(1 << msfsSdk.RunwaySurfaceType.WaterFSX
        | 1 << msfsSdk.RunwaySurfaceType.Lake
        | 1 << msfsSdk.RunwaySurfaceType.Ocean
        | 1 << msfsSdk.RunwaySurfaceType.Pond
        | 1 << msfsSdk.RunwaySurfaceType.River
        | 1 << msfsSdk.RunwaySurfaceType.WasteWater
        | 1 << msfsSdk.RunwaySurfaceType.Water);

    /**
     * A default implementation of `TerrainSystemStateDataProvider`.
     */
    class DefaultTerrainSystemStateDataProvider {
        /**
         * Creates a new instance of DefaultTerrainSystemStateDataProvider.
         * @param bus The event bus.
         * @param id The ID of the terrain alerting system for which to provide data.
         */
        constructor(bus, id) {
            this.bus = bus;
            this.id = id;
            this._type = msfsSdk.ConsumerSubject.create(null, undefined).pause();
            /** @inheritDoc */
            this.type = this._type;
            this._operatingMode = msfsSdk.ConsumerSubject.create(null, exports.TerrainSystemOperatingMode.Off).pause();
            /** @inheritDoc */
            this.operatingMode = this._operatingMode;
            this.statusFlagsSource = msfsSdk.ConsumerValue.create(null, []);
            this._statusFlags = msfsSdk.SetSubject.create();
            /** @inheritDoc */
            this.statusFlags = this._statusFlags;
            this.inhibitFlagsSource = msfsSdk.ConsumerValue.create(null, []);
            this._inhibitFlags = msfsSdk.SetSubject.create();
            /** @inheritDoc */
            this.inhibitFlags = this._inhibitFlags;
            this.activeAlertsSource = msfsSdk.ConsumerValue.create(null, []);
            this._activeAlerts = msfsSdk.SetSubject.create();
            /** @inheritDoc */
            this.activeAlerts = this._activeAlerts;
            this._prioritizedAlert = msfsSdk.ConsumerSubject.create(null, null).pause();
            /** @inheritDoc */
            this.prioritizedAlert = this._prioritizedAlert;
            this.isInit = false;
            this.isAlive = true;
            this.isPaused = true;
            this.pauseable = [
                this._type,
                this._operatingMode,
                this._prioritizedAlert
            ];
            this.subscriptions = [
                this._type,
                this._operatingMode,
                this._prioritizedAlert,
                this.statusFlagsSource,
                this.inhibitFlagsSource,
                this.activeAlertsSource
            ];
        }
        /**
         * Initializes this data provider. Once initialized, this data provider will continuously update its data until
         * paused or destroyed.
         * @param paused Whether to initialize this data provider as paused. If `true`, this data provider will provide an
         * initial set of data but will not update the provided data until it is resumed. Defaults to `false`.
         * @throws Error if this data provider is dead.
         */
        init(paused = false) {
            if (!this.isAlive) {
                throw new Error('DefaultTerrainSystemStateDataProvider: cannot initialize a dead provider');
            }
            if (this.isInit) {
                return;
            }
            this.isInit = true;
            const sub = this.bus.getSubscriber();
            this._type.setConsumer(TerrainSystemUtils.onEvent(this.id, sub, 'terrainsys_type'));
            this._operatingMode.setConsumer(TerrainSystemUtils.onEvent(this.id, sub, 'terrainsys_operating_mode'));
            this.statusFlagsSource.setConsumer(TerrainSystemUtils.onEvent(this.id, sub, 'terrainsys_status_flags'));
            this.inhibitFlagsSource.setConsumer(TerrainSystemUtils.onEvent(this.id, sub, 'terrainsys_inhibit_flags'));
            this.activeAlertsSource.setConsumer(TerrainSystemUtils.onEvent(this.id, sub, 'terrainsys_active_alerts'));
            this._prioritizedAlert.setConsumer(TerrainSystemUtils.onEvent(this.id, sub, 'terrainsys_prioritized_alert'));
            const addToSet = (set, key) => {
                set.add(key);
            };
            const removeFromSet = (set, key) => {
                set.delete(key);
            };
            const setSubs = [
                TerrainSystemUtils.onEvent(this.id, sub, 'terrainsys_status_added').handle(addToSet.bind(this, this._statusFlags)),
                TerrainSystemUtils.onEvent(this.id, sub, 'terrainsys_status_removed').handle(removeFromSet.bind(this, this._statusFlags)),
                TerrainSystemUtils.onEvent(this.id, sub, 'terrainsys_inhibit_added').handle(addToSet.bind(this, this._inhibitFlags)),
                TerrainSystemUtils.onEvent(this.id, sub, 'terrainsys_inhibit_removed').handle(removeFromSet.bind(this, this._inhibitFlags)),
                TerrainSystemUtils.onEvent(this.id, sub, 'terrainsys_alert_activated').handle(addToSet.bind(this, this._activeAlerts)),
                TerrainSystemUtils.onEvent(this.id, sub, 'terrainsys_alert_deactivated').handle(removeFromSet.bind(this, this._activeAlerts))
            ];
            this.pauseable.push(...setSubs);
            this.subscriptions.push(...setSubs);
            if (!paused) {
                this.resume();
            }
        }
        /**
         * Resumes this data provider. Once resumed, this data provider will continuously update its data until paused or
         * destroyed.
         * @throws Error if this data provider is dead.
         */
        resume() {
            if (!this.isAlive) {
                throw new Error('DefaultTerrainSystemStateDataProvider: cannot resume a dead provider');
            }
            if (!this.isInit || !this.isPaused) {
                return;
            }
            this.isPaused = false;
            this._statusFlags.set(this.statusFlagsSource.get());
            this._inhibitFlags.set(this.inhibitFlagsSource.get());
            this._activeAlerts.set(this.activeAlertsSource.get());
            for (const pauseable of this.pauseable) {
                pauseable.resume();
            }
        }
        /**
         * Pauses this data provider. Once paused, this data provider will not update its data until it is resumed.
         * @throws Error if this data provider is dead.
         */
        pause() {
            if (!this.isAlive) {
                throw new Error('DefaultTerrainSystemStateDataProvider: cannot pause a dead provider');
            }
            if (!this.isInit || this.isPaused) {
                return;
            }
            this.isPaused = true;
            for (const pauseable of this.pauseable) {
                pauseable.pause();
            }
        }
        /**
         * Destroys this data provider. Once destroyed, this data provider will no longer update its provided data, and can
         * no longer be paused or resumed.
         */
        destroy() {
            this.isAlive = false;
            for (const sub of this.subscriptions) {
                sub.destroy();
            }
        }
    }

    /**
     * A Garmin TAWS-A/B terrain alerting system.
     */
    class GarminTaws extends AbstractTerrainSystem {
        /**
         * Creates a new instance of GarminTaws.
         * @param id This terrain system's ID.
         * @param type This terrain system's type.
         * @param bus The event bus.
         * @param dataProvider A provider of terrain system data.
         * @param options Options with which to configure the system.
         */
        constructor(id, type, bus, dataProvider, options) {
            var _a, _b, _c;
            super(id, type, bus, dataProvider, (_a = options === null || options === void 0 ? void 0 : options.prioritizedAlertSelector) !== null && _a !== void 0 ? _a : GarminTaws.selectDefaultPrioritizedAlert);
            this.testTimeRemaining = 0;
            this.lastUpdateSimTime = undefined;
            this.supportGpwsFailStatus = (_b = options === null || options === void 0 ? void 0 : options.supportGpwsFailStatus) !== null && _b !== void 0 ? _b : false;
            this.testDuration = (_c = options === null || options === void 0 ? void 0 : options.testDuration) !== null && _c !== void 0 ? _c : 15000;
        }
        /** @inheritDoc */
        onTurnOn() {
            if (this.operatingMode.get() !== exports.TerrainSystemOperatingMode.Off) {
                return;
            }
            this.operatingMode.set(exports.TerrainSystemOperatingMode.Operating);
        }
        /** @inheritDoc */
        onTurnOff() {
            this.operatingMode.set(exports.TerrainSystemOperatingMode.Off);
        }
        /** @inheritDoc */
        onStartTest() {
            if (this.operatingMode.get() !== exports.TerrainSystemOperatingMode.Operating) {
                return;
            }
            this.operatingMode.set(exports.TerrainSystemOperatingMode.Test);
            this.testTimeRemaining = this.testDuration;
        }
        /** @inheritDoc */
        onUpdate() {
            const operatingMode = this.operatingMode.get();
            const data = this.dataProvider.data;
            if (operatingMode !== exports.TerrainSystemOperatingMode.Off) {
                this.statuses.toggle(exports.GarminTawsStatus.TawsNotAvailable, !data.isGpsPosValid);
                if (this.supportGpwsFailStatus) {
                    this.statuses.toggle(exports.GarminTawsStatus.GpwsFailed, !data.isRadarAltitudeValid || (!data.isBaroAltitudeValid && !data.isGpsPosValid));
                }
                if (operatingMode === exports.TerrainSystemOperatingMode.Test) {
                    if (!this.statuses.has(exports.GarminTawsStatus.TawsFailed)
                        && !this.statuses.has(exports.GarminTawsStatus.TawsNotAvailable)
                        && !this.statuses.has(exports.GarminTawsStatus.GpwsFailed)) {
                        this.testTimeRemaining -= this.lastUpdateSimTime === undefined ? 0 : Math.max(0, data.simTime - this.lastUpdateSimTime);
                    }
                    if (this.testTimeRemaining <= 0) {
                        this.operatingMode.set(exports.TerrainSystemOperatingMode.Operating);
                    }
                }
            }
            this.updateModules();
            this.lastUpdateSimTime = data.simTime;
        }
        /**
         * Selects a prioritized alert from an iterable of active alerts based on the criteria published in TSO-151c.
         * @param alerts An iterable of active alerts.
         * @returns The prioritized alert from the specified set of active alerts, or `null` if a prioritized alert could not
         * be selected.
         */
        static selectDefaultPrioritizedAlert(alerts) {
            var _a;
            let bestPriority = -1;
            let bestAlert = null;
            for (const alert of alerts) {
                const priority = (_a = GarminTaws.DEFAULT_ALERT_PRIORITIES[alert]) !== null && _a !== void 0 ? _a : -1;
                if (priority > bestPriority) {
                    bestPriority = priority;
                    bestAlert = alert;
                }
            }
            return bestAlert;
        }
    }
    GarminTaws.DEFAULT_ALERT_PRIORITIES = {
        [exports.GarminTawsAlert.EdrWarning]: 13,
        [exports.GarminTawsAlert.EcrWarning]: 12,
        [exports.GarminTawsAlert.RtcWarning]: 11,
        [exports.GarminTawsAlert.ItiWarning]: 10,
        [exports.GarminTawsAlert.RocWarning]: 10,
        [exports.GarminTawsAlert.IoiWarning]: 10,
        [exports.GarminTawsAlert.RtcCaution]: 9,
        [exports.GarminTawsAlert.ItiCaution]: 8,
        [exports.GarminTawsAlert.RocCaution]: 8,
        [exports.GarminTawsAlert.IoiCaution]: 8,
        [exports.GarminTawsAlert.EcrCaution]: 7,
        [exports.GarminTawsAlert.FitTerrainCaution]: 6,
        [exports.GarminTawsAlert.FitTakeoffCaution]: 6,
        [exports.GarminTawsAlert.PdaCaution]: 5,
        [exports.GarminTawsAlert.FitGearCaution]: 4,
        [exports.GarminTawsAlert.FitFlapsCaution]: 3,
        [exports.GarminTawsAlert.EdrCaution]: 2,
        [exports.GarminTawsAlert.NcrCaution]: 1,
        [exports.GarminTawsAlert.GsdGlideslopeCaution]: 0,
        [exports.GarminTawsAlert.GsdGlidepathCaution]: 0
    };

    /**
     * A manager of timers for Garmin avionics. Manages one flight timer and an arbitrary number of generic timers.
     */
    class GarminTimerManager {
        // eslint-disable-next-line jsdoc/require-jsdoc
        constructor(bus, arg2) {
            var _a;
            this.bus = bus;
            this.genericTimers = {};
            this.isAlive = true;
            this.isInit = false;
            this.controlSubs = [];
            if (typeof arg2 === 'number') {
                this.id = '';
                this.genericTimerCount = Math.max(arg2, 0);
            }
            else {
                this.id = (_a = arg2.id) !== null && _a !== void 0 ? _a : '';
                this.genericTimerCount = Math.max(arg2.genericTimerCount, 0);
            }
            for (let i = 1; i <= this.genericTimerCount; i++) {
                this.genericTimers[i] = new GenericTimer(bus, this.id, i);
            }
        }
        /**
         * Initializes this manager.
         * @throws Error if this manager has been destroyed.
         */
        init() {
            if (!this.isAlive) {
                throw new Error('GarminTimerManager: cannot initialize a dead manager');
            }
            if (this.isInit) {
                return;
            }
            this.isInit = true;
            // TODO: Support the flight timer.
            for (let i = 1; i <= this.genericTimerCount; i++) {
                this.genericTimers[i].init();
            }
        }
        /**
         * Destroys this manager.
         */
        destroy() {
            this.isAlive = false;
            for (let i = 1; i <= this.genericTimerCount; i++) {
                this.genericTimers[i].destroy();
            }
            this.controlSubs.forEach(sub => { sub.destroy(); });
        }
    }
    /** The index of the flight timer. */
    GarminTimerManager.FLIGHT_TIMER_INDEX = 1;
    /** The index of the first generic timer. */
    GarminTimerManager.GENERIC_TIMER_INDEX = 2;
    /** The maximum value of a generic timer, in milliseconds, exclusive. */
    GarminTimerManager.MAX_GENERIC_TIMER_VALUE = msfsSdk.UnitType.HOUR.convertTo(24, msfsSdk.UnitType.MILLISECOND);
    /**
     * A Garmin generic timer. Supports counting up and down from an initial value within the range 00:00:00 to 23:59:59.
     * If a timer reaches 0 when counting down, its initial value is set to 00:00:00 and it will begin counting up from
     * that value. If a timer reaches 23:59:59 when counting up, its initial value is set to 00:00:00 and
     * it will begin counting up from that value.
     */
    class GenericTimer {
        /**
         * Creates a new instance of GenericTimer.
         * @param bus The event bus.
         * @param id The ID of this timer.
         * @param index The index of this timer.
         */
        constructor(bus, id, index) {
            this.bus = bus;
            this.id = id;
            this.index = index;
            this.idSuffix = msfsSdk.FlightTimerUtils.getIdSuffix(this.id);
            this.timerIndex = this.index + GarminTimerManager.GENERIC_TIMER_INDEX - 1;
            this.publisher = this.bus.getPublisher();
            this.controlEventTopics = {
                setMode: `timer_set_mode${this.idSuffix}_${this.timerIndex}`,
                setInitialValue: `timer_set_initial_value${this.idSuffix}_${this.timerIndex}`,
                setValue: `timer_set_value${this.idSuffix}_${this.timerIndex}`,
                start: `timer_start${this.idSuffix}_${this.timerIndex}`,
                stop: `timer_stop${this.idSuffix}_${this.timerIndex}`,
                reset: `timer_reset${this.idSuffix}_${this.timerIndex}`
            };
            this.isRunning = msfsSdk.ConsumerSubject.create(null, false);
            this.mode = msfsSdk.ConsumerSubject.create(null, msfsSdk.FlightTimerMode.CountingDown);
            this.value = msfsSdk.ConsumerSubject.create(null, 0);
            this.modeValueState = msfsSdk.MappedSubject.create(this.mode, this.value);
            this.controlSubs = [];
        }
        /**
         * Initializes this timer. Once this timer is initialized, it will respond to timer control events and will
         * automatically handle when its current value counts down to below zero or counts up to above the maximum value.
         */
        init() {
            const sub = this.bus.getSubscriber();
            this.isRunning.setConsumer(msfsSdk.FlightTimerUtils.onEvent(this.id, this.timerIndex, sub, 'timer_is_running'));
            this.mode.setConsumer(msfsSdk.FlightTimerUtils.onEvent(this.id, this.timerIndex, sub, 'timer_mode'));
            this.value.setConsumer(msfsSdk.FlightTimerUtils.onEvent(this.id, this.timerIndex, sub, 'timer_value_ms'));
            const modeValueStateSub = this.modeValueState.sub(([mode, value]) => {
                if (value < 0) {
                    // If timer value is negative and counting down, reset the initial value to 0 and set the timer to start
                    // counting up from the time it reached 0, otherwise clamp the value to 0.
                    if (mode === msfsSdk.FlightTimerMode.CountingUp) {
                        this.publisher.pub(this.controlEventTopics.setValue, 0, true, false);
                    }
                    else {
                        this.publisher.pub(this.controlEventTopics.setInitialValue, 0, true, false);
                        this.publisher.pub(this.controlEventTopics.setValue, -value, true, false);
                        this.publisher.pub(this.controlEventTopics.setMode, msfsSdk.FlightTimerMode.CountingUp, true, false);
                    }
                }
                else if (value >= GenericTimer.MAX_VALUE && mode !== msfsSdk.FlightTimerMode.CountingDown) {
                    // If timer value is overflowing the maximum value, reset the initial and current values to 0 if timer is
                    // counting up, otherwise clamp the value to below the maximum.
                    if (mode === msfsSdk.FlightTimerMode.CountingUp) {
                        this.publisher.pub(this.controlEventTopics.setInitialValue, 0, true, false);
                        this.publisher.pub(this.controlEventTopics.setValue, value - GenericTimer.MAX_VALUE, true, false);
                    }
                    else {
                        this.publisher.pub(this.controlEventTopics.setValue, GenericTimer.MAX_VALUE - 1000, true, false);
                    }
                }
            }, false, true);
            this.isRunning.sub(isRunning => {
                if (isRunning) {
                    modeValueStateSub.resume(true);
                }
                else {
                    modeValueStateSub.pause();
                }
            }, true);
            const controlSub = this.bus.getSubscriber();
            this.controlSubs.push(controlSub.on(`garmin_gen_timer_set_mode${this.idSuffix}_${this.index}`).handle(mode => {
                this.publisher.pub(this.controlEventTopics.setMode, mode, true, false);
            }));
            this.controlSubs.push(controlSub.on(`garmin_gen_timer_set_value${this.idSuffix}_${this.index}`).handle(value => {
                value = msfsSdk.MathUtils.clamp(value, 0, GenericTimer.MAX_VALUE - 1000);
                this.publisher.pub(this.controlEventTopics.stop, undefined, true, false);
                this.publisher.pub(this.controlEventTopics.setInitialValue, value, true, false);
                this.publisher.pub(this.controlEventTopics.reset, undefined, true, false);
            }));
            this.controlSubs.push(controlSub.on(`garmin_gen_timer_start${this.idSuffix}_${this.index}`).handle(() => {
                this.publisher.pub(this.controlEventTopics.start, undefined, true, false);
            }));
            this.controlSubs.push(controlSub.on(`garmin_gen_timer_stop${this.idSuffix}_${this.index}`).handle(() => {
                this.publisher.pub(this.controlEventTopics.stop, undefined, true, false);
            }));
            this.controlSubs.push(controlSub.on(`garmin_gen_timer_reset${this.idSuffix}_${this.index}`).handle(() => {
                this.publisher.pub(this.controlEventTopics.stop, undefined, true, false);
                this.publisher.pub(this.controlEventTopics.reset, undefined, true, false);
            }));
        }
        /**
         * Destroys this timer.
         */
        destroy() {
            this.mode.destroy();
            this.value.destroy();
            this.controlSubs.forEach(sub => { sub.destroy(); });
        }
    }
    GenericTimer.MAX_VALUE = msfsSdk.UnitType.HOUR.convertTo(24, msfsSdk.UnitType.MILLISECOND);

    /**
     * ADS-B Conflict Situational Awareness (CSA) sensitivity parameters.
     */
    class AdsbSensitivityParameters {
        /**
         * Selects a sensitivity level for a specified environment.
         * @param altitude The indicated altitude of the own airplane.
         * @param cdiScalingLabel The CDI scaling sensitivity of the own airplane.
         * @param radarAltitude The radar altitude of the own airplane.
         * @returns The sensitivity level for the specified environment.
         */
        selectLevel(altitude, cdiScalingLabel, radarAltitude) {
            const altFeet = altitude.asUnit(msfsSdk.UnitType.FOOT);
            const radarAltFeet = radarAltitude === null || radarAltitude === void 0 ? void 0 : radarAltitude.asUnit(msfsSdk.UnitType.FOOT);
            let isApproach = false;
            switch (cdiScalingLabel) {
                case exports.CDIScaleLabel.LNav:
                case exports.CDIScaleLabel.LNavPlusV:
                case exports.CDIScaleLabel.LNavVNav:
                case exports.CDIScaleLabel.LP:
                case exports.CDIScaleLabel.LPPlusV:
                case exports.CDIScaleLabel.LPV:
                case exports.CDIScaleLabel.MissedApproach:
                    isApproach = true;
            }
            let level;
            if ((radarAltFeet === undefined || radarAltFeet > 2350)
                && (!isApproach && cdiScalingLabel !== exports.CDIScaleLabel.Terminal)) {
                if (altFeet > 42000) {
                    level = 6;
                }
                else if (altFeet > 20000) {
                    level = 5;
                }
                else if (altFeet > 10000) {
                    level = 4;
                }
                else if (altFeet > 5000) {
                    level = 3;
                }
                else {
                    level = 2;
                }
            }
            else if (cdiScalingLabel === exports.CDIScaleLabel.Terminal
                || (radarAltFeet !== undefined && radarAltFeet > 1000)) {
                level = 1;
            }
            else {
                level = 0;
            }
            return level;
        }
        /**
         * Selects Traffic Advisory sensitivity settings for a specified environment.
         * @param altitude The indicated altitude of the own airplane.
         * @param cdiScalingLabel The CDI scaling sensitivity of the own airplane.
         * @param radarAltitude The radar altitude of the own airplane.
         * @returns Traffic Advisory sensitivity settings for the specified environment.
         */
        selectTA(altitude, cdiScalingLabel, radarAltitude) {
            return AdsbSensitivityParameters.TA_LEVELS[this.selectLevel(altitude, cdiScalingLabel, radarAltitude)];
        }
        /**
         * Gets Traffic Advisory sensitivity parameters for a given sensitivity level.
         * @param level A sensitivity level.
         * @returns Traffic Advisory sensitivity parameters for the given sensitivity level.
         */
        getTA(level) {
            return AdsbSensitivityParameters.TA_LEVELS[msfsSdk.MathUtils.clamp(level, 0, AdsbSensitivityParameters.TA_LEVELS.length - 1)];
        }
    }
    AdsbSensitivityParameters.TA_LEVELS = [
        {
            tau: msfsSdk.UnitType.SECOND.createNumber(20),
            protectedRadius: msfsSdk.UnitType.NMILE.createNumber(0.2),
            protectedHeight: msfsSdk.UnitType.FOOT.createNumber(850)
        },
        {
            tau: msfsSdk.UnitType.SECOND.createNumber(25),
            protectedRadius: msfsSdk.UnitType.NMILE.createNumber(0.2),
            protectedHeight: msfsSdk.UnitType.FOOT.createNumber(850)
        },
        {
            tau: msfsSdk.UnitType.SECOND.createNumber(30),
            protectedRadius: msfsSdk.UnitType.NMILE.createNumber(0.35),
            protectedHeight: msfsSdk.UnitType.FOOT.createNumber(850)
        },
        {
            tau: msfsSdk.UnitType.SECOND.createNumber(40),
            protectedRadius: msfsSdk.UnitType.NMILE.createNumber(0.55),
            protectedHeight: msfsSdk.UnitType.FOOT.createNumber(850)
        },
        {
            tau: msfsSdk.UnitType.SECOND.createNumber(45),
            protectedRadius: msfsSdk.UnitType.NMILE.createNumber(0.8),
            protectedHeight: msfsSdk.UnitType.FOOT.createNumber(850)
        },
        {
            tau: msfsSdk.UnitType.SECOND.createNumber(48),
            protectedRadius: msfsSdk.UnitType.NMILE.createNumber(1.1),
            protectedHeight: msfsSdk.UnitType.FOOT.createNumber(850)
        },
        {
            tau: msfsSdk.UnitType.SECOND.createNumber(48),
            protectedRadius: msfsSdk.UnitType.NMILE.createNumber(1.1),
            protectedHeight: msfsSdk.UnitType.FOOT.createNumber(1200)
        }
    ];

    /**
     * A Garmin ADS-B system.
     */
    class GarminAdsb extends msfsSdk.Adsb {
        constructor() {
            super(...arguments);
            this.adsbEnabledSetting = TrafficUserSettings.getManager(this.bus).getSetting('trafficAdsbEnabled');
        }
        /** @inheritdoc */
        init() {
            super.init();
            this.adsbEnabledSetting.sub(isEnabled => {
                // TODO: Support surface mode
                this.operatingMode.set(isEnabled ? msfsSdk.AdsbOperatingMode.Airborne : msfsSdk.AdsbOperatingMode.Standby);
            }, true);
        }
    }

    /**
     * An intruder tracked by Garmin traffic systems.
     */
    class GarminTcasIntruder extends msfsSdk.AbstractTcasIntruder {
        // eslint-disable-next-line jsdoc/require-returns
        /**
         * The sim time, as a UNIX timestamp in milliseconds, at which this intruder's alert level was most recently switched
         * to Traffic Advisory from another alert level.
         */
        get taOnTime() {
            return this._taOnTime;
        }
        // eslint-disable-next-line jsdoc/require-returns
        /**
         * The sim time, as a UNIX timestamp in milliseconds, at which this intruder's alert level was most recently switched
         * from Traffic Advisory to another alert level.
         */
        get taOffTime() {
            return this._taOffTime;
        }
        /**
         * Constructor.
         * @param contact The traffic contact associated with this intruder.
         * @param simTime A subscribable which provides the current sim time, as a UNIX timestamp in milliseconds.
         */
        constructor(contact, simTime) {
            super(contact);
            this.simTime = simTime;
            this._taOnTime = 0;
            this._taOffTime = 0;
            this.lastAlertLevel = this.alertLevel.get();
            this.alertLevel.sub(this.onAlertLevelChanged.bind(this));
        }
        /**
         * Responds to changes in this intruder's alert level.
         * @param alertLevel The new alert level.
         */
        onAlertLevelChanged(alertLevel) {
            if (alertLevel === msfsSdk.TcasAlertLevel.TrafficAdvisory) {
                this._taOnTime = this.simTime.get();
            }
            else if (this.lastAlertLevel === msfsSdk.TcasAlertLevel.TrafficAdvisory) {
                this._taOffTime = this.simTime.get();
            }
            this.lastAlertLevel = alertLevel;
        }
    }

    /**
     * Garmin TCAS-II.
     */
    class GarminTcasII extends msfsSdk.Tcas {
        /**
         * Constructor.
         * @param bus The event bus.
         * @param tfcInstrument The traffic instrument which provides traffic contacts for this TCAS.
         * @param adsb The ADS-B system associated with this TCAS, or `null` if this TCAS does not support ADS-B.
         * @param maxIntruderCount The maximum number of intruders tracked at any one time by this TCAS. Defaults to
         * {@link GarminTcasII.DEFAULT_MAX_INTRUDER_COUNT}.
         * @param realTimeUpdateFreq The maximum update frequency (Hz) in real time. Defaults to
         * {@link GarminTcasII.DEFAULT_REAL_TIME_UPDATE_FREQ}.
         * @param simTimeUpdateFreq The maximum update frequency (Hz) in sim time. Defaults to
         * {@link GarminTcasII.DEFAULT_SIM_TIME_UPDATE_FREQ}.
         */
        constructor(bus, tfcInstrument, adsb, maxIntruderCount = GarminTcasII.DEFAULT_MAX_INTRUDER_COUNT, realTimeUpdateFreq = GarminTcasII.DEFAULT_REAL_TIME_UPDATE_FREQ, simTimeUpdateFreq = GarminTcasII.DEFAULT_SIM_TIME_UPDATE_FREQ) {
            super(bus, tfcInstrument, maxIntruderCount, realTimeUpdateFreq, simTimeUpdateFreq);
            this.adsb = adsb;
            this.type = exports.TrafficSystemType.TcasII;
            this.cdiScalingLabel = exports.CDIScaleLabel.Enroute;
            this._isPowered = msfsSdk.Subject.create(true);
            this.operatingModeSetting = TrafficUserSettings.getManager(this.bus).getSetting('trafficOperatingMode');
            this.operatingModeState = msfsSdk.MappedSubject.create(this._isPowered, this.operatingModeSetting);
            this.raAltitudeInhibitFlag = msfsSdk.MappedSubject.create(([radarAlt, isClimbing]) => {
                return radarAlt < (isClimbing ? 900 : 1100);
            }, this.ownAirplaneSubs.radarAltitude.map(radarAlt => Math.round(radarAlt.asUnit(msfsSdk.UnitType.FOOT))), this.ownAirplaneSubs.verticalSpeed.map(verticalSpeed => verticalSpeed.number >= 0));
        }
        /** @inheritdoc */
        createSensitivity() {
            return new GarminTcasIISensitivity();
        }
        /** @inheritdoc */
        init() {
            var _a;
            super.init();
            this.bus.getSubscriber().on('lnavdata_cdi_scale_label').whenChanged().handle(label => { this.cdiScalingLabel = label; });
            this.operatingModeState.sub(([isPowered, operatingModeSetting]) => {
                if (!isPowered) {
                    this.operatingModeSub.set(msfsSdk.TcasOperatingMode.Off);
                }
                else {
                    switch (operatingModeSetting) {
                        case exports.TrafficOperatingModeSetting.Operating:
                        case exports.TrafficOperatingModeSetting.Auto:
                            if (this.raAltitudeInhibitFlag.get()) {
                                this.setOperatingMode(msfsSdk.TcasOperatingMode.TAOnly);
                            }
                            else {
                                this.setOperatingMode(msfsSdk.TcasOperatingMode.TA_RA);
                            }
                            break;
                        case exports.TrafficOperatingModeSetting.TAOnly:
                            this.operatingModeSub.set(msfsSdk.TcasOperatingMode.TAOnly);
                            break;
                        default:
                            this.operatingModeSub.set(msfsSdk.TcasOperatingMode.Standby);
                    }
                }
            }, true);
            this.raAltitudeInhibitFlag.sub(inhibit => {
                if (this._isPowered.get() && this.operatingModeSetting.value === exports.TrafficOperatingModeSetting.Auto) {
                    this.setOperatingMode(inhibit ? msfsSdk.TcasOperatingMode.TAOnly : msfsSdk.TcasOperatingMode.TA_RA);
                }
            });
            (_a = this.adsb) === null || _a === void 0 ? void 0 : _a.init();
        }
        /** @inheritdoc */
        isPowered() {
            return this._isPowered.get();
        }
        /** @inheritdoc */
        setPowered(isPowered) {
            this._isPowered.set(isPowered);
        }
        /** @inheritdoc */
        createIntruderEntry(contact) {
            return new GarminTcasIntruder(contact, this.simTime);
        }
        /** @inheritdoc */
        updateSensitivity() {
            if (this.adsb) {
                this.sensitivity.update(this.adsb.getOperatingMode(), this.ownAirplaneSubs.altitude.get(), this.cdiScalingLabel, this.ownAirplaneSubs.radarAltitude.get());
            }
            else {
                this.sensitivity.update(this.ownAirplaneSubs.altitude.get(), this.ownAirplaneSubs.radarAltitude.get());
            }
        }
        /** @inheritdoc */
        canIssueTrafficAdvisory(simTime, intruder) {
            if (this.ownAirplaneSubs.isOnGround.get()) {
                return false;
            }
            if (intruder.alertLevel.get() !== msfsSdk.TcasAlertLevel.TrafficAdvisory) {
                const dt = simTime - intruder.taOffTime;
                return dt < 0 || dt >= GarminTcasII.TA_ON_HYSTERESIS;
            }
            return true;
        }
        /** @inheritdoc */
        canCancelTrafficAdvisory(simTime, intruder) {
            if (this.ownAirplaneSubs.isOnGround.get()) {
                return true;
            }
            const dt = simTime - intruder.taOnTime;
            return dt < 0 || dt >= GarminTcasII.TA_OFF_HYSTERESIS;
        }
    }
    GarminTcasII.DEFAULT_MAX_INTRUDER_COUNT = 40;
    GarminTcasII.DEFAULT_REAL_TIME_UPDATE_FREQ = 2; // hz
    GarminTcasII.DEFAULT_SIM_TIME_UPDATE_FREQ = 1; // hz
    GarminTcasII.TA_ON_HYSTERESIS = 2000; // ms
    GarminTcasII.TA_OFF_HYSTERESIS = 8000; // ms
    /**
     * An implementation of {@link TcasSensitivity} which provides sensitivity parameters for the Garmin TCAS-II. When
     * ADS-B is operating, Traffic Advisory sensitivity is selected based on the ADS-B Conflict Situational Awareness (CSA)
     * algorithm. When ADS-B is not operating, Traffic Advisory sensitivity is selected based on the TCAS-II algorithm.
     * Resolution Advisory sensitivity is always determined by the TCAS-II algorithm.
     */
    class GarminTcasIISensitivity {
        constructor() {
            this.tcasIISensitivity = new msfsSdk.TcasIISensitivityParameters();
            this.adsbSensitivity = new AdsbSensitivityParameters();
            this.tcasIIParams = {
                parametersPA: this.tcasIISensitivity.getPA(0),
                parametersTA: this.tcasIISensitivity.getTA(0),
                parametersRA: this.tcasIISensitivity.getRA(0)
            };
            this.adsbParams = {
                parametersPA: this.tcasIISensitivity.getPA(0),
                parametersTA: this.adsbSensitivity.getTA(0),
                parametersRA: this.tcasIISensitivity.getRA(0)
            };
            this.tcasIILevel = 0;
            this.adsbLevel = 0;
            this.activeParams = this.tcasIIParams;
        }
        /** @inheritdoc */
        selectParameters() {
            return this.activeParams;
        }
        /** @inheritdoc */
        selectRAAlim() {
            return this.tcasIISensitivity.getRAAlim(this.tcasIILevel);
        }
        // eslint-disable-next-line jsdoc/require-jsdoc
        update(arg1, arg2, arg3, arg4) {
            let altitude;
            let radarAltitude;
            let supportAdsb;
            if (typeof arg1 === 'object') {
                altitude = arg1;
                radarAltitude = arg2;
                supportAdsb = false;
            }
            else {
                altitude = arg2;
                radarAltitude = arg4;
                supportAdsb = true;
            }
            this.tcasIILevel = this.tcasIISensitivity.selectLevel(altitude, radarAltitude);
            this.tcasIIParams.parametersPA = this.tcasIISensitivity.getPA(this.tcasIILevel);
            this.tcasIIParams.parametersTA = this.tcasIISensitivity.getTA(this.tcasIILevel);
            this.tcasIIParams.parametersRA = this.tcasIISensitivity.getRA(this.tcasIILevel);
            if (supportAdsb) {
                this.adsbLevel = this.adsbSensitivity.selectLevel(altitude, arg3, radarAltitude);
                this.adsbParams.parametersPA = this.tcasIISensitivity.getPA(this.tcasIILevel);
                this.adsbParams.parametersTA = this.adsbSensitivity.getTA(this.adsbLevel);
                this.adsbParams.parametersRA = this.tcasIISensitivity.getRA(this.tcasIILevel);
                // Right now we just assume every intruder is tracked by ADS-B if ADS-B is operating
                this.activeParams = arg1 === msfsSdk.AdsbOperatingMode.Standby ? this.tcasIIParams : this.adsbParams;
            }
            else {
                this.activeParams = this.tcasIIParams;
            }
        }
    }

    /**
     * A manager which reconciles the operating modes of the active transponder and the Garmin TCAS-II traffic system.
     * The manager ensures that any time the active transponder is set to a non-altitude reporting mode, TCAS-II is set to
     * standby mode, and anytime TCAS-II is set to a non-standby mode, the active transponder is set to altitude reporting
     * mode.
     */
    class GarminXpdrTcasManager {
        /**
         * Constructor.
         * @param bus The event bus.
         * @param activeXpdrIndex The index of the active transponder.
         */
        constructor(bus, activeXpdrIndex) {
            this.bus = bus;
            this.controlPublisher = this.bus.getPublisher();
            this.xpdrMode = msfsSdk.ConsumerSubject.create(null, msfsSdk.XPDRMode.OFF);
            this.trafficOperatingModeSetting = TrafficUserSettings.getManager(this.bus).getSetting('trafficOperatingMode');
            this.isAlive = true;
            this.isInit = false;
            this.isPaused = false;
            this.activeXpdrIndex = msfsSdk.SubscribableUtils.toSubscribable(activeXpdrIndex, true);
        }
        /**
         * Initializes this manager. This will perform an immediate reconciliation of transponder and TCAS operating modes
         * (the transponder mode takes precedence), and from this point on the manager will keep the two modes in a valid
         * state until it is destroyed.
         * @param paused Whether to initialize this manager as paused.
         * @throws Error if this manager has been destroyed.
         */
        init(paused = false) {
            if (!this.isAlive) {
                throw new Error('GarminXpdrTcasManager: cannot initialize a dead manager');
            }
            if (this.isInit) {
                return;
            }
            this.isInit = true;
            this.isPaused = paused;
            const sub = this.bus.getSubscriber();
            this.xpdrIndexSub = this.activeXpdrIndex.sub(index => {
                this.xpdrMode.setConsumer(sub.on(`xpdr_mode_${index}`));
            }, true);
            this.xpdrModeSub = this.xpdrMode.sub(mode => {
                if (mode !== msfsSdk.XPDRMode.ALT && this.trafficOperatingModeSetting.value !== exports.TrafficOperatingModeSetting.Standby) {
                    this.trafficOperatingModeSetting.value = exports.TrafficOperatingModeSetting.Standby;
                }
            }, false, paused);
            this.trafficModeSub = this.trafficOperatingModeSetting.sub(mode => {
                if (mode !== exports.TrafficOperatingModeSetting.Standby && this.xpdrMode.get() !== msfsSdk.XPDRMode.ALT) {
                    this.controlPublisher.pub(`publish_xpdr_mode_${this.activeXpdrIndex.get()}`, msfsSdk.XPDRMode.ALT);
                }
            }, !paused, paused); // Perform an initial reconciliation (TCAS setting has precedence) if not paused.
        }
        /**
         * Resumes this manager. Once this manager is resumed, it will automatically reconcile TCAS operating and transponder
         * modes.
         * @throws Error if this manager has been destroyed.
         */
        resume() {
            var _a, _b;
            if (!this.isAlive) {
                throw new Error('GarminXpdrTcasManager: cannot resume a dead manager');
            }
            if (!this.isInit || !this.isPaused) {
                return;
            }
            this.isPaused = false;
            (_a = this.xpdrModeSub) === null || _a === void 0 ? void 0 : _a.resume();
            (_b = this.trafficModeSub) === null || _b === void 0 ? void 0 : _b.resume(true);
        }
        /**
         * Pauses this manager. Once this manager is paused, it will no longer automatically reconcile TCAS operating and
         * transponder modes until resumed.
         * @throws Error if this manager has been destroyed.
         */
        pause() {
            var _a, _b;
            if (!this.isAlive) {
                throw new Error('GarminXpdrTcasManager: cannot pause a dead manager');
            }
            if (!this.isInit || this.isPaused) {
                return;
            }
            this.isPaused = true;
            (_a = this.xpdrModeSub) === null || _a === void 0 ? void 0 : _a.pause();
            (_b = this.trafficModeSub) === null || _b === void 0 ? void 0 : _b.pause();
        }
        /**
         * Resets this manager. Sets the TCAS operating mode to AUTO and the transponder mode to ALT reporting. Has no effect
         * if this manager is not initialized.
         * @throws Error if this manager has been destroyed.
         */
        reset() {
            if (!this.isAlive) {
                throw new Error('GarminXpdrTcasManager: cannot reset a dead manager');
            }
            if (!this.isInit) {
                return;
            }
            this.trafficOperatingModeSetting.value = exports.TrafficOperatingModeSetting.Auto;
            this.controlPublisher.pub(`publish_xpdr_mode_${this.activeXpdrIndex.get()}`, msfsSdk.XPDRMode.ALT);
        }
        /**
         * Destroys this manager.
         */
        destroy() {
            var _a, _b;
            this.isAlive = false;
            this.xpdrMode.destroy();
            (_a = this.xpdrIndexSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.trafficModeSub) === null || _b === void 0 ? void 0 : _b.destroy();
        }
    }

    /**
     * A default implementation of {@link TcasRaCommandDataProvider}.
     */
    class DefaultTcasRaCommandDataProvider {
        /**
         * Constructor.
         * @param bus The event bus.
         * @param tcas The TCAS which from which this data provider sources resolution advisory commands.
         */
        constructor(bus, tcas) {
            this.bus = bus;
            this.tcas = tcas;
            this._raMinVs = msfsSdk.Subject.create(null);
            /** @inheritdoc */
            this.raMinVs = this._raMinVs;
            this._raMaxVs = msfsSdk.Subject.create(null);
            /** @inheritdoc */
            this.raMaxVs = this._raMaxVs;
            this._raFlyToMinVs = msfsSdk.Subject.create(null);
            /** @inheritdoc */
            this.raFlyToMinVs = this._raFlyToMinVs;
            this._raFlyToMaxVs = msfsSdk.Subject.create(null);
            /** @inheritdoc */
            this.raFlyToMaxVs = this._raFlyToMaxVs;
            this.isInit = false;
            this.isAlive = true;
            this.isPaused = false;
            this.tcasRaSubs = [];
        }
        /**
         * Initializes this data provider. Once initialized, this data provider will continuously update its data until
         * paused or destroyed.
         * @param paused Whether to initialize this data provider as paused. If `true`, this data provider will provide an
         * initial set of data but will not update the provided data until it is resumed. Defaults to `false`.
         * @throws Error if this data provider has been destroyed.
         */
        init(paused = false) {
            if (!this.isAlive) {
                throw new Error('DefaultTcasRaCommandDataProvider: cannot initialize a dead provider');
            }
            if (this.isInit) {
                return;
            }
            this.isInit = true;
            this.isPaused = paused;
            const sub = this.bus.getSubscriber();
            const updateRaSpeeds = this.updateRaSpeeds.bind(this);
            updateRaSpeeds();
            this.tcasRaSubs.push(sub.on('tcas_ra_issued').handle(updateRaSpeeds), sub.on('tcas_ra_updated').handle(updateRaSpeeds), sub.on('tcas_ra_canceled').handle(updateRaSpeeds));
            if (paused) {
                this.pause();
            }
        }
        /**
         * Update vertical speeds commanded by TCAS resolution advisories.
         */
        updateRaSpeeds() {
            const host = this.tcas.getResolutionAdvisoryHost();
            if (host.primaryType === msfsSdk.TcasResolutionAdvisoryType.Clear) {
                this._raMinVs.set(null);
                this._raMaxVs.set(null);
                this._raFlyToMinVs.set(null);
                this._raFlyToMaxVs.set(null);
                return;
            }
            const minVsFpm = host.minVerticalSpeed.asUnit(msfsSdk.UnitType.FPM);
            const maxVsFpm = host.maxVerticalSpeed.asUnit(msfsSdk.UnitType.FPM);
            if (host.secondaryType === null) {
                // Single RA
                if (msfsSdk.BitFlags.isAll(host.primaryFlags, msfsSdk.TcasResolutionAdvisoryFlags.UpSense)) {
                    // Upward sense
                    this._raMaxVs.set(null);
                    this._raMinVs.set(minVsFpm > -100 && minVsFpm < 100 ? -100 : minVsFpm);
                }
                else {
                    // Downward sense
                    this._raMinVs.set(null);
                    this._raMaxVs.set(maxVsFpm > -100 && maxVsFpm < 100 ? 100 : maxVsFpm);
                }
                if (msfsSdk.BitFlags.isAny(host.primaryFlags, msfsSdk.TcasResolutionAdvisoryFlags.Climb | msfsSdk.TcasResolutionAdvisoryFlags.Descend)) {
                    // Corrective positive
                    this._raFlyToMaxVs.set(maxVsFpm);
                    this._raFlyToMinVs.set(minVsFpm);
                }
                else if (host.primaryType === msfsSdk.TcasResolutionAdvisoryType.ReduceDescent) {
                    // Corrective upward negative
                    this._raFlyToMinVs.set(-100);
                    this._raFlyToMaxVs.set(500);
                }
                else if (host.primaryType === msfsSdk.TcasResolutionAdvisoryType.ReduceClimb) {
                    // Corrective downward negative
                    this._raFlyToMinVs.set(-500);
                    this._raFlyToMaxVs.set(100);
                }
                else {
                    // Preventative
                    this._raFlyToMinVs.set(null);
                    this._raFlyToMaxVs.set(null);
                }
            }
            else {
                // Composite RA
                const minVs = minVsFpm > -100 && minVsFpm < 100 ? -100 : minVsFpm;
                const maxVs = maxVsFpm > -100 && maxVsFpm < 100 ? 100 : maxVsFpm;
                this._raMinVs.set(minVs);
                this._raMaxVs.set(maxVs);
                if (host.primaryType === msfsSdk.TcasResolutionAdvisoryType.ReduceDescent) {
                    // Corrective upward negative primary
                    this._raFlyToMinVs.set(-100);
                    this._raFlyToMaxVs.set(Math.min(maxVs, 500));
                }
                else if (host.primaryType === msfsSdk.TcasResolutionAdvisoryType.ReduceClimb) {
                    // Corrective downward negative primary
                    this._raFlyToMinVs.set(Math.max(minVs, -500));
                    this._raFlyToMaxVs.set(100);
                }
                else {
                    // Preventative primary
                    this._raFlyToMinVs.set(null);
                    this._raFlyToMaxVs.set(null);
                }
            }
        }
        /**
         * Resumes this data provider. Once resumed, this data provider will continuously update its data until paused or
         * destroyed.
         * @throws Error if this data provider has been destroyed.
         */
        resume() {
            if (!this.isAlive) {
                throw new Error('DefaultTcasRaCommandDataProvider: cannot resume a dead provider');
            }
            if (!this.isPaused) {
                return;
            }
            this.isPaused = false;
            this.updateRaSpeeds();
            this.tcasRaSubs.forEach(sub => { sub.resume(); });
        }
        /**
         * Pauses this data provider. Once paused, this data provider will not update its data until it is resumed.
         * @throws Error if this data provider has been destroyed.
         */
        pause() {
            if (!this.isAlive) {
                throw new Error('DefaultTcasRaCommandDataProvider: cannot pause a dead provider');
            }
            if (this.isPaused) {
                return;
            }
            this.tcasRaSubs.forEach(sub => { sub.pause(); });
            this.isPaused = true;
        }
        /**
         * Destroys this data provider. Once destroyed, this data provider will no longer update its provided data, and can
         * no longer be paused or resumed.
         */
        destroy() {
            this.isAlive = false;
            this.tcasRaSubs.forEach(sub => { sub.destroy(); });
        }
    }

    /**
     * Garmin Traffic Advisory System (TAS).
     */
    class TrafficAdvisorySystem extends msfsSdk.Tcas {
        /**
         * Constructor.
         * @param bus The event bus.
         * @param tfcInstrument The traffic instrument which provides traffic contacts for this TAS.
         * @param adsb The ADS-B system associated with this TAS, or `null` if this TAS does not support ADS-B.
         * @param supportsRadarAltitude Whether this TAS supports radar altitude.
         * @param maxIntruderCount The maximum number of intruders tracked at any one time by this TAS. Defaults to
         * {@link TrafficAdvisorySystem.DEFAULT_MAX_INTRUDER_COUNT}.
         * @param realTimeUpdateFreq The maximum update frequency (Hz) in real time. Defaults to
         * {@link TrafficAdvisorySystem.DEFAULT_REAL_TIME_UPDATE_FREQ}.
         * @param simTimeUpdateFreq The maximum update frequency (Hz) in sim time. Defaults to
         * {@link TrafficAdvisorySystem.DEFAULT_SIM_TIME_UPDATE_FREQ}.
         */
        constructor(bus, tfcInstrument, adsb, supportsRadarAltitude, maxIntruderCount = TrafficAdvisorySystem.DEFAULT_MAX_INTRUDER_COUNT, realTimeUpdateFreq = TrafficAdvisorySystem.DEFAULT_REAL_TIME_UPDATE_FREQ, simTimeUpdateFreq = TrafficAdvisorySystem.DEFAULT_SIM_TIME_UPDATE_FREQ) {
            super(bus, tfcInstrument, maxIntruderCount, realTimeUpdateFreq, simTimeUpdateFreq);
            this.adsb = adsb;
            this.supportsRadarAltitude = supportsRadarAltitude;
            this.type = exports.TrafficSystemType.Tas;
            this._isPowered = msfsSdk.Subject.create(true);
            this.operatingModeSetting = TrafficUserSettings.getManager(this.bus).getSetting('trafficOperatingMode');
            this.operatingModeState = msfsSdk.MappedSubject.create(this._isPowered, this.operatingModeSetting);
            this.cdiScalingLabel = exports.CDIScaleLabel.Enroute;
        }
        /** @inheritdoc */
        createSensitivity() {
            return new TasSensitivity();
        }
        /** @inheritdoc */
        init() {
            var _a;
            super.init();
            this.bus.getSubscriber().on('lnavdata_cdi_scale_label').whenChanged().handle(label => { this.cdiScalingLabel = label; });
            this.operatingModeState.sub(([isPowered, operatingModeSetting]) => {
                if (!isPowered) {
                    this.operatingModeSub.set(msfsSdk.TcasOperatingMode.Off);
                }
                else {
                    switch (operatingModeSetting) {
                        case exports.TrafficOperatingModeSetting.Operating:
                        case exports.TrafficOperatingModeSetting.Auto:
                        case exports.TrafficOperatingModeSetting.TAOnly:
                            this.operatingModeSub.set(msfsSdk.TcasOperatingMode.TAOnly);
                            break;
                        default:
                            this.operatingModeSub.set(msfsSdk.TcasOperatingMode.Standby);
                    }
                }
            }, true);
            (_a = this.adsb) === null || _a === void 0 ? void 0 : _a.init();
        }
        /** @inheritdoc */
        isPowered() {
            return this._isPowered.get();
        }
        /** @inheritdoc */
        setPowered(isPowered) {
            this._isPowered.set(isPowered);
        }
        /** @inheritdoc */
        createIntruderEntry(contact) {
            return new GarminTcasIntruder(contact, this.simTime);
        }
        /** @inheritdoc */
        updateSensitivity() {
            if (this.adsb) {
                this.sensitivity.update(this.adsb.getOperatingMode(), this.ownAirplaneSubs.altitude.get(), this.ownAirplaneSubs.groundSpeed.get(), this.cdiScalingLabel, this.supportsRadarAltitude ? this.ownAirplaneSubs.radarAltitude.get() : undefined);
            }
            else {
                this.sensitivity.update(this.ownAirplaneSubs.groundSpeed.get(), this.supportsRadarAltitude ? this.ownAirplaneSubs.radarAltitude.get() : undefined);
            }
        }
        /** @inheritdoc */
        canIssueTrafficAdvisory(simTime, intruder) {
            if (this.ownAirplaneSubs.isOnGround.get()) {
                return false;
            }
            if (intruder.alertLevel.get() !== msfsSdk.TcasAlertLevel.TrafficAdvisory) {
                const dt = simTime - intruder.taOffTime;
                return dt < 0 || dt >= TrafficAdvisorySystem.TA_ON_HYSTERESIS;
            }
            return true;
        }
        /** @inheritdoc */
        canCancelTrafficAdvisory(simTime, intruder) {
            if (this.ownAirplaneSubs.isOnGround.get()) {
                return true;
            }
            const dt = simTime - intruder.taOnTime;
            return dt < 0 || dt >= TrafficAdvisorySystem.TA_OFF_HYSTERESIS;
        }
    }
    TrafficAdvisorySystem.DEFAULT_MAX_INTRUDER_COUNT = 30;
    TrafficAdvisorySystem.DEFAULT_REAL_TIME_UPDATE_FREQ = 2; // hz
    TrafficAdvisorySystem.DEFAULT_SIM_TIME_UPDATE_FREQ = 1; // hz
    TrafficAdvisorySystem.TA_ON_HYSTERESIS = 2000; // ms
    TrafficAdvisorySystem.TA_OFF_HYSTERESIS = 8000; // ms
    /**
     * Garmin TAS sensitivity settings.
     */
    class TasSensitivityParameters {
        /**
         * Selects a sensitivity level for a specified environment.
         * @param groundSpeed The ground speed of the own airplane.
         * @param radarAltitude The radar altitude of the own airplane.
         * @returns The sensitivity level for the specified environment.
         */
        selectLevel(groundSpeed, radarAltitude) {
            var _a;
            if (((_a = radarAltitude === null || radarAltitude === void 0 ? void 0 : radarAltitude.compare(2000, msfsSdk.UnitType.FOOT)) !== null && _a !== void 0 ? _a : 1) < 0 || groundSpeed.compare(120, msfsSdk.UnitType.KNOT) < 0) {
                return 0;
            }
            else {
                return 1;
            }
        }
        /**
         * Selects Proximity Advisory sensitivity settings for a specified environment.
         * @param groundSpeed The ground speed of the own airplane.
         * @param radarAltitude The radar altitude of the own airplane.
         * @returns Proximity Advisory sensitivity settings for the specified environment.
         */
        selectPA(
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        groundSpeed, 
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        radarAltitude) {
            return TasSensitivityParameters.PA;
        }
        /**
         * Selects Traffic Advisory sensitivity settings for a specified environment.
         * @param groundSpeed The ground speed of the own airplane.
         * @param radarAltitude The radar altitude of the own airplane.
         * @returns Traffic Advisory sensitivity settings for the specified environment.
         */
        selectTA(groundSpeed, radarAltitude) {
            return TasSensitivityParameters.TA_LEVELS[this.selectLevel(groundSpeed, radarAltitude)];
        }
        /**
         * Gets Proximity Advisory sensitivity parameters for a given sensitivity level.
         * @param level A sensitivity level.
         * @returns Proximity Advisory sensitivity parameters for the given sensitivity level.
         */
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        getPA(level) {
            return TasSensitivityParameters.PA;
        }
        /**
         * Gets Traffic Advisory sensitivity parameters for a given sensitivity level.
         * @param level A sensitivity level.
         * @returns Traffic Advisory sensitivity parameters for the given sensitivity level.
         */
        getTA(level) {
            return TasSensitivityParameters.TA_LEVELS[msfsSdk.MathUtils.clamp(level, 0, TasSensitivityParameters.TA_LEVELS.length - 1)];
        }
    }
    TasSensitivityParameters.PA = {
        protectedRadius: msfsSdk.UnitType.NMILE.createNumber(6),
        protectedHeight: msfsSdk.UnitType.FOOT.createNumber(1200)
    };
    TasSensitivityParameters.TA_LEVELS = [
        {
            tau: msfsSdk.UnitType.SECOND.createNumber(20),
            protectedRadius: msfsSdk.UnitType.NMILE.createNumber(0.2),
            protectedHeight: msfsSdk.UnitType.FOOT.createNumber(600)
        },
        {
            tau: msfsSdk.UnitType.SECOND.createNumber(30),
            protectedRadius: msfsSdk.UnitType.NMILE.createNumber(0.55),
            protectedHeight: msfsSdk.UnitType.FOOT.createNumber(800)
        }
    ];
    /**
     * An implementation of {@link TcasSensitivity} which provides sensitivity parameters for the Garmin Traffic Advisory
     * System (TAS). When ADS-B is operating, Traffic Advisory sensitivity is selected based on the ADS-B Conflict
     * Situational Awareness (CSA) algorithm. When ADS-B is not operating, Traffic Advisory sensitivity is selected based
     * on the TAS algorithm.
     */
    class TasSensitivity {
        constructor() {
            this.adsbTASensitivity = new AdsbSensitivityParameters();
            this.tasSensitivity = new TasSensitivityParameters();
            this.tasParams = {
                parametersPA: this.tasSensitivity.getPA(0),
                parametersTA: this.tasSensitivity.getTA(0),
                parametersRA: {
                    tau: msfsSdk.UnitType.SECOND.createNumber(NaN),
                    protectedRadius: msfsSdk.UnitType.NMILE.createNumber(NaN),
                    protectedHeight: msfsSdk.UnitType.FOOT.createNumber(NaN),
                    alim: msfsSdk.UnitType.FOOT.createNumber(NaN)
                }
            };
            this.adsbParams = {
                parametersPA: this.tasSensitivity.getPA(0),
                parametersTA: this.adsbTASensitivity.getTA(0),
                parametersRA: {
                    tau: msfsSdk.UnitType.SECOND.createNumber(NaN),
                    protectedRadius: msfsSdk.UnitType.NMILE.createNumber(NaN),
                    protectedHeight: msfsSdk.UnitType.FOOT.createNumber(NaN),
                    alim: msfsSdk.UnitType.FOOT.createNumber(NaN)
                }
            };
            this.activeParams = this.tasParams;
        }
        /** @inheritdoc */
        selectParameters() {
            return this.activeParams;
        }
        /** @inheritdoc */
        selectRAAlim() {
            return this.tasParams.parametersRA.alim;
        }
        // eslint-disable-next-line jsdoc/require-jsdoc
        update(arg1, arg2, arg3, arg4, arg5) {
            let groundSpeed;
            let radarAltitude;
            let supportAdsb;
            if (typeof arg1 === 'object') {
                groundSpeed = arg1;
                radarAltitude = arg2;
                supportAdsb = false;
            }
            else {
                groundSpeed = arg3;
                radarAltitude = arg5;
                supportAdsb = true;
            }
            const tisLevel = this.tasSensitivity.selectLevel(groundSpeed, radarAltitude);
            this.tasParams.parametersPA = this.tasSensitivity.getPA(tisLevel);
            this.tasParams.parametersTA = this.tasSensitivity.getTA(tisLevel);
            if (supportAdsb) {
                const adsbLevel = this.adsbTASensitivity.selectLevel(arg2, arg4, radarAltitude);
                this.adsbParams.parametersPA = this.tasSensitivity.getPA(tisLevel);
                this.adsbParams.parametersTA = this.adsbTASensitivity.getTA(adsbLevel);
                // Right now we just assume every intruder is tracked by ADS-B if ADS-B is operating
                this.activeParams = arg1 === msfsSdk.AdsbOperatingMode.Standby ? this.tasParams : this.adsbParams;
            }
            else {
                this.activeParams = this.tasParams;
            }
        }
    }

    /**
     * Garmin Traffic Information Service.
     */
    class TrafficInfoService extends msfsSdk.Tcas {
        // eslint-disable-next-line jsdoc/require-jsdoc
        constructor(bus, tfcInstrument, arg3, arg4, arg5, arg6) {
            var _a, _b, _c, _d, _e, _f, _g, _h, _j;
            let opts;
            let maxIntruderCount;
            let realTimeUpdateFreq;
            let simTimeUpdateFreq;
            if (arg3 === undefined || typeof arg3 === 'object') {
                opts = arg3;
                const noAdsbMaxIntruderCount = (_a = opts === null || opts === void 0 ? void 0 : opts.maxIntruderCount) !== null && _a !== void 0 ? _a : TrafficInfoService.DEFAULT_MAX_INTRUDER_COUNT;
                const noAdsbRealTimeUpdateFreq = (_b = opts === null || opts === void 0 ? void 0 : opts.realTimeUpdateFreq) !== null && _b !== void 0 ? _b : TrafficInfoService.DEFAULT_REAL_TIME_UPDATE_FREQ;
                const noAdsbSimTimeUpdateFreq = (_c = opts === null || opts === void 0 ? void 0 : opts.simTimeUpdateFreq) !== null && _c !== void 0 ? _c : TrafficInfoService.DEFAULT_SIM_TIME_UPDATE_FREQ;
                if (opts === null || opts === void 0 ? void 0 : opts.adsb) {
                    const adsbMaxIntruderCount = (_d = opts === null || opts === void 0 ? void 0 : opts.adsbMaxIntruderCount) !== null && _d !== void 0 ? _d : TrafficInfoService.DEFAULT_ADSB_MAX_INTRUDER_COUNT;
                    const adsbRealTimeUpdateFreq = (_e = opts === null || opts === void 0 ? void 0 : opts.adsbRealTimeUpdateFreq) !== null && _e !== void 0 ? _e : TrafficInfoService.DEFAULT_ADSB_REAL_TIME_UPDATE_FREQ;
                    const adsbSimTimeUpdateFreq = (_f = opts === null || opts === void 0 ? void 0 : opts.adsbSimTimeUpdateFreq) !== null && _f !== void 0 ? _f : TrafficInfoService.DEFAULT_ADSB_SIM_TIME_UPDATE_FREQ;
                    maxIntruderCount = msfsSdk.Subject.create(noAdsbMaxIntruderCount);
                    realTimeUpdateFreq = msfsSdk.Subject.create(noAdsbRealTimeUpdateFreq);
                    simTimeUpdateFreq = msfsSdk.Subject.create(noAdsbSimTimeUpdateFreq);
                    opts.adsb.getEventSubscriber().on('adsb_operating_mode').handle(mode => {
                        if (mode === msfsSdk.AdsbOperatingMode.Standby) {
                            maxIntruderCount.set(noAdsbMaxIntruderCount);
                            realTimeUpdateFreq.set(noAdsbRealTimeUpdateFreq);
                            simTimeUpdateFreq.set(noAdsbSimTimeUpdateFreq);
                        }
                        else {
                            maxIntruderCount.set(adsbMaxIntruderCount);
                            realTimeUpdateFreq.set(adsbRealTimeUpdateFreq);
                            simTimeUpdateFreq.set(adsbSimTimeUpdateFreq);
                        }
                    });
                }
                else {
                    maxIntruderCount = noAdsbMaxIntruderCount;
                    realTimeUpdateFreq = noAdsbRealTimeUpdateFreq;
                    simTimeUpdateFreq = noAdsbSimTimeUpdateFreq;
                }
            }
            else {
                maxIntruderCount = arg4 !== null && arg4 !== void 0 ? arg4 : TrafficInfoService.DEFAULT_MAX_INTRUDER_COUNT;
                realTimeUpdateFreq = arg5 !== null && arg5 !== void 0 ? arg5 : TrafficInfoService.DEFAULT_REAL_TIME_UPDATE_FREQ;
                simTimeUpdateFreq = arg6 !== null && arg6 !== void 0 ? arg6 : TrafficInfoService.DEFAULT_SIM_TIME_UPDATE_FREQ;
            }
            super(bus, tfcInstrument, maxIntruderCount, realTimeUpdateFreq, simTimeUpdateFreq);
            this.type = exports.TrafficSystemType.Tis;
            if (opts) {
                this.adsb = (_g = opts.adsb) !== null && _g !== void 0 ? _g : null;
                this.supportsRadarAltitude = (_h = opts.supportRadarAltitude) !== null && _h !== void 0 ? _h : false;
                this.supportsGpsFlightPhase = (_j = opts.supportGpsFlightPhase) !== null && _j !== void 0 ? _j : false;
            }
            else {
                this.adsb = null;
                this.supportsRadarAltitude = arg3;
                this.supportsGpsFlightPhase = false;
            }
            this._isPowered = msfsSdk.Subject.create(true);
            this.operatingModeSetting = TrafficUserSettings.getManager(this.bus).getSetting('trafficOperatingMode');
            this.operatingModeState = msfsSdk.MappedSubject.create(this._isPowered, this.operatingModeSetting);
        }
        /** @inheritdoc */
        createSensitivity() {
            return new TisSensitivity();
        }
        /** @inheritdoc */
        init() {
            var _a;
            super.init();
            if (this.adsb && this.supportsGpsFlightPhase) {
                this.bus.getSubscriber().on('lnavdata_cdi_scale_label').whenChanged().handle(label => { this.cdiScalingLabel = label; });
            }
            this.operatingModeState.sub(([isPowered, operatingModeSetting]) => {
                if (!isPowered) {
                    this.operatingModeSub.set(msfsSdk.TcasOperatingMode.Off);
                }
                else {
                    switch (operatingModeSetting) {
                        case exports.TrafficOperatingModeSetting.Operating:
                        case exports.TrafficOperatingModeSetting.Auto:
                        case exports.TrafficOperatingModeSetting.TAOnly:
                            this.operatingModeSub.set(msfsSdk.TcasOperatingMode.TAOnly);
                            break;
                        default:
                            this.operatingModeSub.set(msfsSdk.TcasOperatingMode.Standby);
                    }
                }
            }, true);
            (_a = this.adsb) === null || _a === void 0 ? void 0 : _a.init();
        }
        /** @inheritdoc */
        isPowered() {
            return this._isPowered.get();
        }
        /** @inheritdoc */
        setPowered(isPowered) {
            this._isPowered.set(isPowered);
        }
        /** @inheritdoc */
        createIntruderEntry(contact) {
            return new GarminTcasIntruder(contact, this.simTime);
        }
        /** @inheritdoc */
        filterIntruder(intruder) {
            if (this.adsb && this.adsb.getOperatingMode() !== msfsSdk.AdsbOperatingMode.Standby) {
                return true;
            }
            const relativePosVec = intruder.relativePositionVec;
            return TrafficInfoService.MAX_INTRUDER_ALTITUDE_BELOW.compare(-relativePosVec[2], msfsSdk.UnitType.METER) >= 0
                && TrafficInfoService.MAX_INTRUDER_ALTITUDE_ABOVE.compare(relativePosVec[2], msfsSdk.UnitType.METER) >= 0
                && TrafficInfoService.MAX_INTRUDER_DISTANCE.compare(msfsSdk.Vec2Math.abs(relativePosVec), msfsSdk.UnitType.METER) >= 0;
        }
        /** @inheritdoc */
        updateSensitivity() {
            if (this.adsb) {
                this.sensitivity.update(this.adsb.getOperatingMode(), this.ownAirplaneSubs.altitude.get(), this.ownAirplaneSubs.groundSpeed.get(), this.cdiScalingLabel, this.supportsRadarAltitude ? this.ownAirplaneSubs.radarAltitude.get() : undefined);
            }
            else {
                this.sensitivity.update(this.ownAirplaneSubs.groundSpeed.get(), this.supportsRadarAltitude ? this.ownAirplaneSubs.radarAltitude.get() : undefined);
            }
        }
        /** @inheritdoc */
        canIssueTrafficAdvisory(simTime, intruder) {
            if (this.ownAirplaneSubs.isOnGround.get()) {
                return false;
            }
            if (intruder.alertLevel.get() !== msfsSdk.TcasAlertLevel.TrafficAdvisory) {
                const dt = simTime - intruder.taOffTime;
                return dt < 0 || dt >= TrafficInfoService.TA_ON_HYSTERESIS;
            }
            return true;
        }
        /** @inheritdoc */
        canCancelTrafficAdvisory(simTime, intruder) {
            if (this.ownAirplaneSubs.isOnGround.get()) {
                return true;
            }
            const dt = simTime - intruder.taOnTime;
            return dt < 0 || dt >= TrafficInfoService.TA_OFF_HYSTERESIS;
        }
    }
    TrafficInfoService.DEFAULT_MAX_INTRUDER_COUNT = 8;
    TrafficInfoService.DEFAULT_REAL_TIME_UPDATE_FREQ = 2; // hz
    TrafficInfoService.DEFAULT_SIM_TIME_UPDATE_FREQ = 0.2; // hz
    TrafficInfoService.DEFAULT_ADSB_MAX_INTRUDER_COUNT = 30;
    TrafficInfoService.DEFAULT_ADSB_REAL_TIME_UPDATE_FREQ = 2; // hz
    TrafficInfoService.DEFAULT_ADSB_SIM_TIME_UPDATE_FREQ = 1; // hz
    TrafficInfoService.MAX_INTRUDER_ALTITUDE_BELOW = msfsSdk.UnitType.FOOT.createNumber(3000);
    TrafficInfoService.MAX_INTRUDER_ALTITUDE_ABOVE = msfsSdk.UnitType.FOOT.createNumber(3500);
    TrafficInfoService.MAX_INTRUDER_DISTANCE = msfsSdk.UnitType.NMILE.createNumber(7.5);
    TrafficInfoService.TA_ON_HYSTERESIS = 2000; // ms
    TrafficInfoService.TA_OFF_HYSTERESIS = 8000; // ms
    /**
     * Garmin TIS sensitivity settings.
     */
    class TisSensitivityParameters {
        /**
         * Selects a sensitivity level for a specified environment.
         * @param groundSpeed The ground speed of the own airplane.
         * @param radarAltitude The radar altitude of the own airplane.
         * @returns The sensitivity level for the specified environment.
         */
        selectLevel(groundSpeed, radarAltitude) {
            // TODO: I couldn't find any specific details on how TIS determines sensitivity levels, so for now this is
            // identical to the TAS algorithm.
            var _a;
            if (((_a = radarAltitude === null || radarAltitude === void 0 ? void 0 : radarAltitude.compare(2000, msfsSdk.UnitType.FOOT)) !== null && _a !== void 0 ? _a : 1) < 0 || groundSpeed.compare(120, msfsSdk.UnitType.KNOT) < 0) {
                return 0;
            }
            else {
                return 1;
            }
        }
        /**
         * Selects Proximity Advisory sensitivity settings for a specified environment.
         * @param groundSpeed The ground speed of the own airplane.
         * @param radarAltitude The radar altitude of the own airplane.
         * @returns Proximity Advisory sensitivity settings for the specified environment.
         */
        selectPA(
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        groundSpeed, 
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        radarAltitude) {
            return TisSensitivityParameters.PA;
        }
        /**
         * Selects Traffic Advisory sensitivity settings for a specified environment.
         * @param groundSpeed The ground speed of the own airplane.
         * @param radarAltitude The radar altitude of the own airplane.
         * @returns Traffic Advisory sensitivity settings for the specified environment.
         */
        selectTA(groundSpeed, radarAltitude) {
            return TisSensitivityParameters.TA_LEVELS[this.selectLevel(groundSpeed, radarAltitude)];
        }
        /**
         * Gets Proximity Advisory sensitivity parameters for a given sensitivity level.
         * @param level A sensitivity level.
         * @returns Proximity Advisory sensitivity parameters for the given sensitivity level.
         */
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        getPA(level) {
            return TisSensitivityParameters.PA;
        }
        /**
         * Gets Traffic Advisory sensitivity parameters for a given sensitivity level.
         * @param level A sensitivity level.
         * @returns Traffic Advisory sensitivity parameters for the given sensitivity level.
         */
        getTA(level) {
            return TisSensitivityParameters.TA_LEVELS[msfsSdk.MathUtils.clamp(level, 0, TisSensitivityParameters.TA_LEVELS.length - 1)];
        }
    }
    TisSensitivityParameters.PA = {
        protectedRadius: msfsSdk.UnitType.NMILE.createNumber(6),
        protectedHeight: msfsSdk.UnitType.FOOT.createNumber(1200)
    };
    TisSensitivityParameters.TA_LEVELS = [
        {
            tau: msfsSdk.UnitType.SECOND.createNumber(20),
            protectedRadius: msfsSdk.UnitType.NMILE.createNumber(0.2),
            protectedHeight: msfsSdk.UnitType.FOOT.createNumber(600)
        },
        {
            tau: msfsSdk.UnitType.SECOND.createNumber(30),
            protectedRadius: msfsSdk.UnitType.NMILE.createNumber(0.55),
            protectedHeight: msfsSdk.UnitType.FOOT.createNumber(800)
        }
    ];
    /**
     * An implementation of {@link TcasSensitivity} which provides sensitivity parameters for the Garmin Traffic
     * Information Service.
     */
    class TisSensitivity {
        constructor() {
            this.adsbTASensitivity = new AdsbSensitivityParameters();
            this.tisSensitivity = new TisSensitivityParameters();
            this.tisParams = {
                parametersPA: this.tisSensitivity.getPA(0),
                parametersTA: this.tisSensitivity.getTA(0),
                parametersRA: {
                    tau: msfsSdk.UnitType.SECOND.createNumber(NaN),
                    protectedRadius: msfsSdk.UnitType.NMILE.createNumber(NaN),
                    protectedHeight: msfsSdk.UnitType.FOOT.createNumber(NaN),
                    alim: msfsSdk.UnitType.FOOT.createNumber(NaN)
                }
            };
            this.adsbParams = {
                parametersPA: this.tisSensitivity.getPA(0),
                parametersTA: this.adsbTASensitivity.getTA(0),
                parametersRA: {
                    tau: msfsSdk.UnitType.SECOND.createNumber(NaN),
                    protectedRadius: msfsSdk.UnitType.NMILE.createNumber(NaN),
                    protectedHeight: msfsSdk.UnitType.FOOT.createNumber(NaN),
                    alim: msfsSdk.UnitType.FOOT.createNumber(NaN)
                }
            };
            this.activeParams = this.tisParams;
        }
        /** @inheritdoc */
        selectParameters() {
            return this.activeParams;
        }
        /** @inheritdoc */
        selectRAAlim() {
            return this.tisParams.parametersRA.alim;
        }
        // eslint-disable-next-line jsdoc/require-jsdoc
        update(arg1, arg2, arg3, arg4, arg5) {
            let groundSpeed;
            let radarAltitude;
            let supportAdsb;
            if (typeof arg1 === 'object') {
                groundSpeed = arg1;
                radarAltitude = arg2;
                supportAdsb = false;
            }
            else {
                groundSpeed = arg3;
                radarAltitude = arg5;
                supportAdsb = true;
            }
            const tisLevel = this.tisSensitivity.selectLevel(groundSpeed, radarAltitude);
            this.tisParams.parametersPA = this.tisSensitivity.getPA(tisLevel);
            this.tisParams.parametersTA = this.tisSensitivity.getTA(tisLevel);
            if (supportAdsb) {
                const adsbLevel = this.adsbTASensitivity.selectLevel(arg2, arg4, radarAltitude);
                this.adsbParams.parametersPA = this.tisSensitivity.getPA(tisLevel);
                this.adsbParams.parametersTA = this.adsbTASensitivity.getTA(adsbLevel);
                // Right now we just assume every intruder is tracked by ADS-B if ADS-B is operating
                this.activeParams = arg1 === msfsSdk.AdsbOperatingMode.Standby ? this.tisParams : this.adsbParams;
            }
            else {
                this.activeParams = this.tisParams;
            }
        }
    }

    /**
     * A manager for automatically changing traffic system operating mode on takeoff and landing.
     */
    class TrafficOperatingModeManager {
        /**
         * Constructor.
         * @param bus The event bus.
         * @param takeoffDelay The delay, in seconds, after takeoff before this manager switches the traffic system to its
         * operating mode. Defaults to {@link TrafficOperatingModeManager.DEFAULT_TAKEOFF_DELAY}.
         * @param landingDelay The delay, in seconds, after landing before this manager switches the traffic system to its
         * standby mode. Defaults to {@link TrafficOperatingModeManager.DEFAULT_LANDING_DELAY}.
         * @param operatingMode The traffic system operating mode that is automatically set by this manager after takeoff.
         * Defaults to {@link TrafficOperatingModeSetting.Operating}.
         */
        constructor(bus, takeoffDelay = TrafficOperatingModeManager.DEFAULT_TAKEOFF_DELAY, landingDelay = TrafficOperatingModeManager.DEFAULT_LANDING_DELAY, operatingMode = exports.TrafficOperatingModeSetting.Operating) {
            this.bus = bus;
            this.takeoffDelay = takeoffDelay;
            this.landingDelay = landingDelay;
            this.operatingMode = operatingMode;
            this.trafficSettingManager = TrafficUserSettings.getManager(this.bus);
            this.operatingModeSetting = this.trafficSettingManager.getSetting('trafficOperatingMode');
            this.isOnGround = msfsSdk.ConsumerSubject.create(null, undefined);
            this.hasFirstOnGroundValue = false;
            this.operatingArmed = false;
            this.standbyArmed = false;
            this.operatingModeChangeTimer = new msfsSdk.DebounceTimer();
            this.isAlive = true;
            this.isInit = false;
            this.isPaused = false;
        }
        /**
         * Initializes this manager. Once this manager is initialized, it will automatically change traffic system operating
         * mode on takeoff and landing. Additionally, at the time of initialization, the traffic system will be set to
         * operate if the airplane is already in the air.
         * @param paused Whether to initialize this manager as paused.
         * @throws Error if this manager has been destroyed.
         */
        init(paused = false) {
            if (!this.isAlive) {
                throw new Error('TrafficOperatingModeManager: cannot initialize a dead manager');
            }
            if (this.isInit) {
                return;
            }
            this.isInit = true;
            this.isPaused = paused;
            this.operatingModeSub = this.operatingModeSetting.sub(this.cancelOperatingModeChange.bind(this), false, paused);
            this.isOnGround.setConsumer(this.bus.getSubscriber().on('on_ground'));
            this.isOnGroundSub = this.isOnGround.sub(this.onGroundChanged.bind(this), !paused, paused);
        }
        /**
         * A callback which is called when whether own airplane is on the ground changes.
         * @param isOnGround Whether own airplane is on the ground.
         */
        onGroundChanged(isOnGround) {
            if (isOnGround === undefined) {
                return;
            }
            if (!this.hasFirstOnGroundValue) {
                this.hasFirstOnGroundValue = true;
                if (!isOnGround) {
                    this.operatingArmed = false;
                    this.standbyArmed = true;
                    this.operatingModeSetting.value = this.operatingMode;
                }
                else {
                    this.operatingArmed = true;
                    this.standbyArmed = false;
                }
                return;
            }
            this.cancelOperatingModeChange();
            if (isOnGround) {
                if (this.standbyArmed && this.operatingModeSetting.value !== exports.TrafficOperatingModeSetting.Standby) {
                    this.scheduleOperatingModeChange(exports.TrafficOperatingModeSetting.Standby, this.landingDelay * 1000);
                }
                this.operatingArmed = true;
                this.standbyArmed = false;
            }
            else {
                if (this.operatingArmed && this.operatingModeSetting.value === exports.TrafficOperatingModeSetting.Standby) {
                    this.scheduleOperatingModeChange(this.operatingMode, this.takeoffDelay * 1000);
                }
                this.operatingArmed = false;
                this.standbyArmed = true;
            }
        }
        /**
         * Schedules a delayed operating mode change.
         * @param toMode The target operating mode.
         * @param delay The delay, in milliseconds.
         */
        scheduleOperatingModeChange(toMode, delay) {
            this.operatingModeChangeTimer.schedule(() => {
                this.operatingModeSetting.value = toMode;
            }, delay);
        }
        /**
         * Cancels the currently scheduled operating mode change, if one exists.
         */
        cancelOperatingModeChange() {
            this.operatingModeChangeTimer.clear();
        }
        /**
         * Resumes this manager. Once this manager is resumed, it will automatically change traffic system operating
         * mode on takeoff and landing.
         * @throws Error if this manager has been destroyed.
         */
        resume() {
            var _a, _b;
            if (!this.isAlive) {
                throw new Error('TrafficOperatingModeManager: cannot resume a dead manager');
            }
            if (!this.isInit || !this.isPaused) {
                return;
            }
            this.isPaused = false;
            (_a = this.operatingModeSub) === null || _a === void 0 ? void 0 : _a.resume();
            (_b = this.isOnGroundSub) === null || _b === void 0 ? void 0 : _b.resume(true);
        }
        /**
         * Pauses this manager. Once this manager is paused, it will no longer automatically change traffic system operating
         * mode on takeoff and landing until resumed.
         * @throws Error if this manager has been destroyed.
         */
        pause() {
            var _a, _b;
            if (!this.isAlive) {
                throw new Error('TrafficOperatingModeManager: cannot pause a dead manager');
            }
            if (!this.isInit || this.isPaused) {
                return;
            }
            this.isPaused = true;
            this.cancelOperatingModeChange();
            (_a = this.operatingModeSub) === null || _a === void 0 ? void 0 : _a.pause();
            (_b = this.isOnGroundSub) === null || _b === void 0 ? void 0 : _b.pause();
        }
        /**
         * Resets this manager. Sets the traffic systems operating mode to standby and arms (but does not schedule) the
         * transition to operating or standby modes based on whether the airplane is currently on the ground or in the air.
         * Has no effect if this manager is not initialized.
         * @throws Error if this manager has been destroyed.
         */
        reset() {
            if (!this.isAlive) {
                throw new Error('TrafficOperatingModeManager: cannot reset a dead manager');
            }
            if (!this.isInit) {
                return;
            }
            const isOnGround = this.isOnGround.get();
            if (isOnGround !== undefined) {
                if (isOnGround) {
                    this.operatingArmed = true;
                    this.standbyArmed = false;
                }
                else {
                    this.operatingArmed = false;
                    this.standbyArmed = true;
                }
            }
            this.operatingModeSetting.value = exports.TrafficOperatingModeSetting.Standby;
        }
        /**
         * Destroys this manager.
         */
        destroy() {
            var _a;
            this.isAlive = false;
            this.cancelOperatingModeChange();
            this.isOnGround.destroy();
            (_a = this.operatingModeSub) === null || _a === void 0 ? void 0 : _a.destroy();
        }
    }
    TrafficOperatingModeManager.DEFAULT_TAKEOFF_DELAY = 8; // seconds
    TrafficOperatingModeManager.DEFAULT_LANDING_DELAY = 24; // seconds

    /**
     * A default implementation of {@link WindDataProvider}.
     */
    class DefaultWindDataProvider {
        /**
         * Constructor.
         * @param bus The event bus.
         * @param adcIndex The index of the ADC that is the source of this provider's data.
         * @param ahrsIndex The index of the AHRS that is the source of this provider's data.
         * @param fmsPosIndex The index of the FMS geo-positioning system that is the source of this provider's data.
         * @param smoothingTau The smoothing time constant, in milliseconds, used to apply smoothing to wind speed and
         * direction. Defaults to {@link DefaultWindDataProvider.DEFAULT_SMOOTHING_TAU}.
         * @param accumulateTime The time required for this provider to accumulate enough source data to generate valid wind
         * data.
         */
        constructor(bus, adcIndex, ahrsIndex, fmsPosIndex, smoothingTau = DefaultWindDataProvider.DEFAULT_SMOOTHING_TAU, accumulateTime = DefaultWindDataProvider.DEFAULT_ACCUMULATE_TIME) {
            this.bus = bus;
            this.smoothingTau = smoothingTau;
            this.accumulateTime = accumulateTime;
            this._windDirection = msfsSdk.Subject.create(0);
            /** @inheritdoc */
            this.windDirection = this._windDirection;
            this._windDirectionRelative = msfsSdk.Subject.create(0);
            /** @inheritdoc */
            this.windDirectionRelative = this._windDirectionRelative;
            this._windSpeed = msfsSdk.Subject.create(0);
            /** @inheritdoc */
            this.windSpeed = this._windSpeed;
            this._headwind = msfsSdk.Subject.create(0);
            /** @inheritdoc */
            this.headwind = this._headwind;
            this._crosswind = msfsSdk.Subject.create(0);
            /** @inheritdoc */
            this.crosswind = this._crosswind;
            this._magVar = msfsSdk.ConsumerSubject.create(null, 0).pause();
            /** @inheritdoc */
            this.magVar = this._magVar;
            this._isGpsDeadReckoning = msfsSdk.Subject.create(false);
            /** @inheritdoc */
            this.isGpsDeadReckoning = this._isGpsDeadReckoning;
            this._isDataFailed = msfsSdk.Subject.create(false);
            /** @inheritdoc */
            this.isDataFailed = this._isDataFailed;
            this.windDirectionSource = msfsSdk.ConsumerValue.create(null, 0).pause();
            this.windSpeedSource = msfsSdk.ConsumerValue.create(null, 0).pause();
            this.isOnGround = msfsSdk.ConsumerValue.create(null, false).pause();
            this.tas = msfsSdk.ConsumerValue.create(null, 0).pause();
            this.headingTrue = msfsSdk.ConsumerValue.create(null, 0).pause();
            this.adcSystemState = msfsSdk.ConsumerSubject.create(null, { previous: undefined, current: undefined }).pause();
            this.isAdcDataValid = this.adcSystemState.map(state => state.current === undefined || state.current === msfsSdk.AvionicsSystemState.On);
            this.isHeadingDataValid = msfsSdk.ConsumerValue.create(null, false).pause();
            this.fmsPosMode = msfsSdk.ConsumerValue.create(null, exports.FmsPositionMode.None).pause();
            this.directionSmoother = new msfsSdk.ExpSmoother(this.smoothingTau);
            this.speedSmoother = new msfsSdk.ExpSmoother(this.smoothingTau);
            this.lastUpdateTime = 0;
            this.lastDeadTime = 0;
            this.isInit = false;
            this.isAlive = true;
            this.isPaused = true;
            this.pauseable = [
                this.windDirectionSource,
                this.windSpeedSource,
                this._magVar,
                this.isOnGround,
                this.tas,
                this.headingTrue,
                this.adcSystemState,
                this.isHeadingDataValid,
                this.fmsPosMode,
            ];
            this.adcIndex = msfsSdk.SubscribableUtils.toSubscribable(adcIndex, true);
            this.ahrsIndex = msfsSdk.SubscribableUtils.toSubscribable(ahrsIndex, true);
            this.fmsPosIndex = msfsSdk.SubscribableUtils.toSubscribable(fmsPosIndex, true);
        }
        /**
         * Initializes this data provider. Once initialized, this data provider will continuously update its data until
         * paused or destroyed.
         * @param paused Whether to initialize this data provider as paused. If `true`, this data provider will provide an
         * initial set of data but will not update the provided data until it is resumed. Defaults to `false`.
         * @throws Error if this data provider is dead.
         */
        init(paused = false) {
            if (!this.isAlive) {
                throw new Error('DefaultWindDataProvider: cannot initialize a dead provider');
            }
            if (this.isInit) {
                return;
            }
            this.isInit = true;
            const sub = this.bus.getSubscriber();
            this.windDirectionSource.setConsumer(sub.on('ambient_wind_direction'));
            this.windSpeedSource.setConsumer(sub.on('ambient_wind_velocity'));
            this._magVar.setConsumer(sub.on('magvar'));
            this.isOnGround.setConsumer(sub.on('on_ground'));
            this.adcIndexSub = this.adcIndex.sub(index => {
                this.tas.setConsumer(sub.on(`adc_tas_${index}`));
                this.adcSystemState.setConsumer(sub.on(`adc_state_${index}`));
            }, true);
            this.ahrsIndexSub = this.ahrsIndex.sub(index => {
                this.headingTrue.setConsumer(sub.on(`ahrs_hdg_deg_true_${index}`));
                this.isHeadingDataValid.setConsumer(sub.on(`ahrs_heading_data_valid_${index}`));
            }, true);
            this.fmsPosIndexSub = this.fmsPosIndex.sub(index => {
                this.fmsPosMode.setConsumer(sub.on(`fms_pos_mode_${index}`));
            }, true);
            this.clockSub = sub.on('simTime').handle(this.update.bind(this));
            if (!paused) {
                this.resume();
            }
        }
        /**
         * Resumes this data provider. Once resumed, this data provider will continuously update its data until paused or
         * destroyed.
         * @throws Error if this data provider is dead.
         */
        resume() {
            var _a;
            if (!this.isAlive) {
                throw new Error('DefaultWindDataProvider: cannot resume a dead provider');
            }
            if (!this.isInit || !this.isPaused) {
                return;
            }
            this.isPaused = false;
            for (const pauseable of this.pauseable) {
                pauseable.resume();
            }
            (_a = this.clockSub) === null || _a === void 0 ? void 0 : _a.resume(true);
        }
        /**
         * Pauses this data provider. Once paused, this data provider will not update its data until it is resumed.
         * @throws Error if this data provider is dead.
         */
        pause() {
            var _a;
            if (!this.isAlive) {
                throw new Error('DefaultWindDataProvider: cannot pause a dead provider');
            }
            if (!this.isInit || this.isPaused) {
                return;
            }
            this.isPaused = true;
            for (const pauseable of this.pauseable) {
                pauseable.pause();
            }
            (_a = this.clockSub) === null || _a === void 0 ? void 0 : _a.pause();
            this.directionSmoother.reset();
            this.speedSmoother.reset();
        }
        /**
         * Updates this data provider.
         * @param simTime The current simulation time, as a UNIX timestamp in milliseconds.
         */
        update(simTime) {
            const fmsPosMode = this.fmsPosMode.get();
            const isDead = this.isOnGround.get()
                || this.tas.get() < DefaultWindDataProvider.MIN_TAS
                || !this.isAdcDataValid.get()
                || !this.isHeadingDataValid.get()
                || fmsPosMode === exports.FmsPositionMode.None;
            const dt = Math.max(0, simTime - this.lastUpdateTime);
            this.lastUpdateTime = simTime;
            if (isDead) {
                this.lastDeadTime = simTime;
            }
            else {
                this.lastDeadTime = Math.min(this.lastDeadTime, simTime);
            }
            this._isGpsDeadReckoning.set(fmsPosMode === exports.FmsPositionMode.DeadReckoning || fmsPosMode === exports.FmsPositionMode.DeadReckoningExpired);
            this._isDataFailed.set(isDead || simTime - this.lastDeadTime < this.accumulateTime);
            let windDirection = this.windDirectionSource.get();
            const windSpeed = this.windSpeedSource.get();
            const lastWindDirection = this.directionSmoother.last();
            if (lastWindDirection !== null) {
                // need to handle wraparounds
                windDirection = lastWindDirection + msfsSdk.NavMath.diffAngle(lastWindDirection, windDirection);
            }
            // Keep the smoothed direction in the range [0, 360).
            const windDirectionSmoothed = this.directionSmoother.reset(msfsSdk.NavMath.normalizeHeading(this.directionSmoother.next(windDirection, dt)));
            const windSpeedSmoothed = this.speedSmoother.next(windSpeed, dt);
            const headingTrue = this.headingTrue.get();
            const relativeDirection = (windDirectionSmoothed - headingTrue + 360) % 360;
            const relativeDirectionRad = relativeDirection * Avionics.Utils.DEG2RAD;
            const headwind = windSpeedSmoothed * Math.cos(relativeDirectionRad);
            const crosswind = windSpeedSmoothed * Math.sin(relativeDirectionRad);
            this._windDirection.set(windDirectionSmoothed);
            this._windSpeed.set(windSpeedSmoothed);
            this._windDirectionRelative.set(relativeDirection);
            this._headwind.set(headwind);
            this._crosswind.set(crosswind);
        }
        /**
         * Destroys this data provider. Once destroyed, this data provider will no longer update its provided data, and can
         * no longer be paused or resumed.
         */
        destroy() {
            var _a, _b, _c, _d;
            this.isAlive = false;
            for (const pauseable of this.pauseable) {
                pauseable.destroy();
            }
            (_a = this.adcIndexSub) === null || _a === void 0 ? void 0 : _a.destroy();
            (_b = this.ahrsIndexSub) === null || _b === void 0 ? void 0 : _b.destroy();
            (_c = this.fmsPosIndexSub) === null || _c === void 0 ? void 0 : _c.destroy();
            (_d = this.clockSub) === null || _d === void 0 ? void 0 : _d.destroy();
        }
    }
    DefaultWindDataProvider.DEFAULT_SMOOTHING_TAU = 3000 / Math.LN2; // milliseconds
    DefaultWindDataProvider.DEFAULT_ACCUMULATE_TIME = 3000; // milliseconds
    /** The minimum true airspeed, in knots, required to provide valid wind data. */
    DefaultWindDataProvider.MIN_TAS = 30;

    exports.APExternalGuidanceProvider = APExternalGuidanceProvider;
    exports.AbstractFlightPlanLegWaypointsRecord = AbstractFlightPlanLegWaypointsRecord;
    exports.AbstractNavReferenceBase = AbstractNavReferenceBase;
    exports.AbstractTerrainSystem = AbstractTerrainSystem;
    exports.AdcSystem = AdcSystem;
    exports.AdcSystemSelector = AdcSystemSelector;
    exports.AdfRadioNavSource = AdfRadioNavSource;
    exports.AdsbSensitivityParameters = AdsbSensitivityParameters;
    exports.AhrsSystem = AhrsSystem;
    exports.AhrsSystemSelector = AhrsSystemSelector;
    exports.AirportWaypoint = AirportWaypoint;
    exports.AirspeedIndicator = AirspeedIndicator;
    exports.Altimeter = Altimeter;
    exports.AltitudeAlerter = AltitudeAlerter;
    exports.AoaIndicator = AoaIndicator;
    exports.AoaSystem = AoaSystem;
    exports.ApproachNameDisplay = ApproachNameDisplay;
    exports.ArtificialHorizon = ArtificialHorizon;
    exports.AttitudeAircraftSymbol = AttitudeAircraftSymbol;
    exports.BasicNavReferenceIndicator = BasicNavReferenceIndicator;
    exports.BearingDisplay = BearingDisplay;
    exports.BgImgTouchButton = BgImgTouchButton;
    exports.CASDisplay = CASDisplay;
    exports.CdiScaleFormatter = CdiScaleFormatter;
    exports.ComRadioSpacingManager = ComRadioSpacingManager;
    exports.ComRadioUserSettings = ComRadioUserSettings;
    exports.DateTimeUserSettings = DateTimeUserSettings;
    exports.DefaultAirspeedIndicatorDataProvider = DefaultAirspeedIndicatorDataProvider;
    exports.DefaultAltimeterDataProvider = DefaultAltimeterDataProvider;
    exports.DefaultAoaDataProvider = DefaultAoaDataProvider;
    exports.DefaultBaseFlightPathPlanRenderer = DefaultBaseFlightPathPlanRenderer;
    exports.DefaultFlightDirectorDataProvider = DefaultFlightDirectorDataProvider;
    exports.DefaultFlightPathPlanRenderer = DefaultFlightPathPlanRenderer;
    exports.DefaultFullFlightPathPlanRenderer = DefaultFullFlightPathPlanRenderer;
    exports.DefaultGpsIntegrityDataProvider = DefaultGpsIntegrityDataProvider;
    exports.DefaultMapRunwayDesignationImageCache = DefaultMapRunwayDesignationImageCache;
    exports.DefaultMarkerBeaconDataProvider = DefaultMarkerBeaconDataProvider;
    exports.DefaultMinimumsDataProvider = DefaultMinimumsDataProvider;
    exports.DefaultNavDataBarFieldModelFactory = DefaultNavDataBarFieldModelFactory;
    exports.DefaultNavStatusBoxDataProvider = DefaultNavStatusBoxDataProvider;
    exports.DefaultObsSuspDataProvider = DefaultObsSuspDataProvider;
    exports.DefaultRadarAltimeterDataProvider = DefaultRadarAltimeterDataProvider;
    exports.DefaultTcasRaCommandDataProvider = DefaultTcasRaCommandDataProvider;
    exports.DefaultTerrainSystemDataProvider = DefaultTerrainSystemDataProvider;
    exports.DefaultTerrainSystemStateDataProvider = DefaultTerrainSystemStateDataProvider;
    exports.DefaultUnitsUserSettingManager = DefaultUnitsUserSettingManager;
    exports.DefaultVNavDataProvider = DefaultVNavDataProvider;
    exports.DefaultVSpeedAnnunciationDataProvider = DefaultVSpeedAnnunciationDataProvider;
    exports.DefaultVsiDataProvider = DefaultVsiDataProvider;
    exports.DefaultWaypointIconImageCache = DefaultWaypointIconImageCache;
    exports.DefaultWindDataProvider = DefaultWindDataProvider;
    exports.DmeUserSettings = DmeUserSettings;
    exports.DynamicList = DynamicList;
    exports.EventBusNavDataBarFieldTypeModelFactory = EventBusNavDataBarFieldTypeModelFactory;
    exports.FailureBox = FailureBox;
    exports.FixIcaoWaypointsRecord = FixIcaoWaypointsRecord;
    exports.FlightDirectorDualCue = FlightDirectorDualCue;
    exports.FlightDirectorSingleCue = FlightDirectorSingleCue;
    exports.FlightPathCalculatorManager = FlightPathCalculatorManager;
    exports.FlightPathMarker = FlightPathMarker;
    exports.FlightPathTerminatorWaypointsRecord = FlightPathTerminatorWaypointsRecord;
    exports.FlightPlanSimSyncManager = FlightPlanSimSyncManager;
    exports.FmaMasterSlot = FmaMasterSlot;
    exports.FmaModeSlot = FmaModeSlot;
    exports.Fms = Fms;
    exports.FmsPositionSystem = FmsPositionSystem;
    exports.FmsPositionSystemSelector = FmsPositionSystemSelector;
    exports.FmsUtils = FmsUtils;
    exports.GarminAPConfig = GarminAPConfig;
    exports.GarminAPStateManager = GarminAPStateManager;
    exports.GarminAPUtils = GarminAPUtils;
    exports.GarminAdsb = GarminAdsb;
    exports.GarminAirspaceShowTypeMap = GarminAirspaceShowTypeMap;
    exports.GarminAutopilot = GarminAutopilot;
    exports.GarminExcessiveClosureRateModule = GarminExcessiveClosureRateModule;
    exports.GarminExcessiveDescentRateModule = GarminExcessiveDescentRateModule;
    exports.GarminFacilityWaypointCache = GarminFacilityWaypointCache;
    exports.GarminGlidepathComputer = GarminGlidepathComputer;
    exports.GarminGoAroundManager = GarminGoAroundManager;
    exports.GarminHeadingSyncManager = GarminHeadingSyncManager;
    exports.GarminLowBankManager = GarminLowBankManager;
    exports.GarminMapBuilder = GarminMapBuilder;
    exports.GarminMapKeys = GarminMapKeys;
    exports.GarminNavSimVarPublisher = GarminNavSimVarPublisher;
    exports.GarminNavToNavComputer = GarminNavToNavComputer;
    exports.GarminNavToNavManager = GarminNavToNavManager;
    exports.GarminNavToNavManager2 = GarminNavToNavManager2;
    exports.GarminNegativeClimbRateModule = GarminNegativeClimbRateModule;
    exports.GarminObsDirector = GarminObsDirector;
    exports.GarminObsLNavModule = GarminObsLNavModule;
    exports.GarminPrematureDescentModule = GarminPrematureDescentModule;
    exports.GarminSpeedConstraintStore = GarminSpeedConstraintStore;
    exports.GarminTaws = GarminTaws;
    exports.GarminTcasII = GarminTcasII;
    exports.GarminTcasIISensitivity = GarminTcasIISensitivity;
    exports.GarminTcasIntruder = GarminTcasIntruder;
    exports.GarminTimerManager = GarminTimerManager;
    exports.GarminVNavComputer = GarminVNavComputer;
    exports.GarminVNavManager = GarminVNavManager;
    exports.GarminVNavManager2 = GarminVNavManager2;
    exports.GarminVNavUtils = GarminVNavUtils;
    exports.GarminVoiceCalloutModule = GarminVoiceCalloutModule;
    exports.GarminXpdrTcasManager = GarminXpdrTcasManager;
    exports.GenericNavDataBarFieldModelFactory = GenericNavDataBarFieldModelFactory;
    exports.GenericNavDataFieldRenderer = GenericNavDataFieldRenderer;
    exports.GlidepathServiceLevelCalculator = GlidepathServiceLevelCalculator;
    exports.GpsNavSource = GpsNavSource;
    exports.GpsReceiverSelector = GpsReceiverSelector;
    exports.GpsReceiverSystem = GpsReceiverSystem;
    exports.HorizonDisplay = HorizonDisplay;
    exports.HorizonLine = HorizonLine;
    exports.HsiCompass = HsiCompass;
    exports.HsiGpsIntegrityAnnunciation = HsiGpsIntegrityAnnunciation;
    exports.HsiSource = HsiSource;
    exports.ImgTouchButton = ImgTouchButton;
    exports.LNavDataSimVarPublisher = LNavDataSimVarPublisher;
    exports.LatLonDisplay = LatLonDisplay;
    exports.MagVarDisplay = MagVarDisplay;
    exports.MagnetometerSystem = MagnetometerSystem;
    exports.MapActiveFlightPlanDataProvider = MapActiveFlightPlanDataProvider;
    exports.MapAirportIcon = MapAirportIcon;
    exports.MapAirspaceRendering = MapAirspaceRendering;
    exports.MapAirspaceVisController = MapAirspaceVisController;
    exports.MapBannerIndicator = MapBannerIndicator;
    exports.MapCrosshairLayer = MapCrosshairLayer;
    exports.MapCrosshairModule = MapCrosshairModule;
    exports.MapDataIntegrityRTRController = MapDataIntegrityRTRController;
    exports.MapDeadReckoningLayer = MapDeadReckoningLayer;
    exports.MapDeclutterModule = MapDeclutterModule;
    exports.MapDesiredOrientationController = MapDesiredOrientationController;
    exports.MapDetailIndicator = MapDetailIndicator;
    exports.MapFlightPathStyles = MapFlightPathStyles;
    exports.MapFlightPlanFocusCalculator = MapFlightPlanFocusCalculator;
    exports.MapFlightPlanFocusModule = MapFlightPlanFocusModule;
    exports.MapFlightPlanFocusRTRController = MapFlightPlanFocusRTRController;
    exports.MapFlightPlanLayer = MapFlightPlanLayer;
    exports.MapFlightPlanWaypointRecordManager = MapFlightPlanWaypointRecordManager;
    exports.MapFlightPlannerPlanDataProvider = MapFlightPlannerPlanDataProvider;
    exports.MapGarminAutopilotPropsController = MapGarminAutopilotPropsController;
    exports.MapGarminAutopilotPropsModule = MapGarminAutopilotPropsModule;
    exports.MapGarminDataIntegrityModule = MapGarminDataIntegrityModule;
    exports.MapGarminTrafficController = MapGarminTrafficController;
    exports.MapGarminTrafficModule = MapGarminTrafficModule;
    exports.MapMiniCompassLayer = MapMiniCompassLayer;
    exports.MapNexradController = MapNexradController;
    exports.MapNexradModule = MapNexradModule;
    exports.MapOrientationController = MapOrientationController;
    exports.MapOrientationIndicator = MapOrientationIndicator;
    exports.MapOrientationModeController = MapOrientationModeController;
    exports.MapOrientationModule = MapOrientationModule;
    exports.MapOrientationRTRController = MapOrientationRTRController;
    exports.MapOrientationSettingsController = MapOrientationSettingsController;
    exports.MapPanningModule = MapPanningModule;
    exports.MapPanningRTRController = MapPanningRTRController;
    exports.MapPointerController = MapPointerController;
    exports.MapPointerInfoLayer = MapPointerInfoLayer;
    exports.MapPointerLayer = MapPointerLayer;
    exports.MapPointerModule = MapPointerModule;
    exports.MapPointerRTRController = MapPointerRTRController;
    exports.MapProcedurePreviewLayer = MapProcedurePreviewLayer;
    exports.MapProcedurePreviewModule = MapProcedurePreviewModule;
    exports.MapRangeCompassController = MapRangeCompassController;
    exports.MapRangeCompassLayer = MapRangeCompassLayer;
    exports.MapRangeCompassModule = MapRangeCompassModule;
    exports.MapRangeController = MapRangeController;
    exports.MapRangeDisplay = MapRangeDisplay;
    exports.MapRangeRTRController = MapRangeRTRController;
    exports.MapRangeRingLayer = MapRangeRingLayer;
    exports.MapRangeRingModule = MapRangeRingModule;
    exports.MapRangeValueDisplay = MapRangeValueDisplay;
    exports.MapRelativeTerrainStatusIndicator = MapRelativeTerrainStatusIndicator;
    exports.MapResourcePriority = MapResourcePriority;
    exports.MapRunwayLabelWaypoint = MapRunwayLabelWaypoint;
    exports.MapRunwayLabelWaypointCache = MapRunwayLabelWaypointCache;
    exports.MapRunwayOutlineIcon = MapRunwayOutlineIcon;
    exports.MapRunwayOutlineWaypoint = MapRunwayOutlineWaypoint;
    exports.MapRunwayOutlineWaypointCache = MapRunwayOutlineWaypointCache;
    exports.MapStandaloneFlightPlanPlanDataProvider = MapStandaloneFlightPlanPlanDataProvider;
    exports.MapSymbolVisController = MapSymbolVisController;
    exports.MapTerrainColorsController = MapTerrainColorsController;
    exports.MapTerrainController = MapTerrainController;
    exports.MapTerrainModule = MapTerrainModule;
    exports.MapTerrainScaleIndicator = MapTerrainScaleIndicator;
    exports.MapTerrainWxSettingCompatManager = MapTerrainWxSettingCompatManager;
    exports.MapTrackVectorLayer = MapTrackVectorLayer;
    exports.MapTrackVectorModule = MapTrackVectorModule;
    exports.MapTrafficController = MapTrafficController;
    exports.MapTrafficFailedIndicator = MapTrafficFailedIndicator;
    exports.MapTrafficIntruderIcon = MapTrafficIntruderIcon;
    exports.MapTrafficOffScaleIndicator = MapTrafficOffScaleIndicator;
    exports.MapTrafficStatusIndicator = MapTrafficStatusIndicator;
    exports.MapUnitsModule = MapUnitsModule;
    exports.MapUserSettingsUtils = MapUserSettingsUtils;
    exports.MapUtils = MapUtils;
    exports.MapWaypointDisplayBuilderClass = MapWaypointDisplayBuilderClass;
    exports.MapWaypointHighlightIcon = MapWaypointHighlightIcon;
    exports.MapWaypointHighlightLayer = MapWaypointHighlightLayer;
    exports.MapWaypointHighlightModule = MapWaypointHighlightModule;
    exports.MapWaypointRenderer = MapWaypointRenderer;
    exports.MapWaypointsLayer = MapWaypointsLayer;
    exports.MapWaypointsModule = MapWaypointsModule;
    exports.MapWaypointsVisController = MapWaypointsVisController;
    exports.MapWindVectorController = MapWindVectorController;
    exports.MapWindVectorLayer = MapWindVectorLayer;
    exports.MapWindVectorModule = MapWindVectorModule;
    exports.MapWxrController = MapWxrController;
    exports.MarkerBeaconDisplay = MarkerBeaconDisplay;
    exports.MarkerBeaconSystem = MarkerBeaconSystem;
    exports.MinimumsAlerter = MinimumsAlerter;
    exports.MinimumsDisplay = MinimumsDisplay;
    exports.MinimumsUnitsManager = MinimumsUnitsManager;
    exports.MultipleSoftKeyEnumController = MultipleSoftKeyEnumController;
    exports.NavDataBar = NavDataBar;
    exports.NavDataBarFieldAglModelFactory = NavDataBarFieldAglModelFactory;
    exports.NavDataBarFieldBrgModelFactory = NavDataBarFieldBrgModelFactory;
    exports.NavDataBarFieldCabinAltitudeModelFactory = NavDataBarFieldCabinAltitudeModelFactory;
    exports.NavDataBarFieldClgModelFactory = NavDataBarFieldClgModelFactory;
    exports.NavDataBarFieldClmModelFactory = NavDataBarFieldClmModelFactory;
    exports.NavDataBarFieldConsumerModel = NavDataBarFieldConsumerModel;
    exports.NavDataBarFieldConsumerNumberUnitModel = NavDataBarFieldConsumerNumberUnitModel;
    exports.NavDataBarFieldConsumerValueModel = NavDataBarFieldConsumerValueModel;
    exports.NavDataBarFieldConsumerValueNumberUnitModel = NavDataBarFieldConsumerValueNumberUnitModel;
    exports.NavDataBarFieldDensityAltitudeModelFactory = NavDataBarFieldDensityAltitudeModelFactory;
    exports.NavDataBarFieldDestModelFactory = NavDataBarFieldDestModelFactory;
    exports.NavDataBarFieldDisModelFactory = NavDataBarFieldDisModelFactory;
    exports.NavDataBarFieldDtgModelFactory = NavDataBarFieldDtgModelFactory;
    exports.NavDataBarFieldDtkModelFactory = NavDataBarFieldDtkModelFactory;
    exports.NavDataBarFieldEcoModelFactory = NavDataBarFieldEcoModelFactory;
    exports.NavDataBarFieldEndModelFactory = NavDataBarFieldEndModelFactory;
    exports.NavDataBarFieldEnrModelFactory = NavDataBarFieldEnrModelFactory;
    exports.NavDataBarFieldEtaModelFactory = NavDataBarFieldEtaModelFactory;
    exports.NavDataBarFieldEteModelFactory = NavDataBarFieldEteModelFactory;
    exports.NavDataBarFieldFlightLevelModelFactory = NavDataBarFieldFlightLevelModelFactory;
    exports.NavDataBarFieldFobModelFactory = NavDataBarFieldFobModelFactory;
    exports.NavDataBarFieldFodModelFactory = NavDataBarFieldFodModelFactory;
    exports.NavDataBarFieldFuelFlowModelFactory = NavDataBarFieldFuelFlowModelFactory;
    exports.NavDataBarFieldGMeterModelFactory = NavDataBarFieldGMeterModelFactory;
    exports.NavDataBarFieldGenericModel = NavDataBarFieldGenericModel;
    exports.NavDataBarFieldGpsAltitudeModelFactory = NavDataBarFieldGpsAltitudeModelFactory;
    exports.NavDataBarFieldGsModelFactory = NavDataBarFieldGsModelFactory;
    exports.NavDataBarFieldIsaModelFactory = NavDataBarFieldIsaModelFactory;
    exports.NavDataBarFieldLdgModelFactory = NavDataBarFieldLdgModelFactory;
    exports.NavDataBarFieldMachModelFactory = NavDataBarFieldMachModelFactory;
    exports.NavDataBarFieldOatModelFactory = NavDataBarFieldOatModelFactory;
    exports.NavDataBarFieldRatModelFactory = NavDataBarFieldRatModelFactory;
    exports.NavDataBarFieldTasModelFactory = NavDataBarFieldTasModelFactory;
    exports.NavDataBarFieldTkeModelFactory = NavDataBarFieldTkeModelFactory;
    exports.NavDataBarFieldTrkModelFactory = NavDataBarFieldTrkModelFactory;
    exports.NavDataBarFieldUtcModelFactory = NavDataBarFieldUtcModelFactory;
    exports.NavDataBarFieldVsrModelFactory = NavDataBarFieldVsrModelFactory;
    exports.NavDataBarFieldWptModelFactory = NavDataBarFieldWptModelFactory;
    exports.NavDataBarFieldXtkModelFactory = NavDataBarFieldXtkModelFactory;
    exports.NavDataBarUserSettings = NavDataBarUserSettings;
    exports.NavDataBearingField = NavDataBearingField;
    exports.NavDataDurationField = NavDataDurationField;
    exports.NavDataField = NavDataField;
    exports.NavDataFieldBearingRenderer = NavDataFieldBearingRenderer;
    exports.NavDataFieldDurationRenderer = NavDataFieldDurationRenderer;
    exports.NavDataFieldLdgRenderer = NavDataFieldLdgRenderer;
    exports.NavDataFieldNumberUnitRenderer = NavDataFieldNumberUnitRenderer;
    exports.NavDataFieldTextRenderer = NavDataFieldTextRenderer;
    exports.NavDataFieldTimeRenderer = NavDataFieldTimeRenderer;
    exports.NavDataGenericField = NavDataGenericField;
    exports.NavDataNumberUnitField = NavDataNumberUnitField;
    exports.NavDataTimeField = NavDataTimeField;
    exports.NavEventsPublisher = NavEventsPublisher;
    exports.NavIndicatorController = NavIndicatorController;
    exports.NavRadioNavSource = NavRadioNavSource;
    exports.NavReferenceIndicatorsCollection = NavReferenceIndicatorsCollection;
    exports.NavReferenceSourceCollection = NavReferenceSourceCollection;
    exports.NavStatusBox = NavStatusBox;
    exports.NavStatusBoxDtkAlert = NavStatusBoxDtkAlert;
    exports.NavStatusBoxFieldBrgModel = NavStatusBoxFieldBrgModel;
    exports.NavStatusBoxFieldBrgModelFactory = NavStatusBoxFieldBrgModelFactory;
    exports.NavStatusBoxFieldDisModel = NavStatusBoxFieldDisModel;
    exports.NavStatusBoxFieldDisModelFactory = NavStatusBoxFieldDisModelFactory;
    exports.NavStatusBoxFieldEteModel = NavStatusBoxFieldEteModel;
    exports.NavStatusBoxFieldEteModelFactory = NavStatusBoxFieldEteModelFactory;
    exports.NavStatusBoxFieldModelFactory = NavStatusBoxFieldModelFactory;
    exports.NavStatusBoxFieldRenderer = NavStatusBoxFieldRenderer;
    exports.NavStatusBoxLegArrow = NavStatusBoxLegArrow;
    exports.NavStatusBoxLegDisplay = NavStatusBoxLegDisplay;
    exports.NavdataComputer = NavdataComputer;
    exports.NearestAirportUserSettings = NearestAirportUserSettings;
    exports.NearestMapRTRController = NearestMapRTRController;
    exports.NeedleAnimator = NeedleAnimator;
    exports.NextGenConnextMapBuilder = NextGenConnextMapBuilder;
    exports.NextGenDateTimeNavDataFieldTypeRenderer = NextGenDateTimeNavDataFieldTypeRenderer;
    exports.NextGenDisplayUnitNavDataFieldTypeRenderer = NextGenDisplayUnitNavDataFieldTypeRenderer;
    exports.NextGenGarminMapBuilder = NextGenGarminMapBuilder;
    exports.NextGenHsiMapBuilder = NextGenHsiMapBuilder;
    exports.NextGenMapWaypointStyles = NextGenMapWaypointStyles;
    exports.NextGenNavDataBarFieldRenderer = NextGenNavDataBarFieldRenderer;
    exports.NextGenNavDataFieldBrgRenderer = NextGenNavDataFieldBrgRenderer;
    exports.NextGenNavDataFieldDestRenderer = NextGenNavDataFieldDestRenderer;
    exports.NextGenNavDataFieldDisRenderer = NextGenNavDataFieldDisRenderer;
    exports.NextGenNavDataFieldDtgRenderer = NextGenNavDataFieldDtgRenderer;
    exports.NextGenNavDataFieldDtkRenderer = NextGenNavDataFieldDtkRenderer;
    exports.NextGenNavDataFieldEndRenderer = NextGenNavDataFieldEndRenderer;
    exports.NextGenNavDataFieldEnrRenderer = NextGenNavDataFieldEnrRenderer;
    exports.NextGenNavDataFieldEtaRenderer = NextGenNavDataFieldEtaRenderer;
    exports.NextGenNavDataFieldEteRenderer = NextGenNavDataFieldEteRenderer;
    exports.NextGenNavDataFieldFobRenderer = NextGenNavDataFieldFobRenderer;
    exports.NextGenNavDataFieldFodRenderer = NextGenNavDataFieldFodRenderer;
    exports.NextGenNavDataFieldGsRenderer = NextGenNavDataFieldGsRenderer;
    exports.NextGenNavDataFieldIsaRenderer = NextGenNavDataFieldIsaRenderer;
    exports.NextGenNavDataFieldTasRenderer = NextGenNavDataFieldTasRenderer;
    exports.NextGenNavDataFieldTkeRenderer = NextGenNavDataFieldTkeRenderer;
    exports.NextGenNavDataFieldTrkRenderer = NextGenNavDataFieldTrkRenderer;
    exports.NextGenNavDataFieldVsrRenderer = NextGenNavDataFieldVsrRenderer;
    exports.NextGenNavDataFieldXtkRenderer = NextGenNavDataFieldXtkRenderer;
    exports.NextGenNavMapBuilder = NextGenNavMapBuilder;
    exports.NextGenNearestMapBuilder = NextGenNearestMapBuilder;
    exports.NextGenProcMapBuilder = NextGenProcMapBuilder;
    exports.NextGenTawsAnnunciationDefs = NextGenTawsAnnunciationDefs;
    exports.NextGenWaypointMapBuilder = NextGenWaypointMapBuilder;
    exports.NullNavSource = NullNavSource;
    exports.NumberUnitDisplay = NumberUnitDisplay;
    exports.PfdDeclutterManager = PfdDeclutterManager;
    exports.PfdTrafficAnnunciation = PfdTrafficAnnunciation;
    exports.PitchLadder = PitchLadder;
    exports.ProcMapFlightPathPlanRenderer = ProcMapFlightPathPlanRenderer;
    exports.ProcMapFullFlightPathPlanRenderer = ProcMapFullFlightPathPlanRenderer;
    exports.ProcMapTransitionWaypointRecordManager = ProcMapTransitionWaypointRecordManager;
    exports.ProcedureTurnLegWaypoint = ProcedureTurnLegWaypoint;
    exports.ProcedureTurnLegWaypointsRecord = ProcedureTurnLegWaypointsRecord;
    exports.RadarAltimeter = RadarAltimeter;
    exports.RadarAltimeterSystem = RadarAltimeterSystem;
    exports.Regions = Regions;
    exports.RollIndicator = RollIndicator;
    exports.ScrollList = ScrollList;
    exports.SetValueTouchButton = SetValueTouchButton;
    exports.SoftKey = SoftKey;
    exports.SoftKeyBar = SoftKeyBar;
    exports.SoftKeyBooleanController = SoftKeyBooleanController;
    exports.SoftKeyEnumController = SoftKeyEnumController;
    exports.SoftKeyMenu = SoftKeyMenu;
    exports.SoftKeyMenuSystem = SoftKeyMenuSystem;
    exports.SyntheticVision = SyntheticVision;
    exports.TasSensitivity = TasSensitivity;
    exports.TasSensitivityParameters = TasSensitivityParameters;
    exports.TcasRaPitchCueLayer = TcasRaPitchCueLayer;
    exports.TerrainSystemAnnunciation = TerrainSystemAnnunciation;
    exports.TerrainSystemUtils = TerrainSystemUtils;
    exports.TimeDisplay = TimeDisplay;
    exports.TisSensitivity = TisSensitivity;
    exports.TisSensitivityParameters = TisSensitivityParameters;
    exports.ToggleStatusBar = ToggleStatusBar;
    exports.ToggleTouchButton = ToggleTouchButton;
    exports.TouchButton = TouchButton;
    exports.TouchList = TouchList;
    exports.TouchPad = TouchPad;
    exports.TouchSlider = TouchSlider;
    exports.TrafficAdvisorySystem = TrafficAdvisorySystem;
    exports.TrafficAvionicsSystem = TrafficAvionicsSystem;
    exports.TrafficInfoService = TrafficInfoService;
    exports.TrafficMapAdsbModeIndicator = TrafficMapAdsbModeIndicator;
    exports.TrafficMapAdsbOffBannerIndicator = TrafficMapAdsbOffBannerIndicator;
    exports.TrafficMapAltitudeModeIndicator = TrafficMapAltitudeModeIndicator;
    exports.TrafficMapBuilder = TrafficMapBuilder;
    exports.TrafficMapFailedBannerIndicator = TrafficMapFailedBannerIndicator;
    exports.TrafficMapOperatingModeIndicator = TrafficMapOperatingModeIndicator;
    exports.TrafficMapRangeController = TrafficMapRangeController;
    exports.TrafficMapRangeLayer = TrafficMapRangeLayer;
    exports.TrafficMapStandbyBannerIndicator = TrafficMapStandbyBannerIndicator;
    exports.TrafficOperatingModeManager = TrafficOperatingModeManager;
    exports.TrafficUserSettings = TrafficUserSettings;
    exports.TurnRateIndicator = TurnRateIndicator;
    exports.UnitFormatter = UnitFormatter;
    exports.UnitsUserSettings = UnitsUserSettings;
    exports.VSpeedUserSettingUtils = VSpeedUserSettingUtils;
    exports.ValueTouchButton = ValueTouchButton;
    exports.VerticalSpeedIndicator = VerticalSpeedIndicator;
    exports.WaypointAlertComputer = WaypointAlertComputer;
    exports.WaypointComponent = WaypointComponent;
    exports.WaypointIcon = WaypointIcon;
    exports.WaypointInfoStore = WaypointInfoStore;
    exports.WaypointMapHighlightController = WaypointMapHighlightController;
    exports.WaypointMapRTRController = WaypointMapRTRController;
    exports.WaypointMapSelectionModule = WaypointMapSelectionModule;
    exports.WeatherMapOrientationController = WeatherMapOrientationController;
    exports.WeatherMapOrientationSettingsController = WeatherMapOrientationSettingsController;
    exports.WeatherMapUserSettingsUtils = WeatherMapUserSettingsUtils;
    exports.WeatherRadar = WeatherRadar;
    exports.WeatherRadarAvionicsSystem = WeatherRadarAvionicsSystem;
    exports.WeatherRadarUtils = WeatherRadarUtils;
    exports.WindDisplay = WindDisplay;

    return exports;

})({}, msfssdk);
//# sourceMappingURL=garminsdk.js.map
