import { isNothing } from "@jet/environment";
import { asInterface, asJSONData, asString, isDefinedNonNull, isDefinedNonNullNonEmpty, isNull, traverse, } from "../../foundation/json-parsing/server-data";
/**
 * This class is used as a singleton to track whether we should use the crossfire attribution from the JS purchase
 * configuration, this is to remedy the following issue:
 *
 * rdar://96291594 ([Sydney][Regression][App Store][Clickstream][iTMS11] RefApp missing from page event - (iOS) Sydney20A312a)
 *
 * The flow of how this object is used is as follows:
 *
 * 1. Deeplink comes in and is routed to the product page controller, containing the crossfire referrer data
 * 2. We build the product page model and pass the referrer data along
 * 3. As we're building the page model for this product page, we check to see if we have valid referrer data. If we do
 * we call into `MetricsReferralContext.beginReferralContextForProduct(appProductId)`, which will create store a unique
 * referral context identifier for this product page. This identifier will be used to make sure we're only every dealing
 * with the original product page.
 * 4. When we create the pageEvent for this product page we modify all the event fields to include unique referral context id,
 * using `MetricsReferralContext.addReferralContextToMetricsFieldsIfNecessary(metricsFields)`
 * 5. The referrer data is then added to the purchase configuration for the product lockup's buy button, which comes back to us
 * at time of purchase decoration
 * 6. Later when the user taps ont the product buy button and we enter the purchase decoration flow, we check to see if we should
 * skip adding the native referrer data to the purchase buy params, which would clear out the referrer data since the page change,
 * event has cleared it out natively. We call `MetricsReferralContext.shouldUseJSReferralData` for this check.
 * 7. If any page event comes in to the metrics event linter, we check to see if it has a referral context id that is not the
 * current one, if it does we clear the current metrics referrer contents using `MetricsReferralContext.endReferralContextIfNecessaryForPageExitEvent(pageEvent)`
 * 8. Additionally in the event linter we always call into `MetricsReferralContext.removeReferralContextInfoFromMetricsEvent(metricsFields)`
 * which deletes the added referralContextId field.
 */
