"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.EventLinter = void 0;
const object_reader_1 = require("../json/reader/object-reader");
const optional_1 = require("../types/optional");
const numerics = require("./helpers/numerics");
/**
 * A type which applies common business rules to metrics fields
 * and generates events which are ready for posting to Figaro.
 *
 * The common business rules are:
 * - Add base event fields provided by linter configuration provider object
 * - Set clientBuiltType and resourceRevNum fields based on EventLinterEnvironment object values
 * - Set xpSendMethod field to "jet-js"
 * - Combine pageType and pageId using compound separator provided by linter configuration provider
 *   and set result to "page" field.
 * - Apply event field de-resolution rules provided by linter configuration provider object
 * - Set the "position" field for events of "media" type.
 */
class EventLinter {
    /**
     * Create an event linter.
     *
     * @param options - The options which specify various behaviors of the new linter.
     * This object will be frozen.
     */
    constructor(options) {
        this.options = Object.freeze(options);
    }
    // MARK: Public Properties
    /**
     * Topic to use if an event fields blob does not specify one.
     */
    get defaultTopic() {
        return this.options.defaultTopic;
    }
    // MARK: Utilities
    /**
     * Reduce the accuracy of fields in a blob according to
     * a given array of rules provided by linter configuration object.
     *
     * @param eventFields - The fields of an event to reduce the accuracy of.
     * @param rules - An array of de-resolution rules to apply to event fields.
     */
    applyDeResolutionRules(eventFields, rules) {
        const eventFieldsReader = new object_reader_1.ObjectReader(eventFields);
        for (const rule of rules) {
            const value = eventFieldsReader.asNumber(rule.fieldName);
            if ((0, optional_1.isNothing)(value)) {
                continue;
            }
            let magnitude = rule.magnitude;
            if ((0, optional_1.isNothing)(magnitude)) {
                magnitude = 1024 * 1024;
            }
            let significantDigits = rule.significantDigits;
            if ((0, optional_1.isNothing)(significantDigits)) {
                significantDigits = 2;
            }
            if (magnitude <= 0.0 || significantDigits < 0.0) {
                // This is the failure mode from MetricsKit.
                eventFields[rule.fieldName] = Number.NaN;
                continue;
            }
            const scaledValue = value / magnitude;
            eventFields[rule.fieldName] = numerics.reduceSignificantDigits(scaledValue, significantDigits);
        }
    }
    // MARK: Rules
    /**
     * Apply the rules which are universal to all metrics events
     * to a given metrics fields linter.
     *
     * @param eventFields - The fields which will be used to construct a built event.
     * @param topic - The topic the built event will be submitted to.
     */
    decorateCommonEventFields(eventFields, topic) {
        const eventFieldsReader = new object_reader_1.ObjectReader(eventFields);
        const configurationProvider = this.options.configuration;
        // - Base metrics fields.
        const baseFields = configurationProvider.baseFields(topic);
        if ((0, optional_1.isSome)(baseFields)) {
            Object.assign(eventFields, baseFields);
        }
        // - Universal basic fields.
        eventFields["clientBuildType"] = this.options.environment.buildType;
        eventFields["resourceRevNum"] = this.options.environment.jsVersion;
        eventFields["xpSendMethod"] = "jet-js";
        // - Page.
        const pageType = eventFieldsReader.asString("pageType");
        const pageId = eventFieldsReader.asString("pageId");
        if ((0, optional_1.isSome)(pageType) && (0, optional_1.isSome)(pageId) && (0, optional_1.isNothing)(eventFields["page"])) {
            const bagValue = configurationProvider.compoundSeparator(topic);
            const separator = (0, optional_1.isSome)(bagValue) ? (0, optional_1.unwrapOptional)(bagValue) : "_";
            eventFields["page"] = `${pageType}${separator}${pageId}`;
        }
        // - Field value resolution reduction.
        const rules = configurationProvider.deResolutionRules(topic);
        this.applyDeResolutionRules(eventFields, rules);
    }
    /**
     * Apply the rules specific to the `media` event.
     *
     * @param eventFields - The fields which will be used to construct a built event.
     */
    decorateMediaEventEvents(eventFields) {
        const eventFieldsReader = new object_reader_1.ObjectReader(eventFields);
        const position = eventFieldsReader.asNumber("position");
        if ((0, optional_1.isSome)(position)) {
            eventFields["position"] = Math.round(position);
        }
    }
    // MARK: Decorating Event Fields
    /**
     * Lint metrics event fields by applying the common business rules to a given fields blob.
     *
     * @remarks
     *
     * Note: A deep copy of event fields is created by linter using `JSON.parse(JSON.stringify(eventFields))`.
     * The original event fields are not modified.
     *
     * @param eventFields - The fields to decorate.
     * @param context - The additional event linter context to be passed to all
     * event linter rules. This is a free-form object so clients can pass custom
     * context information.
     * @returns Decorated fields ready for creating a metrics event.
     */
    lint(eventFields, context = {}) {
        const eventFieldsReader = new object_reader_1.ObjectReader(eventFields);
        const eventType = eventFieldsReader.asString("eventType");
        if (this.options.isLoggingEnabled) {
            console.log(`Building event for event type: ${eventType !== null && eventType !== void 0 ? eventType : "<null>"}`);
        }
        // Make sure we have a deep copy of an object.
        const decoratedEventFields = JSON.parse(JSON.stringify(eventFields));
        const value = eventFieldsReader.asString("topic");
        const topic = (0, optional_1.isSome)(value) ? (0, optional_1.unwrapOptional)(value) : this.options.defaultTopic;
        this.decorateCommonEventFields(decoratedEventFields, topic);
        switch (eventType) {
            case "media" /* MetricsEventType.media */:
                this.decorateMediaEventEvents(decoratedEventFields);
                break;
            default:
                break;
        }
        for (const rule of this.options.rules) {
            rule.apply(decoratedEventFields, context);
        }
        return {
            fields: decoratedEventFields,
        };
    }
}
exports.EventLinter = EventLinter;
//# sourceMappingURL=event-linter.js.map