"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MetricMeterWithComposedTags = exports.noopMetricMeter = void 0;
const internal_workflow_1 = require("./internal-workflow");
////////////////////////////////////////////////////////////////////////////////////////////////////
/**
 * A meter implementation that does nothing.
 */
class NoopMetricMeter {
    createCounter(name, unit, description) {
        return {
            name,
            unit,
            description,
            kind: 'counter',
            valueType: 'int',
            add(_value, _extraTags) { },
            withTags(_extraTags) {
                return this;
            },
        };
    }
    createHistogram(name, valueType = 'int', unit, description) {
        return {
            name,
            unit,
            description,
            kind: 'histogram',
            valueType,
            record(_value, _extraTags) { },
            withTags(_extraTags) {
                return this;
            },
        };
    }
    createGauge(name, valueType = 'int', unit, description) {
        return {
            name,
            unit,
            description,
            kind: 'gauge',
            valueType,
            set(_value, _extraTags) { },
            withTags(_extraTags) {
                return this;
            },
        };
    }
    withTags(_extraTags) {
        return this;
    }
}
exports.noopMetricMeter = new NoopMetricMeter();
/**
 * A meter implementation that adds tags before delegating calls to a parent meter.
 *
 * @experimental The Metric API is an experimental feature and may be subject to change.
 * @internal
 * @hidden
 */
class MetricMeterWithComposedTags {
    parentMeter;
    contributors;
    /**
     * Return a {@link MetricMeter} that adds tags before delegating calls to a parent meter.
     *
     * New tags may either be specified statically as a delta object, or as a function evaluated
     * every time a metric is recorded that will return a delta object.
     *
     * Some optimizations are performed to avoid creating unnecessary objects and to keep runtime
     * overhead associated with resolving tags as low as possible.
     *
     * @param meter The parent meter to delegate calls to.
     * @param tagsOrFunc New tags may either be specified statically as a delta object, or as a function
     *                   evaluated every time a metric is recorded that will return a delta object.
     * @param force if `true`, then a `MetricMeterWithComposedTags` will be created even if there
     *              is no tags to add. This is useful to add tags support to an underlying meter
     *              implementation that does not support tags directly.
     */
    static compose(meter, tagsOrFunc, force = false) {
        if (meter instanceof MetricMeterWithComposedTags) {
            const contributors = appendToChain(meter.contributors, tagsOrFunc);
            // If the new contributor results in no actual change to the chain, then we don't need a new meter
            if (contributors === undefined && !force)
                return meter;
            return new MetricMeterWithComposedTags(meter.parentMeter, contributors ?? []);
        }
        else {
            const contributors = appendToChain(undefined, tagsOrFunc);
            if (contributors === undefined && !force)
                return meter;
            return new MetricMeterWithComposedTags(meter, contributors ?? []);
        }
    }
    constructor(parentMeter, contributors) {
        this.parentMeter = parentMeter;
        this.contributors = contributors;
    }
    createCounter(name, unit, description) {
        const parentCounter = this.parentMeter.createCounter(name, unit, description);
        return new MetricCounterWithComposedTags(parentCounter, this.contributors);
    }
    createHistogram(name, valueType = 'int', unit, description) {
        const parentHistogram = this.parentMeter.createHistogram(name, valueType, unit, description);
        return new MetricHistogramWithComposedTags(parentHistogram, this.contributors);
    }
    createGauge(name, valueType = 'int', unit, description) {
        const parentGauge = this.parentMeter.createGauge(name, valueType, unit, description);
        return new MetricGaugeWithComposedTags(parentGauge, this.contributors);
    }
    withTags(tags) {
        return MetricMeterWithComposedTags.compose(this, tags);
    }
}
exports.MetricMeterWithComposedTags = MetricMeterWithComposedTags;
/**
 * @experimental The Metric API is an experimental feature and may be subject to change.
 */