export class MetricsReferralContext {
    static createSharedMetricsReferralContext(objectGraph) {
        if (MetricsReferralContext.shared) {
            return;
        }
        MetricsReferralContext.shared = new MetricsReferralContext(objectGraph);
    }
    /**
     * Initializers
     */
    /**
     * @param objectGraph The object graph to use to determine if the referral context is needed.
     */
    constructor(objectGraph) {
        /**
         * Properties
         */
        /**
         * Identifier denoting that we should use the extRefUrl2 and extRefApp2 from the original purchase configuration
         * found in the purchase token, rather than using the native metrics data which would clear out the ref data
         * due to pageChange events.
         */
        this.currentReferral = null;
        if (objectGraph.host.isiOS) {
            this.isMetricsReferralContextRequired = true;
            this.isEventDetailClickEventOverrideNecessary = !objectGraph.host.isOSAtLeast(16, 2, 0);
        }
        else if (objectGraph.host.isMac) {
            this.isMetricsReferralContextRequired = objectGraph.host.isOSAtLeast(13, 0, 0);
            this.isEventDetailClickEventOverrideNecessary = false;
        }
        else {
            this.isMetricsReferralContextRequired = false;
            this.isEventDetailClickEventOverrideNecessary = false;
        }
    }
    /**
     * Returns whether we should use the JS referral data or not.
     */
    get shouldUseJSReferralData() {
        return this.isMetricsReferralContextRequired && isDefinedNonNull(this.currentReferral);
    }
    /**
     * Returns the current referral data for the active context
     */
    get activeReferralData() {
        if (!this.shouldUseJSReferralData) {
            return null;
        }
        if (this.currentReferral === null || !this.currentReferral.isActive) {
            return null;
        }
        return this.currentReferral.data;
    }
    /**
     * Setting Referral Data
     */
    /**
     * Called when we get a deep link into the product page and need to make sure we track
     * the referral data for this page.
     *
     * @param productId The id of the product the referral context is for.
     * @param referrerData The referral data for this product page.
     */
    setReferralDataForProduct(productId, referrerData) {
        var _a, _b, _c;
        if (!this.isMetricsReferralContextRequired || isNull(referrerData)) {
            return;
        }
        const extRefApp2 = (_a = asString(referrerData, "app")) !== null && _a !== void 0 ? _a : null;
        const extRefUrl2 = (_b = asString(referrerData, "externalUrl")) !== null && _b !== void 0 ? _b : null;
        const kind = (_c = asInterface(referrerData, "kind")) !== null && _c !== void 0 ? _c : null;
        this.currentReferral = {
            id: `${productId}_${Date.now()}`,
            data: {
                extRefApp2,
                extRefUrl2,
                kind,
                refUrl: null,
            },
            isActive: false,
            productPageExtensionInfo: null,
        };
    }
    /**
     * Called when we are linting a page event, if this page event is for a product page extension, and
     * we don't yet have an active crossfire referral context, we need to make sure we start one, since there
     * should always be one in this case. The fact that there is not means that the pageChange event cleared it out
     * natively.
     *
     * Additionally we're going to add the referral context id to the page event so we can track it later, and know
     * when to end the referral context.
     *
     * @param pageEvent The page event that is currently being linted, so we can check to see if we're on a product page,
     * in the product page extension.
     */
    setReferralDataForProductPageExtensionIfNecessary(pageEvent) {
        var _a, _b;
        if (!this.isMetricsReferralContextRequired) {
            return;
        }
        const productId = asString(pageEvent, "pageId");
        const refApp = asString(pageEvent, "refApp");
        if (!MetricsReferralContextUtil.isProductPageExtension(pageEvent) ||
            !MetricsReferralContextUtil.isValidPageEvent(pageEvent) ||
            isNull(productId) ||
            isNull(refApp)) {
            return;
        }
        const extRefUrl = (_a = asString(pageEvent, "extRefUrl")) !== null && _a !== void 0 ? _a : null;
        const refAppKindName = asString(pageEvent, "refAppType");
        let refAppKindContext;
        switch (refAppKindName) {
            case "trampoline":
                refAppKindContext = asJSONData(traverse(pageEvent, "trampolineContext"));
                break;
            case "widget":
                refAppKindContext = asJSONData(traverse(pageEvent, "widgetContext"));
                break;
            default:
                refAppKindContext = {};
        }
        const refUrl = (_b = asString(pageEvent, "refUrl")) !== null && _b !== void 0 ? _b : null;
        this.currentReferral = {
            id: `${productId}_${Date.now()}`,
            data: {
                extRefApp2: refApp,
                extRefUrl2: extRefUrl,
                refUrl: refUrl,
                kind: {
                    name: refAppKindName,
                    context: refAppKindContext,
                },
            },
            isActive: false,
            productPageExtensionInfo: {
                productId,
            },
        };
        this.addReferralContextToMetricsFieldsIfNecessary(pageEvent);
    }
    /**
     * Begin / End Metrics Referral Context
     */
    /**
     * Called when we get a deep link into the product page and need to make sure we track
     * whether the referral data should be used from the js configuration.
     *
     * @param pageEvent Some page event that may be associated with the current referral context.
     */
    beginReferralContextForPageIfNecessary(pageEvent) {
        if (!this.isMetricsReferralContextRequired || !MetricsReferralContextUtil.isValidPageEvent(pageEvent)) {
            return;
        }
        if (!MetricsReferralContextUtil.isReferralForEvent(this.currentReferral, pageEvent)) {
            return;
        }
        if (this.currentReferral !== null) {
            this.currentReferral.isActive = true;
        }
    }
    /**
     * Called when we get a pageExit event after the page event for the current deeplinked
     * product page, if there is one. This will reset the flag to use the native metrics. This should
     * always be the next pageExit event after the page enter event
     */
    endReferralContextIfNecessaryForPageEvent(pageExitEvent) {
        if (!this.isMetricsReferralContextRequired || !MetricsReferralContextUtil.isValidPageEvent(pageExitEvent)) {
            return;
        }
        if (!MetricsReferralContextUtil.isReferralForEvent(this.currentReferral, pageExitEvent)) {
            return;
        }
        this.currentReferral = null;
    }
    /**
     * Setting / Clearing Page Fields
     */
    /**
     * Called when we're building the metrics events for a product page, this way we can tag the events with the current
     * referral context id if there is one.
     *
     * @param pageMetricsFields The page event fields we can modify to track the current product page.
     */
    addReferralContextToMetricsFieldsIfNecessary(pageMetricsFields) {
        var _a;
        if (!this.isMetricsReferralContextRequired) {
            return;
        }
        pageMetricsFields[MetricsReferralContext.referralContextEventField] = (_a = this.currentReferral) === null || _a === void 0 ? void 0 : _a.id;
    }
    /**
     * Called when linting our metrics events so we can make sure to remove the referral context id, so its not sent to the server
     *
     * @param metricsEvent The metrics event we're currently linting.
     */
    removeReferralContextInfoFromMetricsEvent(metricsEvent) {
        if (!this.isMetricsReferralContextRequired) {
            return;
        }
        delete metricsEvent[MetricsReferralContext.referralContextEventField];
    }
    /**
     * Event Attribution
     */
    /**
     * If we have an active referral context, we need to make sure we add the referral data to the event.
     *
     * @param metricsEvent The metrics event we're currently linting.
     */
    addReferralDataToEventIfNecessary(metricsEvent) {
        if (isNull(this.activeReferralData)) {
            return;
        }
        if (!MetricsReferralContextUtil.shouldAddReferralDataToEvent(metricsEvent)) {
            return;
        }
        if (MetricsReferralContextUtil.isEventDetailsClickEvent(metricsEvent) &&
            !this.isEventDetailClickEventOverrideNecessary) {
            return;
        }
        if (MetricsReferralContextUtil.isEventDetailsClickEvent(metricsEvent)) {
            // Correct the `pageType` of this event for rdar://101302008 ([Sydney][App Store] [Clickstream][iTMS11] click event on EventDetails page from an app referral has incorrect pageType)
            // Then continue on and apply referral data.
            metricsEvent["pageType"] = "EventDetails";
        }
        metricsEvent["refApp"] = this.activeReferralData.extRefApp2;
        metricsEvent["extRefUrl"] = this.activeReferralData.extRefUrl2;
        if (isDefinedNonNullNonEmpty(this.activeReferralData.refUrl)) {
            metricsEvent["refUrl"] = this.activeReferralData.refUrl;
        }
        if (this.activeReferralData !== null && this.activeReferralData.kind !== null) {
            metricsEvent["refAppType"] = this.activeReferralData.kind.name;
            switch (metricsEvent["refAppType"]) {
                case "trampoline":
                    metricsEvent["trampolineContext"] = this.activeReferralData.kind.context;
                    break;
                case "widget":
                    metricsEvent["widgetContext"] = this.activeReferralData.kind.context;
                    break;
                default:
                    break;
            }
        }
    }
}
/**
 * They event field to use on a page event so we can determine later if this page event belongs to
 * the same deeplinked product page.
 */
