var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
import CompareCriteria from "./CompareCriteria";
import { AnomalyDetectionSensitivity, CheckOn, CriteriaFilterUtil, EvaluateOverTimeType, FilterType, NoDataPolicy, } from "../../../../Types/Monitor/CriteriaFilter";
import CaptureSpan from "../../Telemetry/CaptureSpan";
import MetricUnitUtil from "../../../../Utils/MetricUnitUtil";
import MetricFormulaEvaluator from "../../../../Utils/Metrics/MetricFormulaEvaluator";
import MetricBaselineService, { MetricBaselineService as MetricBaselineServiceClass, } from "../../../Services/MetricBaselineService";
export default class MetricMonitorCriteria {
    static async isMonitorInstanceCriteriaFilterMet(input) {
        const evaluations = await MetricMonitorCriteria.evaluateAllSeries(input);
        /*
         * Backwards-compat: the scalar entrypoint collapses per-series
         * evaluation down to the first matching series so existing callers
         * (single-incident path) keep working. The per-series code path uses
         * `evaluateAllSeries` directly.
         */
        const match = evaluations.find((e) => {
            return e.rootCause !== null;
        });
        /*
         * Always populate the legacy single-context field so the root-cause
         * renderer can still read metric identity from the criteria filter
         * even when nothing matched. Pick the first evaluation's context.
         */
        if (evaluations.length > 0) {
            input.criteriaFilter.metricCriteriaContext = (match || evaluations[0]).context;
        }
        return match ? match.rootCause : null;
    }
    /**
     * Evaluate a single criteria filter against every series produced by
     * the monitor. For monitors without group-by, this returns a single
     * evaluation covering all aggregated results (legacy behavior). For
     * monitors with group-by attributes set, it returns one evaluation
     * per unique series fingerprint — each with its own
     * `MetricCriteriaContext` carrying that series' breaching samples
     * and labels. The caller fans this out into one incident per
     * breaching series.
     */
    static async evaluateAllSeries(input) {
        var _a, _b, _c, _d, _e, _f;
        if (input.criteriaFilter.metricMonitorOptions &&
            !input.criteriaFilter.metricMonitorOptions.metricAggregationType) {
            input.criteriaFilter.metricMonitorOptions.metricAggregationType =
                EvaluateOverTimeType.AnyValue;
        }
        if (input.criteriaFilter.checkOn !== CheckOn.MetricValue) {
            return [];
        }
        const metricResponse = input.dataToProcess;
        const seriesBreakdown = metricResponse.seriesBreakdown;
        const queryConfigs = ((_c = (_b = (_a = input.monitorStep.data) === null || _a === void 0 ? void 0 : _a.metricMonitor) === null || _b === void 0 ? void 0 : _b.metricViewConfig) === null || _c === void 0 ? void 0 : _c.queryConfigs) ||
            [];
        const formulaConfigs = ((_f = (_e = (_d = input.monitorStep.data) === null || _d === void 0 ? void 0 : _d.metricMonitor) === null || _e === void 0 ? void 0 : _e.metricViewConfig) === null || _f === void 0 ? void 0 : _f.formulaConfigs) ||
            [];
        const nativeUnitsByMetricName = metricResponse.nativeUnitsByMetricName;
        /*
         * Series-less path: one synthetic "all-series" evaluation over the
         * flat metricResult. Preserves the pre-group-by behavior exactly.
         */
        if (!seriesBreakdown || seriesBreakdown.length === 0) {
            const result = await MetricMonitorCriteria.evaluateOneSeries({
                criteriaFilter: input.criteriaFilter,
                aggregatedResults: metricResponse.metricResult || [],
                queryConfigs,
                formulaConfigs,
                seriesFingerprint: undefined,
                seriesLabels: {},
                projectId: metricResponse.projectId,
                nativeUnitsByMetricName,
            });
            return [result];
        }
        return await Promise.all(seriesBreakdown.map((series) => {
            return MetricMonitorCriteria.evaluateOneSeries({
                criteriaFilter: input.criteriaFilter,
                aggregatedResults: series.aggregatedResults,
                queryConfigs,
                formulaConfigs,
                seriesFingerprint: series.fingerprint,
                seriesLabels: series.labels,
                projectId: metricResponse.projectId,
                nativeUnitsByMetricName,
            });
        }));
    }
    /**
     * Core evaluation loop: compare the samples for one metric series
     * against the criteria threshold. Builds the metric identity context,
     * identifies breaching samples, and assembles the human-readable
     * root-cause message. Factored out so `evaluateAllSeries` can invoke
     * it once per series without duplicating logic.
     */
    static async evaluateOneSeries(input) {
        var _a, _b, _c;
        const rawThreshold = CompareCriteria.convertToNumber(input.criteriaFilter.value);
        const metricAlias = ((_a = input.criteriaFilter.metricMonitorOptions) === null || _a === void 0 ? void 0 : _a.metricAlias) || "";
        const metricAggregatedResult = input.aggregatedResults;
        /*
         * Resolve which query/formula the alias refers to. Use explicit index
         * checks (not `findIndex() || -1`, which incorrectly falls back to -1
         * when the first element matches).
         */
        let matchedQuery = null;
        let matchedFormula = null;
        let aliasIndex = -1;
        if (metricAlias) {
            const qIdx = input.queryConfigs.findIndex((q) => {
                var _a;
                return ((_a = q.metricAliasData) === null || _a === void 0 ? void 0 : _a.metricVariable) === metricAlias;
            });
            if (qIdx >= 0) {
                matchedQuery = input.queryConfigs[qIdx] || null;
                aliasIndex = qIdx;
            }
            else {
                const fIdx = input.formulaConfigs.findIndex((f) => {
                    var _a;
                    return ((_a = f.metricAliasData) === null || _a === void 0 ? void 0 : _a.metricVariable) === metricAlias;
                });
                if (fIdx >= 0) {
                    matchedFormula = input.formulaConfigs[fIdx] || null;
                    aliasIndex = input.queryConfigs.length + fIdx;
                }
            }
        }
        /*
         * If no alias was configured or it didn't match anything, fall back to
         * the first aggregated result / query for a best-effort comparison.
         */
        const aggregatedResult = aliasIndex >= 0
            ? metricAggregatedResult[aliasIndex]
            : metricAggregatedResult[0];
        if (!matchedQuery && !matchedFormula && input.queryConfigs[0]) {
            matchedQuery = input.queryConfigs[0];
        }
        /*
         * Build the metric context regardless of whether the threshold breaches,
         * so the filter message can still reference the metric if needed.
         */
        const metricContext = MetricMonitorCriteria.buildContext({
            matchedQuery,
            matchedFormula,
            metricAlias,
            criteriaFilter: input.criteriaFilter,
            queryConfigs: input.queryConfigs,
            formulaConfigs: input.formulaConfigs,
            nativeUnitsByMetricName: input.nativeUnitsByMetricName,
        });
        if (input.seriesFingerprint) {
            metricContext.seriesFingerprint = input.seriesFingerprint;
        }
        if (input.seriesLabels && Object.keys(input.seriesLabels).length > 0) {
            metricContext.seriesLabels = input.seriesLabels;
        }
        /*
         * Anomaly-detection branch. Skips the static threshold path entirely
         * and instead compares each sample to the rolling baseline for its
         * hour-of-week. Returns Learning state (no breach) when the baseline
         * is missing or unreliable — never raises an alert from a thin
         * baseline.
         */
        if (CriteriaFilterUtil.isAnomalyFilterType(input.criteriaFilter.filterType)) {
            return await MetricMonitorCriteria.evaluateAnomaly({
                criteriaFilter: input.criteriaFilter,
                metricContext,
                aggregatedResult,
                seriesFingerprint: input.seriesFingerprint,
                seriesLabels: input.seriesLabels,
                projectId: input.projectId,
            });
        }
        if (rawThreshold === null) {
            return {
                fingerprint: input.seriesFingerprint,
                labels: input.seriesLabels,
                rootCause: null,
                context: metricContext,
            };
        }
        /*
         * Sample values arrive already converted into the query's configured
         * display unit (legendUnit) — the upstream fetch step uses
         * MetricResultUnitConverter to normalize OpenTelemetry's native unit
         * into whatever the user picked. So `sampleUnit` here is that
         * legendUnit, and `thresholdUnit` is what the user typed into the
         * criteria threshold. When they differ we convert sample values from
         * legendUnit → thresholdUnit before comparing, so the alert message
         * reads in the unit the user actually chose for the threshold.
         */
        const sampleUnit = metricContext.unit || undefined;
        const thresholdUnit = ((_b = input.criteriaFilter.metricMonitorOptions) === null || _b === void 0 ? void 0 : _b.thresholdUnit) || sampleUnit;
        const displayUnit = thresholdUnit;
        /*
         * Threshold is entered in thresholdUnit; keep the numeric value as-is
         * for comparison in that same unit.
         */
        const threshold = rawThreshold;
        const convertToDisplayUnit = (value) => {
            if (!sampleUnit || !displayUnit || sampleUnit === displayUnit) {
                return value;
            }
            return MetricUnitUtil.convertToMetricUnit({
                value,
                fromUnit: sampleUnit,
                metricUnit: displayUnit,
            });
        };
        metricContext.unit = displayUnit || null;
        const samples = (aggregatedResult && aggregatedResult.data) || [];
        /*
         * Respect the configured no-data policy. Without this guard, the
         * evaluator silently treats missing data as a value of 0 and can
         * trigger incidents for monitors that simply haven't received data.
         */
        if (samples.length === 0) {
            const policy = ((_c = input.criteriaFilter.metricMonitorOptions) === null || _c === void 0 ? void 0 : _c.onNoDataPolicy) ||
                NoDataPolicy.Ignore;
            if (policy === NoDataPolicy.Ignore) {
                return {
                    fingerprint: input.seriesFingerprint,
                    labels: input.seriesLabels,
                    rootCause: null,
                    context: metricContext,
                };
            }
            if (policy === NoDataPolicy.Trigger) {
                return {
                    fingerprint: input.seriesFingerprint,
                    labels: input.seriesLabels,
                    rootCause: `No data received for ${metricContext.metricName} in the evaluation window — triggering per no-data policy.`,
                    context: metricContext,
                };
            }
            // TreatAsZero: fall through to the comparator with value 0.
        }
        const numbersInDisplayUnit = samples.map((d) => {
            return convertToDisplayUnit(d.value);
        });
        const comparisonMessage = CompareCriteria.compareCriteriaNumbers({
            value: numbersInDisplayUnit.length > 0 ? numbersInDisplayUnit : 0,
            threshold: threshold,
            criteriaFilter: input.criteriaFilter,
            metricDisplayName: metricContext.metricName,
            unit: displayUnit,
        });
        if (!comparisonMessage) {
            return {
                fingerprint: input.seriesFingerprint,
                labels: input.seriesLabels,
                rootCause: null,
                context: metricContext,
            };
        }
        /*
         * Identify every sample that breached so the root cause can render a
         * timestamp/value table instead of a giant one-line list, and keep a
         * total count so we can say "N of M samples breached". Values are
         * stored in the display unit so they match the comparison message.
         */
        const breachingSamples = [];
        /*
         * For formulas, precompute an index from ISO-timestamp → value for
         * every component series so we can show what `a` and `b` were at
         * the moment the formula breached, without re-walking the arrays
         * inside the per-sample loop.
         */
        const componentValueLookup = matchedFormula
            ? MetricMonitorCriteria.buildComponentValueLookup({
                components: metricContext.components || [],
                queryConfigs: input.queryConfigs,
                formulaConfigs: input.formulaConfigs,
                metricAggregatedResult,
            })
            : null;
        for (const sample of samples) {
            const convertedValue = convertToDisplayUnit(sample.value);
            const breaches = MetricMonitorCriteria.sampleBreaches(convertedValue, threshold, input.criteriaFilter.filterType);
            if (breaches) {
                const entry = {
                    value: convertedValue,
                    timestamp: sample.timestamp,
                    attributes: MetricMonitorCriteria.extractLabelAttributes(sample),
                };
                if (componentValueLookup && metricContext.components) {
                    entry.componentValues = MetricMonitorCriteria.resolveComponentValues({
                        timestamp: sample.timestamp,
                        components: metricContext.components,
                        lookup: componentValueLookup,
                    });
                }
                breachingSamples.push(entry);
            }
        }
        metricContext.totalSamplesInWindow = samples.length;
        if (breachingSamples.length > 0) {
            metricContext.breachingSample = breachingSamples[0];
            metricContext.breachingSamples = breachingSamples;
        }
        return {
            fingerprint: input.seriesFingerprint,
            labels: input.seriesLabels,
            rootCause: comparisonMessage,
            context: metricContext,
        };
    }
    static buildComponentValueLookup(input) {
        // Map of alias -> (isoTimestamp -> value)
        const lookup = new Map();
        for (const component of input.components) {
            const queryIdx = input.queryConfigs.findIndex((q) => {
                var _a;
                return ((((_a = q.metricAliasData) === null || _a === void 0 ? void 0 : _a.metricVariable) || "").toLowerCase() ===
                    component.alias);
            });
            let resultIndex = -1;
            if (queryIdx >= 0) {
                resultIndex = queryIdx;
            }
            else {
                const formulaIdx = input.formulaConfigs.findIndex((fc) => {
                    var _a;
                    return ((((_a = fc.metricAliasData) === null || _a === void 0 ? void 0 : _a.metricVariable) || "").toLowerCase() ===
                        component.alias);
                });
                if (formulaIdx >= 0) {
                    resultIndex = input.queryConfigs.length + formulaIdx;
                }
            }
            if (resultIndex < 0) {
                continue;
            }
            const series = input.metricAggregatedResult[resultIndex];
            if (!series) {
                continue;
            }
            const timestampMap = new Map();
            for (const row of series.data) {
                const iso = MetricMonitorCriteria.toIsoTimestamp(row.timestamp);
                timestampMap.set(iso, row.value);
            }
            lookup.set(component.alias, timestampMap);
        }
        return lookup;
    }
    static resolveComponentValues(input) {
        const iso = MetricMonitorCriteria.toIsoTimestamp(input.timestamp);
        return input.components.map((component) => {
            const series = input.lookup.get(component.alias);
            const value = series ? series.get(iso) : undefined;
            return {
                alias: component.alias,
                value: typeof value === "number" ? value : null,
            };
        });
    }
    static toIsoTimestamp(value) {
        const date = value instanceof Date ? value : new Date(value);
        return isNaN(date.getTime()) ? String(value) : date.toISOString();
    }
    static sampleBreaches(value, threshold, filterType) {
        switch (filterType) {
            case FilterType.GreaterThan:
                return value > threshold;
            case FilterType.GreaterThanOrEqualTo:
                return value >= threshold;
            case FilterType.LessThan:
                return value < threshold;
            case FilterType.LessThanOrEqualTo:
                return value <= threshold;
            case FilterType.EqualTo:
                return value === threshold;
            case FilterType.NotEqualTo:
                return value !== threshold;
            default:
                return false;
        }
    }
    static extractLabelAttributes(sample) {
        /*
         * AggregatedModel has a string index signature that holds group-by
         * attributes alongside `timestamp` and `value`. Strip the known keys
         * to get the label dictionary.
         */
        const labels = {};
        for (const key of Object.keys(sample)) {
            if (key === "timestamp" || key === "value") {
                continue;
            }
            const v = sample[key];
            if (v === undefined || v === null) {
                continue;
            }
            labels[key] = v;
        }
        return labels;
    }
    static buildContext(input) {
        var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
        const q = input.matchedQuery;
        const f = input.matchedFormula;
        const metricName = ((_b = (_a = q === null || q === void 0 ? void 0 : q.metricQueryData) === null || _a === void 0 ? void 0 : _a.filterData) === null || _b === void 0 ? void 0 : _b.metricName) ||
            ((_c = f === null || f === void 0 ? void 0 : f.metricFormulaData) === null || _c === void 0 ? void 0 : _c.metricFormula) ||
            ((_d = q === null || q === void 0 ? void 0 : q.metricAliasData) === null || _d === void 0 ? void 0 : _d.title) ||
            ((_e = f === null || f === void 0 ? void 0 : f.metricAliasData) === null || _e === void 0 ? void 0 : _e.title) ||
            "Metric";
        /*
         * Prefer the legendUnit the user picked, but fall back to the
         * metric's native unit (loaded from MetricType by the worker). The
         * fallback matters for ratio metrics like system.filesystem.utilization,
         * whose native unit is OTel's dimensionless "1" — without it, a "%"
         * threshold can't be compared against the raw [0, 1] samples.
         */
        const rawMetricName = ((_g = (_f = q === null || q === void 0 ? void 0 : q.metricQueryData) === null || _f === void 0 ? void 0 : _f.filterData) === null || _g === void 0 ? void 0 : _g.metricName) ||
            undefined;
        const nativeUnitFromMap = rawMetricName
            ? (_h = input.nativeUnitsByMetricName) === null || _h === void 0 ? void 0 : _h[rawMetricName.toLowerCase()]
            : undefined;
        const unit = ((_j = q === null || q === void 0 ? void 0 : q.metricAliasData) === null || _j === void 0 ? void 0 : _j.legendUnit) ||
            ((_k = f === null || f === void 0 ? void 0 : f.metricAliasData) === null || _k === void 0 ? void 0 : _k.legendUnit) ||
            nativeUnitFromMap ||
            null;
        const aggregationType = ((_m = (_l = q === null || q === void 0 ? void 0 : q.metricQueryData) === null || _l === void 0 ? void 0 : _l.filterData) === null || _m === void 0 ? void 0 : _m.aggegationType) || null;
        const filterAttributes = ((_p = (_o = q === null || q === void 0 ? void 0 : q.metricQueryData) === null || _o === void 0 ? void 0 : _o.filterData) === null || _p === void 0 ? void 0 : _p.attributes) ||
            {};
        const groupBy = ((_q = q === null || q === void 0 ? void 0 : q.metricQueryData) === null || _q === void 0 ? void 0 : _q.groupBy)
            ? Object.keys(q.metricQueryData.groupBy)
            : [];
        /*
         * Include user-selected attribute keys as part of the groupBy view
         * so the root-cause block shows "Grouped By: host.name" not just the
         * raw columns ClickHouse was asked to partition on.
         */
        const groupByAttributeKeys = ((_r = q === null || q === void 0 ? void 0 : q.metricQueryData) === null || _r === void 0 ? void 0 : _r.groupByAttributeKeys) || [];
        const allGroupBy = Array.from(new Set([...groupBy, ...groupByAttributeKeys]));
        const components = f
            ? MetricMonitorCriteria.buildFormulaComponents({
                formulaConfig: f,
                queryConfigs: input.queryConfigs,
                formulaConfigs: input.formulaConfigs,
            })
            : undefined;
        return Object.assign({ metricName, alias: input.metricAlias, unit,
            aggregationType, isFormula: Boolean(f), formulaExpression: (_s = f === null || f === void 0 ? void 0 : f.metricFormulaData) === null || _s === void 0 ? void 0 : _s.metricFormula, filterAttributes, groupBy: allGroupBy, timeWindowMinutes: (_t = input.criteriaFilter.evaluateOverTimeOptions) === null || _t === void 0 ? void 0 : _t.timeValueInMinutes }, (components && components.length > 0 ? { components } : {}));
    }
    /**
     * Resolve the variables the formula references to their source
     * query/formula definitions so the root cause can label each
     * component column with its metric name and native unit.
     */
    static buildFormulaComponents(input) {
        var _a, _b, _c, _d, _e, _f, _g, _h;
        const formula = ((_a = input.formulaConfig.metricFormulaData) === null || _a === void 0 ? void 0 : _a.metricFormula) || "";
        const referenced = MetricFormulaEvaluator.getReferencedVariables(formula);
        const components = [];
        const seen = new Set();
        for (const alias of referenced) {
            const normalizedAlias = alias.toLowerCase();
            if (seen.has(normalizedAlias)) {
                continue;
            }
            seen.add(normalizedAlias);
            const queryMatch = input.queryConfigs.find((q) => {
                var _a;
                return ((((_a = q.metricAliasData) === null || _a === void 0 ? void 0 : _a.metricVariable) || "").toLowerCase() ===
                    normalizedAlias);
            });
            if (queryMatch) {
                const name = ((_c = (_b = queryMatch.metricQueryData) === null || _b === void 0 ? void 0 : _b.filterData) === null || _c === void 0 ? void 0 : _c.metricName) ||
                    ((_d = queryMatch.metricAliasData) === null || _d === void 0 ? void 0 : _d.title) ||
                    normalizedAlias;
                components.push({
                    alias: normalizedAlias,
                    name,
                    unit: ((_e = queryMatch.metricAliasData) === null || _e === void 0 ? void 0 : _e.legendUnit) || null,
                    isFormula: false,
                });
                continue;
            }
            const formulaMatch = input.formulaConfigs.find((fc) => {
                var _a;
                return ((((_a = fc.metricAliasData) === null || _a === void 0 ? void 0 : _a.metricVariable) || "").toLowerCase() ===
                    normalizedAlias);
            });
            if (formulaMatch) {
                const name = ((_f = formulaMatch.metricAliasData) === null || _f === void 0 ? void 0 : _f.title) ||
                    ((_g = formulaMatch.metricFormulaData) === null || _g === void 0 ? void 0 : _g.metricFormula) ||
                    normalizedAlias;
                components.push({
                    alias: normalizedAlias,
                    name,
                    unit: ((_h = formulaMatch.metricAliasData) === null || _h === void 0 ? void 0 : _h.legendUnit) || null,
                    isFormula: true,
                });
            }
        }
        return components;
    }
    /**
     * Evaluate one series against an anomaly criterion. For each sample
     * in the eval window we look up the baseline for its hour-of-week,
     * compute σ from the baseline (mean+stddev or median+MAD path), and
     * mark the sample as breaching when it falls outside the configured
     * direction.
     *
     * Returns no breach when:
     *   - The criterion has no metric name (formulas are not supported in
     *     v1 — we'd need to baseline the formula's output series too).
     *   - The projectId is missing (defensive — should never happen for
     *     metric monitors).
     *   - The baseline is missing or unreliable (cold-start). The
     *     metricContext.anomalyBaseline.state surfaces "Learning" so the
     *     UI can explain why nothing fired.
     */
    static async evaluateAnomaly(input) {
        var _a, _b, _c, _d, _e, _f, _g;
        const { criteriaFilter, metricContext } = input;
        const noBreach = () => {
            return {
                fingerprint: input.seriesFingerprint,
                labels: input.seriesLabels,
                rootCause: null,
                context: metricContext,
            };
        };
        /*
         * Formulas don't carry a baselined metric name (the formula itself
         * is the "metric") — skip rather than misattribute. A future phase
         * can baseline formula output series.
         */
        if (metricContext.isFormula) {
            metricContext.anomalyBaseline = {
                state: "Learning",
                mean: 0,
                stddev: 0,
                expectedHigh: 0,
                expectedLow: 0,
                sigmaCount: 0,
                sampleCount: 0,
                hourOfWeek: -1,
                windowDays: 0,
            };
            return noBreach();
        }
        if (!input.projectId) {
            return noBreach();
        }
        const samples = (input.aggregatedResult && input.aggregatedResult.data) || [];
        if (samples.length === 0) {
            const policy = ((_a = criteriaFilter.metricMonitorOptions) === null || _a === void 0 ? void 0 : _a.onNoDataPolicy) ||
                NoDataPolicy.Ignore;
            if (policy === NoDataPolicy.Trigger) {
                return {
                    fingerprint: input.seriesFingerprint,
                    labels: input.seriesLabels,
                    rootCause: `No data received for ${metricContext.metricName} in the evaluation window — triggering per no-data policy.`,
                    context: metricContext,
                };
            }
            return noBreach();
        }
        const sensitivity = ((_c = (_b = criteriaFilter.metricMonitorOptions) === null || _b === void 0 ? void 0 : _b.anomalyDetection) === null || _c === void 0 ? void 0 : _c.sensitivity) || AnomalyDetectionSensitivity.Medium;
        const sigmaCount = MetricBaselineServiceClass.sigmaForSensitivity(sensitivity);
        const windowDays = (_e = (_d = criteriaFilter.metricMonitorOptions) === null || _d === void 0 ? void 0 : _d.anomalyDetection) === null || _e === void 0 ? void 0 : _e.windowDays;
        const minSamples = (_g = (_f = criteriaFilter.metricMonitorOptions) === null || _f === void 0 ? void 0 : _f.anomalyDetection) === null || _g === void 0 ? void 0 : _g.minSamples;
        /*
         * Look up baselines per distinct hour-of-week present in the eval
         * window. A 5-min window almost always covers a single hour, but
         * windows that straddle the hour boundary need both baselines.
         */
        const hoursInWindow = new Set();
        for (const sample of samples) {
            const ts = sample.timestamp instanceof Date
                ? sample.timestamp
                : new Date(sample.timestamp);
            if (!isNaN(ts.getTime())) {
                hoursInWindow.add(MetricBaselineServiceClass.computeHourOfWeek(ts));
            }
        }
        if (hoursInWindow.size === 0) {
            return noBreach();
        }
        const baselineByHour = new Map();
        for (const hour of hoursInWindow) {
            const baseline = await MetricBaselineService.getBaseline({
                projectId: input.projectId.toString(),
                metricName: metricContext.metricName,
                hourOfWeek: hour,
                windowDays,
                minSamples,
            });
            if (baseline && baseline.isReliable) {
                baselineByHour.set(hour, baseline);
            }
        }
        if (baselineByHour.size === 0) {
            // Cold-start: surface state but don't fire.
            const firstHour = Array.from(hoursInWindow)[0];
            metricContext.anomalyBaseline = {
                state: "Learning",
                mean: 0,
                stddev: 0,
                expectedHigh: 0,
                expectedLow: 0,
                sigmaCount,
                sampleCount: 0,
                hourOfWeek: firstHour,
                windowDays: windowDays || MetricBaselineServiceClass.DEFAULT_WINDOW_DAYS,
            };
            return noBreach();
        }
        /*
         * Walk every sample, compare to its hour's baseline. The first
         * breaching sample produces the root-cause string and seeds
         * metricContext.anomalyBaseline; remaining breaches accumulate
         * into breachingSamples for the incident render.
         */
        const breachingSamples = [];
        let firstBreach = null;
        for (const sample of samples) {
            const ts = sample.timestamp instanceof Date
                ? sample.timestamp
                : new Date(sample.timestamp);
            if (isNaN(ts.getTime())) {
                continue;
            }
            const hour = MetricBaselineServiceClass.computeHourOfWeek(ts);
            const baseline = baselineByHour.get(hour);
            if (!baseline) {
                continue;
            }
            const stddevForSigma = baseline.stddev;
            if (!Number.isFinite(stddevForSigma) || stddevForSigma === 0) {
                // Zero-variance baseline → we'd alert on every sample. Skip.
                continue;
            }
            const expectedHigh = baseline.mean + sigmaCount * stddevForSigma;
            const expectedLow = baseline.mean - sigmaCount * stddevForSigma;
            const observedSigma = (sample.value - baseline.mean) / stddevForSigma;
            const breaches = MetricMonitorCriteria.anomalyBreaches(sample.value, expectedHigh, expectedLow, criteriaFilter.filterType);
            if (breaches) {
                breachingSamples.push({
                    value: sample.value,
                    timestamp: ts,
                    attributes: MetricMonitorCriteria.extractLabelAttributes(sample),
                });
                if (!firstBreach) {
                    firstBreach = { sample, baseline, sigma: observedSigma };
                }
            }
        }
        metricContext.totalSamplesInWindow = samples.length;
        if (!firstBreach) {
            // No breach. Mark Normal so the UI can show "baseline ready".
            const firstHour = Array.from(baselineByHour.keys())[0];
            const firstBaseline = baselineByHour.get(firstHour);
            metricContext.anomalyBaseline = {
                state: "Normal",
                mean: firstBaseline.mean,
                stddev: firstBaseline.stddev,
                expectedHigh: firstBaseline.mean + sigmaCount * firstBaseline.stddev,
                expectedLow: firstBaseline.mean - sigmaCount * firstBaseline.stddev,
                sigmaCount,
                sampleCount: firstBaseline.sampleCount,
                hourOfWeek: firstHour,
                windowDays: firstBaseline.windowDays,
            };
            return noBreach();
        }
        const baseline = firstBreach.baseline;
        const anomaly = {
            state: "Anomalous",
            mean: baseline.mean,
            stddev: baseline.stddev,
            expectedHigh: baseline.mean + sigmaCount * baseline.stddev,
            expectedLow: baseline.mean - sigmaCount * baseline.stddev,
            sigmaCount,
            sampleCount: baseline.sampleCount,
            hourOfWeek: baseline.hourOfWeek,
            windowDays: baseline.windowDays,
            observedValue: firstBreach.sample.value,
            observedSigma: firstBreach.sigma,
        };
        metricContext.anomalyBaseline = anomaly;
        metricContext.breachingSample = breachingSamples[0];
        metricContext.breachingSamples = breachingSamples;
        const direction = criteriaFilter.filterType === FilterType.AnomalouslyHigh
            ? "above"
            : criteriaFilter.filterType === FilterType.AnomalouslyLow
                ? "below"
                : firstBreach.sigma >= 0
                    ? "above"
                    : "below";
        const sigmaAbs = Math.abs(firstBreach.sigma);
        const unitSuffix = metricContext.unit
            ? ` ${metricContext.unit}`
            : "";
        const rootCause = `${metricContext.metricName} value ${firstBreach.sample.value.toFixed(3)}${unitSuffix} ` +
            `is ${sigmaAbs.toFixed(2)}σ ${direction} the same-hour baseline ` +
            `(mean ${baseline.mean.toFixed(3)}${unitSuffix}, σ ${baseline.stddev.toFixed(3)}${unitSuffix}, ` +
            `${baseline.sampleCount} samples over ${baseline.windowDays} days, sensitivity ${sensitivity}).`;
        return {
            fingerprint: input.seriesFingerprint,
            labels: input.seriesLabels,
            rootCause,
            context: metricContext,
        };
    }
    static anomalyBreaches(value, expectedHigh, expectedLow, filterType) {
        switch (filterType) {
            case FilterType.AnomalouslyHigh:
                return value > expectedHigh;
            case FilterType.AnomalouslyLow:
                return value < expectedLow;
            case FilterType.Anomalous:
                return value > expectedHigh || value < expectedLow;
            default:
                return false;
        }
    }
}
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], MetricMonitorCriteria, "isMonitorInstanceCriteriaFilterMet", null);
//# sourceMappingURL=MetricMonitorCriteria.js.map