class MetricCounterWithComposedTags {
    parentCounter;
    contributors;
    kind = 'counter';
    valueType = 'int';
    constructor(parentCounter, contributors) {
        this.parentCounter = parentCounter;
        this.contributors = contributors;
    }
    add(value, extraTags) {
        this.parentCounter.add(value, resolveTags(this.contributors, extraTags));
    }
    withTags(extraTags) {
        const contributors = appendToChain(this.contributors, extraTags);
        if (contributors === undefined)
            return this;
        return new MetricCounterWithComposedTags(this.parentCounter, contributors);
    }
    get name() {
        return this.parentCounter.name;
    }
    get unit() {
        return this.parentCounter.unit;
    }
    get description() {
        return this.parentCounter.description;
    }
}
/**
 * @experimental The Metric API is an experimental feature and may be subject to change.
 */
class MetricHistogramWithComposedTags {
    parentHistogram;
    contributors;
    kind = 'histogram';
    constructor(parentHistogram, contributors) {
        this.parentHistogram = parentHistogram;
        this.contributors = contributors;
    }
    record(value, extraTags) {
        this.parentHistogram.record(value, resolveTags(this.contributors, extraTags));
    }
    withTags(extraTags) {
        const contributors = appendToChain(this.contributors, extraTags);
        if (contributors === undefined)
            return this;
        return new MetricHistogramWithComposedTags(this.parentHistogram, contributors);
    }
    get name() {
        return this.parentHistogram.name;
    }
    get valueType() {
        return this.parentHistogram.valueType;
    }
    get unit() {
        return this.parentHistogram.unit;
    }
    get description() {
        return this.parentHistogram.description;
    }
}
/**
 * @internal
 * @hidden
 */
class MetricGaugeWithComposedTags {
    parentGauge;
    contributors;
    kind = 'gauge';
    constructor(parentGauge, contributors) {
        this.parentGauge = parentGauge;
        this.contributors = contributors;
    }
    set(value, extraTags) {
        this.parentGauge.set(value, resolveTags(this.contributors, extraTags));
    }
    withTags(extraTags) {
        const contributors = appendToChain(this.contributors, extraTags);
        if (contributors === undefined)
            return this;
        return new MetricGaugeWithComposedTags(this.parentGauge, contributors);
    }
    get name() {
        return this.parentGauge.name;
    }
    get valueType() {
        return this.parentGauge.valueType;
    }
    get unit() {
        return this.parentGauge.unit;
    }
    get description() {
        return this.parentGauge.description;
    }
}
function resolveTags(contributors, extraTags) {
    const resolved = {};
    for (const contributor of contributors) {
        Object.assign(resolved, typeof contributor === 'function' ? contributor() : contributor);
    }
    Object.assign(resolved, extraTags);
    return (0, internal_workflow_1.filterNullAndUndefined)(resolved);
}
/**
 * Append a tags contributor to the chain, merging it with the former last contributor if possible.
 *
 * If appending the new contributor results in no actual change to the chain of contributors, return
 * `existingContributors`; in that case, the caller should avoid creating a new object if possible.
 */
function appendToChain(existingContributors, newContributor) {
    // If the new contributor is an empty object, then it results in no actual change to the chain
    if (typeof newContributor === 'object' && Object.keys(newContributor).length === 0) {
        return existingContributors;
    }
    // If existing chain is empty, then the new contributor is the chain
    if (existingContributors == null || existingContributors.length === 0) {
        return [newContributor];
    }
    // If both last contributor and new contributor are plain objects, merge them to a single object.
    const last = existingContributors[existingContributors.length - 1];
    if (typeof last === 'object' && typeof newContributor === 'object') {
        const merged = (0, internal_workflow_1.mergeObjects)(last, newContributor);
        if (merged === last)
            return existingContributors;
        return [...existingContributors.slice(0, -1), merged];
    }
    // Otherwise, just append the new contributor to the chain.
    return [...existingContributors, newContributor];
}
//# sourceMappingURL=metrics.js.map