MetricsReferralContext.referralContextEventField = "referralContextId";
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
class MetricsReferralContextUtil {
    /**
     * Check to see if the pageEvent is within the ProductPageExtension
     *
     * @param pageEvent The page event we're checking to see if its in an extension.
     * @returns Whether "app" for this event is a valid type
     */
    static isProductPageExtension(pageEvent) {
        const app = asString(pageEvent, "app");
        return app === MetricsReferralContextUtil.productPageExtensionAppId;
    }
    /**
     * Check to see if the current page event is for a product page.
     *
     * @param pageEvent The page event we're checking to see if its a product page.
     * @returns Whether "pageType" for this event is a valid type
     */
    static isValidPageEvent(pageEvent) {
        const pageType = asString(pageEvent, "pageType");
        if (isNothing(pageType)) {
            return false;
        }
        return MetricsReferralContextUtil.validPageEventTypes.has(pageType);
    }
    /**
     * This method will check the `referralContextEventField` to see if it matches the current referral.
     *
     * @param referral The current metrics referral taken from the referral context
     * @param event Some event to test whether there is an associated referral context. And if so
     * if that referral context matches.
     */
    static isReferralForEvent(referral, event) {
        var _a;
        if (isNull(referral)) {
            return false;
        }
        const referralContextId = event[MetricsReferralContext.referralContextEventField];
        const productId = asString(event, "pageId");
        if (isDefinedNonNull(referralContextId)) {
            return referralContextId === referral.id;
        }
        else if (MetricsReferralContextUtil.isProductPageExtension(event) && isDefinedNonNull(productId)) {
            // For product page extensions we do not get a chance to add the referralContextId to the
            // pageExit event so we need to check the productId to see if it matches the current referral.
            return productId === ((_a = referral === null || referral === void 0 ? void 0 : referral.productPageExtensionInfo) === null || _a === void 0 ? void 0 : _a.productId);
        }
        else {
            return false;
        }
    }
    static shouldAddReferralDataToEvent(event) {
        // Generally, we don't want to force referral data onto click events, but this is not true for In-App Events (IAE):
        // rdar://101399254 ([Sydney] [App Store][Clickstream][iTMS11] Missing refApp and extRefURL on IAE Click Open events through App/Web Referrals)
        // This only applies prior to SydneyC, as this was fixed natively there.
        if (event.eventType === "click") {
            return this.isEventDetailsClickEvent(event);
        }
        return true;
    }
    /**
     * Check whether the event is for a click on an In-App Events (IAE) page.
     *
     * @param event The event we're checking to see if it's on a an IAE page.
     */
    static isEventDetailsClickEvent(event) {
        if (event.eventType !== "click") {
            return false;
        }
        const location = event.location;
        const currentLocation = location === null || location === void 0 ? void 0 : location[0];
        return isDefinedNonNull(currentLocation) && currentLocation.locationType === "EventDetails";
    }
}
/**
 * The identifier for the product page extension in a metrics page event
 */
MetricsReferralContextUtil.productPageExtensionAppId = "com.apple.AppStore.ProductPageExtension";
/**
 * The identifier used for the pageType field of an app event page event.
 */
MetricsReferralContextUtil.eventDetailsPageType = "EventDetails";
/**
 * The set of valid page types for a product page, page event
 */
MetricsReferralContextUtil.validPageEventTypes = new Set([
    "Software",
    "SoftwareBundle",
    MetricsReferralContextUtil.eventDetailsPageType,
]);
//# sourceMappingURL=metrics-referral-context.js.map