//
//  offers.ts
//  AppStoreKit
//
//  Created by Kevin MacWhinnie on 8/15/16.
//  Copyright (c) 2016 Apple Inc. All rights reserved.
//
import * as validation from "@jet/environment/json/validation";
import { isNothing, isSome } from "@jet/environment/types/optional";
import * as models from "../../api/models";
import * as serverData from "../../foundation/json-parsing/server-data";
import * as mediaAttributes from "../../foundation/media/attributes";
import * as dateUtil from "../../foundation/util/date-util";
import * as objects from "../../foundation/util/objects";
import { MetricsIdentifierType } from "../../foundation/metrics/metrics-identifiers-cache";
import * as client from "../../foundation/wrappers/client";
import * as ageRatings from "../content/age-ratings";
import * as artworkBuilder from "../content/artwork/artwork";
import * as contentAttributes from "../content/attributes";
import * as content from "../content/content";
import * as contentDeviceFamily from "../content/device-family";
import * as gameController from "../content/game-controller";
import * as sad from "../content/sad";
import * as filtering from "../filtering";
import * as links from "../linking/os-update-links";
import * as lockups from "../lockups/lockups";
import { adBuyParamKeys } from "../metrics/helpers/buy";
import * as metricsHelpersClicks from "../metrics/helpers/clicks";
import * as metricsUtil from "../metrics/helpers/util";
import * as productVariant from "../product-page/product-page-variants";
import { externalPurchasesPlacementIsEnabled, hasExternalPurchasesForData } from "./external-purchases";
/**
 * Create a purchase configuration (aka purchase token) from the provided product and other data.
 * @param objectGraph The App Store object graph.
 * @param product The data for the product.
 * @param buyParams The buy params being passed through the offer.
 * @param isPreorder Whether the purchase is a pre-order.
 * @param excludeAttribution Whether attribution should be excluded for this purchase.
 * @param pageInformation The information for the page the purchase will take place on.
 * @param metricsPlatformDisplayStyle The platform display style for the purchase.
 * @param options The MetricsClickOptions.
 * @param referrerData Referrer data for the purchase.
 * @param isDefaultBrowser Whether the install is a default browser install.
 * @returns A complete PurchaseConfiguration.
 */
export function purchaseConfigurationFromProduct(objectGraph, product, buyParams, isPreorder, excludeAttribution, pageInformation, metricsPlatformDisplayStyle, options, referrerData, isDefaultBrowser) {
    return validation.context("purchaseConfigurationFromProduct", () => {
        const appTitle = mediaAttributes.attributeAsString(product, "name");
        let vendor = mediaAttributes.attributeAsString(product, "artistName");
        if (!vendor) {
            vendor = "test";
        }
        const bundleId = sad.systemApps(objectGraph).bundleIdFromData(product);
        const appPlatforms = content.supportedAppPlatformsFromData(objectGraph, product);
        const lineItem = mediaAttributes.attributeAsString(product, "iad.lineItem");
        const preflightPackageUrl = contentAttributes.contentAttributeAsString(objectGraph, product, "preflightPackageUrl");
        const supportsArcade = content.isArcadeSupported(objectGraph, product);
        const supportsMacOSCompatibleIOSBinary = content.supportsMacOSCompatibleIOSBinaryFromData(objectGraph, product, objectGraph.appleSilicon.isSupportEnabled);
        const supportsVisionOSCompatibleIOSBinary = content.supportsVisionOSCompatibleIOSBinaryFromData(objectGraph, product);
        const extRefApp2 = serverData.asString(referrerData, "app");
        const extRefUrl2 = serverData.asString(referrerData, "externalUrl");
        // Get the Serial Numbers from the devices we want to download from
        const remoteDownloadIdentifiers = supportsVisionDownloadFromVisionCompanion(objectGraph, product)
            ? objectGraph.client.remoteDownloadIdentifiers
            : [];
        const hasMacIPAPackage = hasMacIPAPackageForData(objectGraph, product);
        const contentRating = ageRatings.value(objectGraph, product, true);
        const purchaseConfiguration = new models.PurchaseConfiguration(buyParams, vendor, appTitle, bundleId, appPlatforms, isPreorder, excludeAttribution, metricsPlatformDisplayStyle, lineItem, false, preflightPackageUrl, supportsArcade, supportsMacOSCompatibleIOSBinary, supportsVisionOSCompatibleIOSBinary, options.inAppEventId, extRefApp2, extRefUrl2, undefined, content.appBinaryTraitsFromData(objectGraph, product), isDefaultBrowser, remoteDownloadIdentifiers, hasMacIPAPackage, contentRating);
        purchaseConfiguration.pageInformation = { ...pageInformation };
        purchaseConfiguration.productVariantData = productVariant.productVariantDataForData(objectGraph, product);
        purchaseConfiguration.targetType = options.targetType;
        purchaseConfiguration.metricsKind = options.kind;
        return purchaseConfiguration;
    });
}
/**
 * Extract the offer data from the provided data object.
 * @param objectGraph The App Store object graph.
 * @param data The data blob from which to extract the offer data.
 * @param attributePlatformOverride An override platform, from which to fetch the offer data.
 * @returns A `JSONData` with offer data.
 */
export function offerDataFromData(objectGraph, data, attributePlatformOverride = undefined) {
    return validation.context("offerDataFromData", () => {
        const offers = contentAttributes.contentAttributeAsArrayOrEmpty(objectGraph, data, "offers", attributePlatformOverride);
        if (offers.length === 0) {
            return null;
        }
        return offers[0];
    });
}
export function offerDataFromMarketingItem(objectGraph, marketingItemData) {
    const offersArray = mediaAttributes.attributeAsArrayOrEmpty(marketingItemData, "offers");
    if (offersArray.length === 0) {
        return null;
    }
    return offersArray[0];
}
export function updateOfferDataFromData(objectGraph, data) {
    return validation.context("updateOfferDataFromData", () => {
        const offers = contentAttributes.contentAttributeAsArrayOrEmpty(objectGraph, data, "offers");
        if (offers.length === 0) {
            return null;
        }
        for (const offerDict of offers) {
            const type = serverData.asString(offerDict, "type");
            if (type === "update") {
                return offerDict;
            }
        }
        return null;
    });
}
/**
 * Create an offer action using the provided offer data.
 * @param objectGraph The App Store object graph.
 * @param offerData The offer data from the original data.
 * @param data The data for the product.
 * @param isPreorder Whether the purchase is a pre-order.
 * @param includeBetaApps Whether beta apps should be included.
 * @param metricsPlatformDisplayStyle The platform display style for the purchase.
 * @param options The MetricsClickOptions.
 * @param context The context in which the offer will be placed.
 * @param referrerData Referrer data for the purchase.
 * @param isDefaultBrowser Whether the install is a default browser install.
 * @returns A complete OfferAction.
 */
export function offerActionFromOfferData(objectGraph, offerData, data, isPreorder, includeBetaApps, metricsPlatformDisplayStyle, options, context = "default", referrerData, isDefaultBrowser, parentAdamId) {
    return validation.context(`offerActionFromOfferData: ${data.id}`, () => {
        var _a, _b, _c, _d, _e, _f, _g;
        /* Buy Params */
        let buyParams = serverData.asString(offerData, "buyParams");
        if (serverData.isNull(buyParams)) {
            validation.unexpectedNull("ignoredValue", "string", "item.offer.buyParams");
            return null;
        }
        // This was added as a workaround for:
        // <rdar://problem/33567931> Bundles: Suppress CMB dialog
        //
        // TODO: We need to suppress this dialog until CmB is completed:
        // <rdar://problem/32007058> Bundles: Implement complete my bundle
        //
        // TODO: Unsuppress when commerce has new intrim alerts ready:
        // <rdar://problem/33732045> Bundles: Remove Supressing CMB dialog
        if (data.type === "app-bundles") {
            if (buyParams.indexOf("rebuy") >= 0) {
                buyParams = buyParams.replace("rebuy=false", "rebuy=true");
            }
            else {
                if (buyParams.length > 0) {
                    buyParams += "&";
                }
                buyParams += "rebuy=true";
            }
        }
        if (serverData.isDefinedNonNullNonEmpty(options.inAppEventId)) {
            if (buyParams.length > 0) {
                buyParams += "&";
            }
            buyParams += `mtInAppEventId=${options.inAppEventId}`;
        }
        if (options.isAdvert) {
            const iAdPlacementId = (_b = (_a = options.pageInformation) === null || _a === void 0 ? void 0 : _a.iAdInfo) === null || _b === void 0 ? void 0 : _b.placementId;
            if (serverData.isDefinedNonNull(iAdPlacementId)) {
                if (buyParams.length > 0) {
                    buyParams += "&";
                }
                buyParams += `${adBuyParamKeys.placementId}=${iAdPlacementId}`;
            }
            const iAdContainerId = (_d = (_c = options.pageInformation) === null || _c === void 0 ? void 0 : _c.iAdInfo) === null || _d === void 0 ? void 0 : _d.containerId;
            if (serverData.isDefinedNonNull(iAdContainerId)) {
                if (buyParams.length > 0) {
                    buyParams += "&";
                }
                buyParams += `${adBuyParamKeys.containerId}=${iAdContainerId}`;
            }
            const iAdTemplateType = (_f = (_e = options.pageInformation) === null || _e === void 0 ? void 0 : _e.iAdInfo) === null || _f === void 0 ? void 0 : _f.clickFields["iAdTemplateType"];
            if (serverData.isDefinedNonNull(iAdTemplateType)) {
                if (buyParams.length > 0) {
                    buyParams += "&";
                }
                buyParams += `${adBuyParamKeys.templateType}=${iAdTemplateType}`;
            }
        }
        /* Adam Id */
        const adamId = data.id;
        if (serverData.isNull(adamId)) {
            validation.unexpectedNull("ignoredValue", "string", "item.offer.id");
            return null;
        }
        /* Purchase Configuration */
        const purchaseConfiguration = purchaseConfigurationFromProduct(objectGraph, data, buyParams, isPreorder, options.excludeAttribution, options.pageInformation, metricsPlatformDisplayStyle, options, referrerData, isDefaultBrowser);
        const action = internalOfferActionFromOfferData(objectGraph, offerData, adamId, purchaseConfiguration, includeBetaApps, context, (_g = options.isAdvert) !== null && _g !== void 0 ? _g : false, parentAdamId);
        metricsHelpersClicks.addBuyEventToOfferActionOnPage(objectGraph, action, options, isPreorder, isDefaultBrowser);
        return action;
    });
}
/**
 * Determines whether a given offer action is a free.
 * @param offerAction   The offer action to check.
 * @returns true if it's a free
 */
export function isFreeFromOfferAction(objectGraph, offerAction) {
    return serverData.isNull(offerAction) || serverData.isNull(offerAction.price) || offerAction.price === 0;
}
export function expectedReleaseDateFromData(objectGraph, data) {
    return validation.context("expectedReleaseDateFromData", () => {
        const expectedReleaseDateString = mediaAttributes.attributeAsString(data, "offers.0.expectedReleaseDate");
        return dateUtil.parseDateOmittingTimeFromString(expectedReleaseDateString);
    });
}
/**
 * Determines the price for the offer data.
 * @param {JSONData} offer The offer data from which to determine the price.
 * @returns {number} The price for the offer.
 */
export function priceFromOfferData(objectGraph, offer) {
    const type = serverData.asString(offer, "type");
    if (type === "buy" || type === "complete" || type === "preorder") {
        return serverData.asNumber(offer, "price");
    }
    return null;
}
/**
 * Common builder for `OfferAction` model object. Metrics events should be added by calling function.
 * @param offer JSONData of offer from platform data.
 * @param adamId AdamId of offered product.
 * @param purchaseConfiguration Configuration to use in offer.
 * @param includeBetaApps Whether to include beta apps in the offer button configuration.
 * @param context The context within which this offer button is visible.
 * @param isAd Whether offer button is for an ad.
 * @returns An `OfferAction` with given parameters.
 */
function internalOfferActionFromOfferData(objectGraph, offer, adamId, purchaseConfiguration, includeBetaApps, context = "default", isAd = false, parentAdamId) {
    return validation.context("offerActionFromOfferData", () => {
        // Localize action title
        const type = serverData.asString(offer, "type");
        const useAdsLocale = isAd && context === "default" && isSome(objectGraph.bag.adsOverrideLanguage);
        const adsOverrideLocalizer = useAdsLocale ? objectGraph.adsLoc : objectGraph.loc;
        let actionTitle;
        switch (type) {
            case "get":
                if (context === "flowPreview") {
                    actionTitle = objectGraph.loc.string("OfferButton.FlowPreview.Get", "Get");
                }
                else {
                    const watchOSActionTitleKey = "OfferButton.Title.Get.TitleCase";
                    const actionTitleKey = objectGraph.client.isWatch ? watchOSActionTitleKey : "OfferButton.Title.Get";
                    actionTitle = adsOverrideLocalizer.string(actionTitleKey);
                }
                break;
            case "preorder":
                if (context === "flowPreview") {
                    actionTitle = objectGraph.loc.string("OfferButton.FlowPreview.Preorder", "Pre-Order");
                }
                else {
                    actionTitle = adsOverrideLocalizer.string("OfferButton.Title.Get");
                }
                break;
            default:
                actionTitle = type;
        }
        let actionPrice = null;
        let actionPriceFormatted = null;
        const price = priceFromOfferData(objectGraph, offer);
        if (price > 0) {
            actionPrice = price;
            actionPriceFormatted = serverData.asString(offer, "priceFormatted");
        }
        const expectedReleaseDateString = serverData.asString(offer, "expectedReleaseDate");
        const expectedReleaseDate = dateUtil.parseDateOmittingTimeFromString(expectedReleaseDateString);
        const action = new models.OfferAction(actionTitle, adamId, purchaseConfiguration, parentAdamId);
        action.price = actionPrice;
        action.priceFormatted = actionPriceFormatted;
        action.expectedReleaseDate = expectedReleaseDate;
        action.includeBetaApps = includeBetaApps;
        return action;
    });
}
function wrapOfferActionForPreorder(objectGraph, action, data, options, context) {
    if (serverData.isNull(action)) {
        return null;
    }
    const preorderStateAction = cancellablePreorderOfferStateAction(objectGraph, data, action, false, options);
    preorderStateAction.buyAction = action;
    return preorderStateAction;
}
export function wrapOfferActionIfNeeded(objectGraph, action, data, isPreorder, metricsOptions, context = "default", clientIdentifierOverride = null, shouldNavigateToProductPage = false) {
    if (isNothing(action)) {
        return null;
    }
    // Emit update links for macOS installers
    if (content.isMacOSInstaller(objectGraph, data)) {
        if (context === "flowPreview") {
            return null;
        }
        // When `default`, i.e. not product page, show "VIEW".
        if (context === "default") {
            return lockups.actionFromData(objectGraph, data, metricsOptions, null);
        }
        // Otherwise punt to Software Update
        const matchingOSBundle = contentAttributes.contentAttributeAsString(objectGraph, data, "bundleId");
        if (serverData.isDefinedNonNullNonEmpty(matchingOSBundle)) {
            const installUpdateUrl = links.osUpdateUrl("mac", matchingOSBundle);
            if (isSome(installUpdateUrl)) {
                const updateAction = new models.ExternalUrlAction(installUpdateUrl);
                return new models.OfferStateAction(action.adamId, updateAction);
            }
        }
    }
    if (context === "default") {
        if (data.type === "app-bundles" ||
            content.isUnsupportedByCurrentCompanion(objectGraph, data) ||
            shouldNavigateToProductPage) {
            return lockups.actionFromData(objectGraph, data, metricsOptions, null);
        }
    }
    // Wrap non-Arcade preorders to enable view to drill into product page (except for tvOS)
    // Note: Arcade pre-orders are handled by `wrapArcadeAppOfferActionIfNeeded`.
    if (isPreorder && objectGraph.client.deviceType !== "tv" && !content.isArcadeSupported(objectGraph, data)) {
        const wrappedPreorderAction = wrapOfferActionForPreorder(objectGraph, action, data, metricsOptions, context);
        if (wrappedPreorderAction !== null) {
            return wrappedPreorderAction;
        }
    }
    // Configure chain of dialogs for vision-only purchase.
    // Note: Arcade purchases are handled by `wrapArcadeAppOfferActionIfNeeded`.
    const isVisionOnlyApp = contentDeviceFamily.dataOnlyHasDeviceFamily(objectGraph, data, "realityDevice");
    const isCompanion = objectGraph.client.isCompanionVisionApp;
    if (!content.isArcadeSupported(objectGraph, data) &&
        (isCompanion || (isVisionOnlyApp && objectGraph.client.deviceType !== "vision"))) {
        const isFree = isFreeFromOfferAction(objectGraph, action);
        const isArcade = content.isArcadeSupported(objectGraph, data);
        return visionAppActionForBuyAction(objectGraph, action, data, isFree, isArcade);
    }
    // Configure chain of dialogs for tvOS-only purchase.
    const isTvOnlyApp = contentDeviceFamily.dataOnlyHasDeviceFamily(objectGraph, data, "tvos");
    if (isTvOnlyApp && objectGraph.client.deviceType !== "tv") {
        const requiresGameController = contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "requiresGameController");
        return tvOnlyAppActionForBuyAction(objectGraph, action, requiresGameController);
    }
    // Configure dialogs for watchOS-only purchase.
    const isWatchOnlyApp = !contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "isDeliveredInIOSAppForWatchOS") &&
        contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "isStandaloneForWatchOS");
    if (isWatchOnlyApp && objectGraph.client.deviceType !== "watch") {
        return watchOnlyAppActionForBuyAction(objectGraph, action);
    }
    // App is not supported on paired watch OS version
    const minimumWatchOSVersionString = contentAttributes.contentAttributeAsString(objectGraph, data, "minimumWatchOSVersion");
    if (serverData.isDefinedNonNullNonEmpty(clientIdentifierOverride) &&
        clientIdentifierOverride === client.watchIdentifier &&
        isSome(minimumWatchOSVersionString) &&
        content.isActivePairedWatchOSBelowVersion(objectGraph, minimumWatchOSVersionString)) {
        return watchUpdateRequiredActionForBuyAction(objectGraph, action, minimumWatchOSVersionString);
    }
    if (shouldWrapOffer(objectGraph, data)) {
        // Handle all Arcade offers.
        if (content.isArcadeSupported(objectGraph, data)) {
            return wrapArcadeAppOfferActionIfNeeded(objectGraph, action, data, isPreorder, context, arcadeSubscribeContextFromOfferContext(objectGraph, context, isPreorder), metricsOptions);
        }
        // Wrap offers on tvOS in `OfferAlertAction` in order to check requirements and confirm.
        const offerActionIndex = createOfferAlertActionIfNeeded(objectGraph, action, data, isPreorder, metricsOptions);
        const offerAlertAction = offerActionIndex.startAction;
        // Handle all NON-Arcade pre-orders.
        if (isPreorder) {
            const preorderStateAction = cancellablePreorderOfferStateAction(objectGraph, data, offerActionIndex.underlyingOfferAction, false, metricsOptions);
            preorderStateAction.buyAction = offerAlertAction;
            return preorderStateAction;
        }
        // Handle NON-Arcade NON-pre-orders.
        return offerAlertAction;
    }
    else {
        // Arcade offers do *not* get wrapped in a two-phase confirmation.
        return wrapOfferInTwoPhasedConfirmationActionIfNeeded(objectGraph, action, isPreorder, metricsOptions);
    }
}
function shouldWrapOffer(objectGraph, data) {
    if (content.isArcadeSupported(objectGraph, data)) {
        return true;
    }
    if (objectGraph.client.isTV || objectGraph.client.isVision) {
        return true;
    }
    return false;
}
function arcadeSubscribeContextFromOfferContext(objectGraph, offerContext, isArcadePreorder) {
    if (isArcadePreorder) {
        return models.marketingItemContextFromString("arcadeComingSoon");
    }
    switch (offerContext) {
        case "productPage":
            return models.marketingItemContextFromString("productPage");
        case "default":
        case "flowPreview":
            return models.marketingItemContextFromString("groupingLockup");
        default:
            return models.marketingItemContextFromString("generic");
    }
}
export function appInstallActionFromAppData(objectGraph, data, offerContext, marketingItemContext, isPreorder, metricsOptions, clientIdentifierOverride = null) {
    switch (marketingItemContext) {
        case models.marketingItemContextFromString("productPage"):
        case models.marketingItemContextFromString("groupingLockup"):
            const primaryIcon = content.iconFromData(objectGraph, data, {
                useCase: 3 /* content.ArtworkUseCase.LockupIconLarge */,
            });
            const metricsClickOptions = metricsHelpersClicks.clickOptionsForLockup(objectGraph, data, metricsOptions);
            const offerData = offerDataFromData(objectGraph, data);
            const metricsPlatformDisplayStyle = metricsUtil.metricsPlatformDisplayStyleFromData(objectGraph, data, primaryIcon, clientIdentifierOverride);
            return offerActionFromOfferData(objectGraph, offerData, data, isPreorder, false, metricsPlatformDisplayStyle, metricsClickOptions, offerContext);
        default:
            return null;
    }
}
/**
 * Ensures that the provided action is properly wrapped if it is for an Arcade app.
 * @param objectGraph The object graph.
 * @param action The naked offer action.
 * @param underlyingAction The buy action for tvOS.
 * @param data The data for the product.
 * @param isPreorder Indicates whether or not this offer action is for a preorder.
 * @param offerContext Contextual information about this offer.
 * @param marketingItemContext Contextual information about the marketing item.
 * @param metricsOptions Contextual information about metrics.
 */
function wrapArcadeAppOfferActionIfNeeded(objectGraph, offerAction, data, isPreorder, offerContext, marketingItemContext, metricsOptions) {
    if (!content.isArcadeSupported(objectGraph, data)) {
        return offerAction;
    }
    switch (objectGraph.client.deviceType) {
        case "tv":
            return wrapArcadeAppOfferActionForTV(objectGraph, offerAction, data, isPreorder, marketingItemContext, metricsOptions);
        case "vision":
            return wrapArcadeAppOfferActionForVision(objectGraph, offerAction, data, isPreorder, marketingItemContext, metricsOptions);
        default:
            return wrapArcadeAppOfferActionForOtherPlatforms(objectGraph, offerAction, data, isPreorder, offerContext, marketingItemContext, metricsOptions);
    }
}
/**
 * Wrap the offer action for Arcade app in TV to check requirements, presenting upsell and confirm.
 *
 * If the app is preorder app:
 *  - It should show `OfferAlertAction` first to check requirements (eg: game controller)
 *  - After alert is shown, perform the preorder action to subscribe/unsubscribe coming soon order.
 *  - Then show the upsell with rate limit applied.
 *  - If user purchase subscription success, perform the buy action.
 *
 * Otherwise:
 *  - Show the `OfferAlertAction` first to check requirements (eg: game controller)
 *  - After alert is shown, show the upsell action.
 *  - If user purchase subscription success, perform the buy action.
 *
 * @param objectGraph The object graph.
 * @param action The offer action that perform requirements check before buy.
 * @param underlyingAction The buy action for tvOS.
 * @param data The data for the product.
 * @param isPreorder Indicates whether or not this offer action is for a preorder.
 * @param marketingItemContext Contextual information about the marketing item.
 * @param metricsOptions Contextual information about metrics.
 */
function wrapArcadeAppOfferActionForTV(objectGraph, offerAction, data, isPreorder, marketingItemContext, metricsOptions) {
    var _a, _b, _c, _d;
    // Wrap offers on tvOS in `OfferAlertAction` in order to check requirements and confirm.
    const offerActionIndex = createTVOfferAlertActionIfNeeded(objectGraph, offerAction, null, data, isPreorder, metricsOptions);
    const offerAlertAction = offerActionIndex.startAction;
    const buyAction = offerActionIndex.underlyingOfferAction;
    const isContextual = models.isContextualUpsellContext(marketingItemContext);
    const upsellRequestInfo = new models.MarketingItemRequestInfo("arcade", marketingItemContext, objectGraph.bag.metricsTopic, data.id);
    if (isSome((_b = (_a = metricsOptions.pageInformation) === null || _a === void 0 ? void 0 : _a.searchTermContext) === null || _b === void 0 ? void 0 : _b.term)) {
        upsellRequestInfo.metricsOverlay["searchTerm"] = (_c = metricsOptions.pageInformation.searchTermContext) === null || _c === void 0 ? void 0 : _c.term;
    }
    const metricsIdentifierFields = (_d = objectGraph.metricsIdentifiersCache) === null || _d === void 0 ? void 0 : _d.getMetricsFieldsForTypes([
        MetricsIdentifierType.user,
        MetricsIdentifierType.client,
        MetricsIdentifierType.canonical,
    ]);
    if (isSome(metricsIdentifierFields)) {
        upsellRequestInfo.metricsOverlay = {
            ...upsellRequestInfo.metricsOverlay,
            ...metricsIdentifierFields,
        };
    }
    if (isContextual) {
        upsellRequestInfo.purchaseSuccessAction = buyAction;
        upsellRequestInfo.carrierLinkSuccessAction = buyAction;
    }
    const upsellAction = new models.FlowAction("upsellMarketingItem");
    upsellAction.pageData = upsellRequestInfo;
    if (metricsOptions && metricsOptions.pageInformation) {
        upsellAction.referrerUrl = metricsOptions.pageInformation.pageUrl;
    }
    // Always add buyParams to actionDetails of offer actions so that we have them when reporting events for subscription buys
    if (offerAlertAction instanceof models.OfferAction) {
        metricsOptions.actionDetails = {
            buyParams: offerAlertAction.purchaseConfiguration.buyParams,
            ...metricsOptions.actionDetails,
        };
    }
    metricsHelpersClicks.addClickEventToArcadeBuyInitiateAction(objectGraph, upsellAction, metricsOptions);
    // Arcade Pre-order logic
    if (isPreorder) {
        const preorderStateAction = cancellablePreorderOfferStateAction(objectGraph, data, buyAction, true, metricsOptions); // For subscribers
        preorderStateAction.buyAction = offerAlertAction;
        const preorderOrCancelSubscribePageAction = cancellablePreorderOfferStateAction(objectGraph, data, buyAction, true, metricsOptions); // For non-subscribers
        preorderOrCancelSubscribePageAction.buyAction = offerAlertAction;
        preorderStateAction.subscribePageAction = preorderOrCancelSubscribePageAction;
        buyAction.buyCompletedAction = arcadePreOrderBuyCompleteActionForTV(objectGraph, upsellAction);
        return preorderStateAction;
    }
    else {
        // If user is not subscribed to Arcade: show OfferAlertAction then display upsell
        // Otherwise: we want to show OfferAlertAction without upsell
        const stateAction = new models.OfferStateAction(data.id, offerAlertAction);
        const wrappedUpsellAction = createTVOfferAlertActionIfNeeded(objectGraph, offerAction, upsellAction, data, isPreorder, metricsOptions).startAction;
        stateAction.subscribePageAction = wrappedUpsellAction;
        return stateAction;
    }
}
/**
 * Wrap the offer action for Arcade app in Vision to check requirements, presenting upsell and confirm.
 *
 * If the app is preorder app:
 *  - It should show `OfferAlertAction` first to check requirements (eg: game controller)
 *  - After alert is shown, perform the preorder action to subscribe/unsubscribe coming soon order.
 *  - Then show the upsell with rate limit applied.
 *  - If user purchase subscription success, perform the buy action.
 *
 * Otherwise:
 *  - Show the `OfferAlertAction` first to check requirements (eg: game controller)
 *  - After alert is shown, show the upsell action.
 *  - If user purchase subscription success, perform the buy action.
 *
 * @param objectGraph The object graph.
 * @param action The offer action that perform requirements check before buy.
 * @param underlyingAction The buy action for tvOS.
 * @param data The data for the product.
 * @param isPreorder Indicates whether or not this offer action is for a preorder.
 * @param marketingItemContext Contextual information about the marketing item.
 * @param metricsOptions Contextual information about metrics.
 */
function wrapArcadeAppOfferActionForVision(objectGraph, offerAction, data, isPreorder, marketingItemContext, metricsOptions) {
    var _a, _b, _c, _d;
    // Wrap offers on tvOS in `OfferAlertAction` in order to check requirements and confirm.
    const offerActionIndex = createVisionOfferAlertActionIfNeeded(objectGraph, offerAction, null, data, isPreorder, metricsOptions);
    const offerAlertAction = offerActionIndex.startAction;
    const buyAction = offerActionIndex.underlyingOfferAction;
    const isContextual = models.isContextualUpsellContext(marketingItemContext);
    const upsellRequestInfo = new models.MarketingItemRequestInfo("arcade", marketingItemContext, objectGraph.bag.metricsTopic, data.id);
    if (isSome((_b = (_a = metricsOptions.pageInformation) === null || _a === void 0 ? void 0 : _a.searchTermContext) === null || _b === void 0 ? void 0 : _b.term)) {
        upsellRequestInfo.metricsOverlay["searchTerm"] = (_c = metricsOptions.pageInformation.searchTermContext) === null || _c === void 0 ? void 0 : _c.term;
    }
    const metricsIdentifierFields = (_d = objectGraph.metricsIdentifiersCache) === null || _d === void 0 ? void 0 : _d.getMetricsFieldsForTypes([
        MetricsIdentifierType.user,
        MetricsIdentifierType.client,
        MetricsIdentifierType.canonical,
    ]);
    if (isSome(metricsIdentifierFields)) {
        upsellRequestInfo.metricsOverlay = {
            ...upsellRequestInfo.metricsOverlay,
            ...metricsIdentifierFields,
        };
    }
    if (isContextual) {
        upsellRequestInfo.purchaseSuccessAction = buyAction;
        upsellRequestInfo.carrierLinkSuccessAction = buyAction;
    }
    const upsellAction = new models.FlowAction("upsellMarketingItem");
    upsellAction.pageData = upsellRequestInfo;
    if (metricsOptions && metricsOptions.pageInformation) {
        upsellAction.referrerUrl = metricsOptions.pageInformation.pageUrl;
    }
    // Always add buyParams to actionDetails of offer actions so that we have them when reporting events for subscription buys
    if (offerAlertAction instanceof models.OfferAction) {
        metricsOptions.actionDetails = {
            buyParams: offerAlertAction.purchaseConfiguration.buyParams,
            ...metricsOptions.actionDetails,
        };
    }
    metricsHelpersClicks.addClickEventToArcadeBuyInitiateAction(objectGraph, upsellAction, metricsOptions);
    // Arcade Pre-order logic
    if (isPreorder && buyAction !== null) {
        const preorderStateAction = cancellablePreorderOfferStateAction(objectGraph, data, buyAction, true, metricsOptions); // For subscribers
        preorderStateAction.buyAction = offerAlertAction;
        const preorderOrCancelSubscribePageAction = cancellablePreorderOfferStateAction(objectGraph, data, buyAction, true, metricsOptions); // For non-subscribers
        preorderOrCancelSubscribePageAction.buyAction = offerAlertAction;
        preorderStateAction.subscribePageAction = preorderOrCancelSubscribePageAction;
        buyAction.buyCompletedAction = arcadePreOrderBuyCompleteAction(objectGraph, upsellAction);
        return preorderStateAction;
    }
    else {
        // If user is not subscribed to Arcade: show OfferAlertAction then display upsell
        // Otherwise: we want to show OfferAlertAction without upsell
        const stateAction = new models.OfferStateAction(data.id, offerAlertAction);
        const wrappedUpsellAction = createVisionOfferAlertActionIfNeeded(objectGraph, offerAction, upsellAction, data, isPreorder, metricsOptions).startAction;
        stateAction.subscribePageAction = wrappedUpsellAction;
        return stateAction;
    }
}
/**
 * Wrap the offer action for Arcade app in MacOS, iOS, WatchOS to present the upsell or subscribe/unsubscribe preorder
 *
 * If the app is preorder app:
 *  - Wraps the offer action with `cancellablePreorderOfferStateAction` for subscribe/unsubscribe coming soon order.
 *  - After the preorder offer action completed, it will call the Upsell FlowAction with rate limited.
 *
 * Otherwise:
 *  - Create a new OfferState action
 *  - Create an Upsell FlowAction, and add the upsell action as `subscribePageAction`.
 *  - Add the offer action as `purchaseSuccessAction` or `carrierLinkSuccessAction` to the Upsell FlowAction.
 *
 * @param objectGraph The object graph.
 * @param action The offer action that perform requirements check before buy.
 * @param underlyingAction The buy action for tvOS.
 * @param data The data for the product.
 * @param isPreorder Indicates whether or not this offer action is for a preorder.
 * @param offerContext Contextual information about this offer.
 * @param marketingItemContext Contextual information about the marketing item.
 * @param metricsOptions Contextual information about metrics.
 * @returns the final action for offer button.
 */
function wrapArcadeAppOfferActionForOtherPlatforms(objectGraph, offerAction, data, isPreorder, offerContext, marketingItemContext, metricsOptions) {
    var _a, _b, _c, _d;
    const isContextual = models.isContextualUpsellContext(marketingItemContext);
    const upsellRequestInfo = new models.MarketingItemRequestInfo("arcade", marketingItemContext, objectGraph.bag.metricsTopic, data.id);
    if (isSome((_b = (_a = metricsOptions.pageInformation) === null || _a === void 0 ? void 0 : _a.searchTermContext) === null || _b === void 0 ? void 0 : _b.term)) {
        upsellRequestInfo.metricsOverlay["searchTerm"] = (_c = metricsOptions.pageInformation.searchTermContext) === null || _c === void 0 ? void 0 : _c.term;
    }
    const metricsIdentifierFields = (_d = objectGraph.metricsIdentifiersCache) === null || _d === void 0 ? void 0 : _d.getMetricsFieldsForTypes([
        MetricsIdentifierType.user,
        MetricsIdentifierType.client,
        MetricsIdentifierType.canonical,
    ]);
    if (isSome(metricsIdentifierFields)) {
        upsellRequestInfo.metricsOverlay = {
            ...upsellRequestInfo.metricsOverlay,
            ...metricsIdentifierFields,
        };
    }
    // If the host app is not set, and this is the isCompanionVisionApp and the supportsCompanionCheck is permitted,
    // update the host app to ClientIdentifier.VisionCompanion.
    if (objectGraph.props.enabled("supportsCompanionCheck") &&
        objectGraph.client.isCompanionVisionApp &&
        isNothing(upsellRequestInfo.metricsOverlay["hostApp"])) {
        upsellRequestInfo.metricsOverlay["hostApp"] = "com.apple.visionproapp" /* ClientIdentifier.VisionCompanion */;
    }
    if (isContextual) {
        upsellRequestInfo.purchaseSuccessAction = offerAction;
        upsellRequestInfo.carrierLinkSuccessAction = offerAction;
    }
    const upsellAction = new models.FlowAction("upsellMarketingItem");
    upsellAction.pageData = upsellRequestInfo;
    if (metricsOptions && metricsOptions.pageInformation) {
        upsellAction.referrerUrl = metricsOptions.pageInformation.pageUrl;
    }
    // Always add buyParams to actionDetails of offer actions so that we have them when reporting events for subscription buys
    metricsOptions.actionDetails = {
        buyParams: offerAction.purchaseConfiguration.buyParams,
        ...metricsOptions.actionDetails,
    };
    metricsHelpersClicks.addClickEventToArcadeBuyInitiateAction(objectGraph, upsellAction, metricsOptions);
    // Arcade Pre-order logic
    if (isPreorder) {
        // iOS and macOS
        if (offerAction instanceof models.OfferAction) {
            const preorderStateAction = cancellablePreorderOfferStateAction(objectGraph, data, offerAction, true, metricsOptions); // For subscribers
            preorderStateAction.buyAction = offerAction;
            const preorderOrCancelSubscribePageAction = cancellablePreorderOfferStateAction(objectGraph, data, offerAction, true, metricsOptions); // For non-subscribers
            preorderOrCancelSubscribePageAction.buyAction = offerAction;
            preorderStateAction.subscribePageAction = preorderOrCancelSubscribePageAction;
            offerAction.buyCompletedAction = arcadePreOrderBuyCompleteAction(objectGraph, upsellAction);
            return preorderStateAction;
        }
    }
    // Vision only, Arcade purchase
    const isVisionOnlyApp = contentDeviceFamily.dataOnlyHasDeviceFamily(objectGraph, data, "realityDevice");
    if (supportsVisionDownloadFromVisionCompanion(objectGraph, data) ||
        (isVisionOnlyApp && objectGraph.client.deviceType !== "vision")) {
        // Alert action for subscribed user with offer action
        const alertActionForSubscribed = visionAppAlertForBuyAction(objectGraph, offerAction, data, true, true);
        // Alert action for unsubscribed user with upsell action
        const alertActionForUnsubscribed = visionAppAlertForBuyAction(objectGraph, upsellAction, data, true, true);
        // Offer state action
        const offerStateAction = new models.OfferStateAction(data.id, alertActionForSubscribed);
        offerStateAction.subscribePageAction = new models.OfferStateAction(data.id, alertActionForUnsubscribed);
        return offerStateAction;
    }
    // No change
    const stateAction = new models.OfferStateAction(data.id, offerAction);
    stateAction.subscribePageAction = upsellAction;
    return stateAction;
}
/**
 * An offer state action for a preorder that is cancellable.
 * @param objectGraph The object graph.
 * @param data The app data.
 * @param action An offer action that will either purchase the app, or present a cancellation dialog.
 * @param isArcade Indicates if this app is an Arcade app.
 * @param metricsClickOptions The metrics click options used for this interaction.
 */
function cancellablePreorderOfferStateAction(objectGraph, data, action, isArcade, metricsClickOptions) {
    let defaultAction;
    if (isArcade) {
        const subscribedAction = wrappedCancellablePreorderAction(objectGraph, data, action.purchaseConfiguration.appName, isArcade, true, metricsClickOptions);
        const notSubscribedAction = wrappedCancellablePreorderAction(objectGraph, data, action.purchaseConfiguration.appName, isArcade, false, metricsClickOptions);
        defaultAction = new models.ArcadeSubscriptionStateAction(notSubscribedAction, notSubscribedAction, subscribedAction, notSubscribedAction);
    }
    else {
        defaultAction = wrappedCancellablePreorderAction(objectGraph, data, action.purchaseConfiguration.appName, false, false, metricsClickOptions);
    }
    return new models.OfferStateAction(data.id, defaultAction);
}
/**
 * A cancellable preorder action that is wrapped in an alert or sheet.
 * @param objectGraph The object graph
 * @param data The app data
 * @param appName The name of the app that will be cancelled.
 * @param isArcade Indicates if this app is an Arcade app.
 * @param isSubscribedToArcade Indicates if this action is for an Arcade subscriber or not.
 * @param metricsClickOptions The metrics click options used for this interaction.
 */
function wrappedCancellablePreorderAction(objectGraph, data, appName, isArcade, isSubscribedToArcade, metricsClickOptions) {
    const cancelPreorderAction = new models.CancelPreorderAction(data.id, isArcade);
    let title;
    let cancelTitle;
    let body;
    // rdar://144833292 (Remove Cancel Preorder Native Loc)
    // Once the service strings are localised and deployed, we should remove the native loc strings.
    if (preprocessor.GAMES_TARGET) {
        title = objectGraph.loc.string("PreOrder.Cancel.Title");
        if (objectGraph.client.isAutomaticDownloadingEnabled() && ((isArcade && isSubscribedToArcade) || !isArcade)) {
            cancelPreorderAction.title = objectGraph.loc.string("PreOrder.Cancel.Button.Download");
            body = objectGraph.loc.string("PreOrder.Cancel.Body.Download").replace("{appName}", appName);
        }
        else {
            cancelPreorderAction.title = objectGraph.loc.string("PreOrder.Cancel.Button");
            body = objectGraph.loc.string("PreOrder.Cancel.Body").replace("{appName}", appName);
        }
        cancelTitle = objectGraph.loc.string("PreOrder.Cancel.NotNow");
    }
    else {
        title = objectGraph.loc.string("CANCEL_COMING_SOON_TITLE");
        if (objectGraph.client.isAutomaticDownloadingEnabled() && ((isArcade && isSubscribedToArcade) || !isArcade)) {
            cancelPreorderAction.title = objectGraph.loc.string("CANCEL_COMING_SOON_BUTTON_DOWNLOAD");
            body = objectGraph.loc.string("COMING_SOON_BODY_DOWNLOAD").replace("{appName}", appName);
        }
        else {
            cancelPreorderAction.title = objectGraph.loc.string("CANCEL_COMING_SOON_BUTTON");
            body = objectGraph.loc.string("COMING_SOON_BODY").replace("{appName}", appName);
        }
        cancelTitle = objectGraph.loc.string("CANCEL_COMING_SOON_CANCEL");
    }
    let wrappedCancellationAction;
    if (objectGraph.client.deviceType === "mac" || objectGraph.client.deviceType === "tv") {
        const alertAction = new models.AlertAction("default");
        alertAction.title = title;
        alertAction.message = body;
        alertAction.buttonActions = [cancelPreorderAction];
        alertAction.isCancelable = true;
        alertAction.cancelTitle = cancelTitle;
        alertAction.destructiveActionIndex = 0;
        wrappedCancellationAction = alertAction;
    }
    else if (objectGraph.client.deviceType === "vision" || preprocessor.GAMES_TARGET) {
        const visionAlertAction = new models.AlertAction("default");
        visionAlertAction.title = title;
        visionAlertAction.artwork = artworkBuilder.createArtworkForResource(objectGraph, "systemimage://bell.slash.fill", 95, 90);
        visionAlertAction.message = body;
        visionAlertAction.buttonActions = [cancelPreorderAction];
        visionAlertAction.isCancelable = true;
        visionAlertAction.cancelTitle = cancelTitle;
        wrappedCancellationAction = visionAlertAction;
    }
    else {
        const sheetAction = new models.SheetAction([cancelPreorderAction]);
        sheetAction.title = title;
        sheetAction.message = body;
        sheetAction.isCancelable = true;
        sheetAction.cancelTitle = cancelTitle;
        sheetAction.isCustom = false;
        sheetAction.destructiveActionIndex = 0;
        wrappedCancellationAction = sheetAction;
    }
    metricsHelpersClicks.addClickEventToAction(objectGraph, cancelPreorderAction, {
        ...metricsClickOptions,
        actionType: "cancelPreorder",
    });
    // Promo codes allow users to install preorder apps before they are available, so we need to
    // allow the app to be opened without showing any alert if it is installed.
    const openAppAction = new models.OpenAppAction(data.id, "app");
    const offerStateAction = new models.OfferStateAction(data.id, wrappedCancellationAction);
    offerStateAction.openAction = openAppAction;
    return offerStateAction;
}
function arcadePreOrderBuyCompleteActionForTV(objectGraph, subscribePageAction) {
    if (!objectGraph.client.isTV) {
        // Use `arcadePreOrderBuyCompleteAction` instead.
        return null;
    }
    // For preorders before Coming Soon Enhancements:
    // On TV, we show the pre-order legal terms as part of the confirmation. On all other supported
    // platforms, the legal terms are visible on the product page itself, so we don't need to show
    // them again.
    const subscriberOfferAlertAction = new models.OfferAlertAction();
    if (objectGraph.client.isAutomaticDownloadingEnabled()) {
        subscriberOfferAlertAction.title = objectGraph.loc.string("PREORDER_NOTIFY_AUTOMATIC_DOWNLOAD_MESSAGE");
    }
    else {
        subscriberOfferAlertAction.title = objectGraph.loc.string("PREORDER_NOTIFY_MESSAGE");
    }
    const unsubscriberOfferAlertAction = new models.OfferAlertAction();
    unsubscriberOfferAlertAction.title = objectGraph.loc.string("PREORDER_NOTIFY_MESSAGE");
    // Configure both actions
    for (const alertAction of [subscriberOfferAlertAction, unsubscriberOfferAlertAction]) {
        alertAction.isCancelable = false;
        alertAction.shouldCheckForAvailableDiskSpace = false;
        alertAction.remoteControllerRequirement = null;
        alertAction.shouldCheckForGameController = false;
        alertAction.shouldPromptForConfirmation = true;
        alertAction.shouldIncludeActiveAccountInFooterMessage = true;
        alertAction.completionAction = new models.BlankAction();
        alertAction.completionAction.title = objectGraph.loc.string("Action.OK");
    }
    // For those not subscribed, show an upsell (rate limited) and a toast.
    const preorderSubscribePageAction = new models.RateLimitedAction("arcade-preorder", new models.CompoundAction([subscribePageAction, unsubscriberOfferAlertAction]));
    preorderSubscribePageAction.rateLimit = objectGraph.bag.arcadePreOrderUpsellLimitSeconds;
    preorderSubscribePageAction.fallbackAction = unsubscriberOfferAlertAction;
    return new models.ArcadeSubscriptionStateAction(preorderSubscribePageAction, subscriberOfferAlertAction, subscriberOfferAlertAction, subscriberOfferAlertAction);
}
function arcadePreOrderBuyCompleteAction(objectGraph, subscribePageAction) {
    if (objectGraph.client.isTV) {
        // Use `arcadePreOrderBuyCompleteActionForTV` instead.
        return null;
    }
    const checkmarkArtwork = artworkBuilder.createArtworkForResource(objectGraph, "systemimage://checkmark", 95, 90);
    const bellArtwork = artworkBuilder.createArtworkForResource(objectGraph, "systemimage://bell.fill", 95, 90);
    // On completion, we will show a toast (and maybe an upsell if you're unsubscribed from Arcade).
    const isVisionOS = objectGraph.client.isVision;
    let subscribedAction;
    if (isVisionOS) {
        subscribedAction = new models.AlertAction("default");
        subscribedAction.title = objectGraph.loc.string("ARCADE_PREORDER_LOCKUP_COMING_SOON");
        subscribedAction.artwork = artworkBuilder.createArtworkForResource(objectGraph, "systemimage://bell.badge.fill", 95, 90);
        subscribedAction.isCancelable = true;
        if (objectGraph.client.isAutomaticDownloadingEnabled()) {
            subscribedAction.message = objectGraph.loc.string("PREORDER_NOTIFY_AUTOMATIC_DOWNLOAD_MESSAGE");
        }
        else {
            subscribedAction.message = objectGraph.loc.string("PREORDER_NOTIFY_MESSAGE");
        }
    }
    else {
        subscribedAction = new models.AlertAction("toast");
        subscribedAction.title = "";
        subscribedAction.artwork = checkmarkArtwork;
        // rdar://144833292 (Remove Cancel Preorder Native Loc)
        // Once the service strings are localised and deployed, we should remove the native loc strings.
        if (preprocessor.GAMES_TARGET) {
            if (objectGraph.client.isAutomaticDownloadingEnabled()) {
                subscribedAction.message = objectGraph.loc.string("PreOrder.Notify.Message.Download");
                subscribedAction.toastDuration = 2.5;
            }
            else {
                subscribedAction.message = objectGraph.loc.string("PreOrder.Notify.Message");
                subscribedAction.toastDuration = 1.5;
            }
        }
        else {
            if (objectGraph.client.isAutomaticDownloadingEnabled()) {
                subscribedAction.message = objectGraph.loc.string("PREORDER_NOTIFY_AUTOMATIC_DOWNLOAD_MESSAGE");
                subscribedAction.toastDuration = 2.5;
            }
            else {
                subscribedAction.message = objectGraph.loc.string("PREORDER_NOTIFY_MESSAGE");
                subscribedAction.toastDuration = 1.5;
            }
        }
    }
    // Users who are not subscribed should never see the 'auto download' message.
    let unsubscribedAction;
    if (isVisionOS) {
        const preorderNotifyAlert = new models.AlertAction("default");
        preorderNotifyAlert.title = objectGraph.loc.string("ARCADE_PREORDER_LOCKUP_COMING_SOON");
        preorderNotifyAlert.artwork = artworkBuilder.createArtworkForResource(objectGraph, "systemimage://bell.badge.fill", 95, 90);
        preorderNotifyAlert.message = objectGraph.loc.string("PREORDER_NOTIFY_MESSAGE");
        preorderNotifyAlert.isCancelable = true;
        const subscribePageRateLimitedAction = new models.RateLimitedAction("arcade-preorder", new models.CompoundAction([subscribePageAction]));
        subscribePageRateLimitedAction.title = objectGraph.loc.string("ACTION_OK");
        subscribePageRateLimitedAction.rateLimit = objectGraph.bag.arcadePreOrderUpsellLimitSeconds;
        subscribePageRateLimitedAction.fallbackAction = null;
        // For those not subscribed, show an alert to confirm preorder first, wait for it dismissed then show an upsell (rate limited).
        preorderNotifyAlert.cancelAction = subscribePageRateLimitedAction;
        unsubscribedAction = preorderNotifyAlert;
    }
    else {
        const preorderNotifyToast = new models.AlertAction("toast");
        preorderNotifyToast.title = "";
        preorderNotifyToast.artwork = bellArtwork;
        // rdar://144833292 (Remove Cancel Preorder Native Loc)
        // Once the service strings are localised and deployed, we should remove the native loc strings.
        preorderNotifyToast.message = preprocessor.GAMES_TARGET
            ? objectGraph.loc.string("PreOrder.Notify.Message")
            : objectGraph.loc.string("PREORDER_NOTIFY_MESSAGE");
        preorderNotifyToast.toastDuration = 1.5;
        // For those not subscribed, show an upsell (rate limited) and a toast.
        const compoundRateLimitedAction = new models.RateLimitedAction("arcade-preorder", new models.CompoundAction([subscribePageAction, preorderNotifyToast]));
        compoundRateLimitedAction.rateLimit = objectGraph.bag.arcadePreOrderUpsellLimitSeconds;
        compoundRateLimitedAction.fallbackAction = preorderNotifyToast;
        unsubscribedAction = compoundRateLimitedAction;
    }
    // Users who have not opted into notifications on their device will be shown a full screen prompt to turn notifications on. To facilitate this we swap out toast and upsell actions for a blank action
    // - For Arcade subscribed users we don't want to overlay the toast in this situation
    // - For Arcade unsubscribed users it was decided that the notifications prompt should take precedence and we shouldn't show that either
    // - To help keep co-ordination of prompts/toasts/sheets simpler this toast and arcade upsell should/will ultimately be moved to be part of an ODJ
    // Until rdar://144839099 (TCC Notification for Moltres), we assume the user is authorized for notifications.
    const isUnauthorizedForUserNotifications = !(preprocessor.GAMES_TARGET || objectGraph.client.isAuthorizedForUserNotifications());
    if (objectGraph.bag.newEventsForODJAreEnabled && isUnauthorizedForUserNotifications) {
        return new models.ArcadeSubscriptionStateAction(new models.BlankAction(), new models.BlankAction(), new models.BlankAction(), new models.BlankAction());
    }
    else {
        return new models.ArcadeSubscriptionStateAction(unsubscribedAction, subscribedAction, subscribedAction, subscribedAction);
    }
}
/**
 * Wrap the offer in an `OfferAlertAction` for tvOS in order to check for requirements and confirm purchase.
 */
function createOfferAlertActionIfNeeded(objectGraph, offerAction, data, isPreorder, metricsOptions) {
    if (objectGraph.client.deviceType === "tv") {
        return createTVOfferAlertActionIfNeeded(objectGraph, offerAction, null, data, isPreorder, metricsOptions);
    }
    else if (objectGraph.client.isVision) {
        return createVisionOfferAlertActionIfNeeded(objectGraph, offerAction, null, data, isPreorder, metricsOptions);
    }
    else {
        return {
            startAction: serverData.isNull(offerAction) ? null : offerAction,
            underlyingOfferAction: null,
        };
    }
}
function createTVOfferAlertActionIfNeeded(objectGraph, offerAction, alertCompletionActionOverride, data, isPreorder, metricsOptions) {
    if (serverData.isNull(offerAction)) {
        return {
            startAction: null,
            underlyingOfferAction: null,
        };
    }
    const offerAlertAction = new models.OfferAlertAction();
    const isFree = isFreeFromOfferAction(objectGraph, offerAction);
    const appName = offerAction.purchaseConfiguration.appName;
    // Configure requirement checks
    offerAlertAction.shouldCheckForAvailableDiskSpace = !isPreorder;
    if (objectGraph.host.isTV) {
        offerAlertAction.remoteControllerRequirement = gameController.controllerRequirementFromData(objectGraph, data);
        offerAlertAction.shouldCheckForGameController = false;
    }
    else {
        offerAlertAction.remoteControllerRequirement = "NO_BADGE";
        offerAlertAction.shouldCheckForGameController = contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "requiresGameController");
    }
    // Configure restrictions check
    const contentRating = ageRatings.value(objectGraph, data, true);
    if (serverData.isDefinedNonNull(contentRating)) {
        offerAlertAction.checkRestrictionsForContentRating = contentRating;
    }
    // Configure confirmation alert title
    if (isFree) {
        const titleFormat = isPreorder
            ? objectGraph.loc.string("OfferAlert.TV.Title.PredorderFree")
            : objectGraph.loc.string("OfferAlert.TV.Title.Free");
        offerAlertAction.title = titleFormat.replace("{title}", appName);
    }
    else {
        const titleFormat = isPreorder
            ? objectGraph.loc.string("OfferAlert.TV.Title.PreorderPaid")
            : objectGraph.loc.string("OfferAlert.TV.Title.Paid");
        offerAlertAction.title = titleFormat.replace("{title}", appName).replace("{price}", offerAction.priceFormatted);
    }
    // Configure confirmation action title
    const buyAction = objects.shallowCopyOf(offerAction);
    if (isPreorder) {
        buyAction.title = objectGraph.loc.string("OfferButton.Title.Preorder");
    }
    else if (isFree) {
        buyAction.title = objectGraph.loc.string("OfferButton.Title.Get");
    }
    else {
        buyAction.title = objectGraph.loc.string("OfferButton.Title.Buy");
    }
    // Configure completion action
    const alertCompletionAction = isNothing(alertCompletionActionOverride)
        ? buyAction
        : alertCompletionActionOverride;
    offerAlertAction.completionAction = alertCompletionAction;
    // Configure footer message
    offerAlertAction.shouldIncludeActiveAccountInFooterMessage = true;
    const footerMessage = [];
    const hasInAppPurchases = contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "hasInAppPurchases");
    if (hasInAppPurchases) {
        footerMessage.push(objectGraph.loc.string("OFFERS_IN_APP_PURCHASES", "Offers In-App Purchases"));
    }
    if (footerMessage.length > 0) {
        offerAlertAction.footerMessage = footerMessage.join(objectGraph.loc.string("TV_OFFER_ALERT_FOOTER_LINE_BREAK"));
    }
    // Add metrics event for initiating purchase
    offerAlertAction.impressionMetrics = buyAction.impressionMetrics;
    // Create an instance of offer alert with requirements checks,
    // but no confirmation prompt for redownloads and updates
    const offerAlertActionWithoutConfirmationPrompt = objects.shallowCopyOf(offerAlertAction);
    offerAlertActionWithoutConfirmationPrompt.shouldPromptForConfirmation = false;
    offerAlertActionWithoutConfirmationPrompt.title = null;
    offerAlertActionWithoutConfirmationPrompt.footerMessage = null;
    // By default, perform requirements checks, but avoid confirmation prompt
    const offerStateAction = new models.OfferStateAction(offerAction.adamId, offerAlertActionWithoutConfirmationPrompt);
    // Perform requirements checks and show confirmation for buys
    if (!content.isArcadeSupported(objectGraph, data)) {
        offerStateAction.buyAction = offerAlertAction;
    }
    // Run offer action directly for opens, bypassing all prompts
    offerStateAction.openAction = offerAction;
    return {
        startAction: offerStateAction,
        underlyingOfferAction: buyAction,
    };
}
function createVisionOfferAlertActionIfNeeded(objectGraph, offerAction, alertCompletionActionOverride, data, isPreorder, metricsOptions) {
    if (serverData.isNull(offerAction)) {
        return {
            startAction: null,
            underlyingOfferAction: null,
        };
    }
    const offerAlertAction = new models.OfferAlertAction();
    const isFree = isFreeFromOfferAction(objectGraph, offerAction);
    offerAlertAction.remoteControllerRequirement = gameController.controllerRequirementFromData(objectGraph, data);
    offerAlertAction.spatialControllerRequirement = gameController.spatialControllerRequirementFromData(objectGraph, data);
    // Configure confirmation action title
    const buyAction = objects.shallowCopyOf(offerAction);
    if (isPreorder) {
        buyAction.title = objectGraph.loc.string("OFFER_BUTTON_TITLE_PREORDER");
    }
    else if (isFree) {
        buyAction.title = objectGraph.loc.string("OFFER_BUTTON_TITLE_GET");
    }
    else {
        buyAction.title = objectGraph.loc.string("OFFER_BUTTON_TITLE_BUY");
    }
    // Configure completion action
    const alertCompletionAction = isNothing(alertCompletionActionOverride)
        ? buyAction
        : alertCompletionActionOverride;
    offerAlertAction.completionAction = alertCompletionAction;
    // Add metrics event for initiating purchase
    offerAlertAction.impressionMetrics = buyAction.impressionMetrics;
    // Create an instance of offer alert with requirements checks,
    // but no confirmation prompt for redownloads and updates
    const offerAlertActionWithoutConfirmationPrompt = objects.shallowCopyOf(offerAlertAction);
    offerAlertActionWithoutConfirmationPrompt.shouldPromptForConfirmation = false;
    offerAlertActionWithoutConfirmationPrompt.title = null;
    offerAlertActionWithoutConfirmationPrompt.footerMessage = null;
    /**
     * - If the app is buyable or updatable: perform requirement checks, then perform the `buyAction`
     * - If the app is cancellable: perform the `buyAction` which will cancel the downloading
     * - If the app is openable: perform the `buyAction` which will opening the app
     * - Otherwise by default perform the offer alert action without confirmation prompt
     */
    const offerStateAction = new models.OfferStateAction(offerAction.adamId, offerAlertActionWithoutConfirmationPrompt);
    offerStateAction.buyAction = offerAlertAction;
    offerStateAction.cancelAction = buyAction;
    offerStateAction.openAction = buyAction;
    return {
        startAction: offerStateAction,
        underlyingOfferAction: buyAction,
    };
}
/**
 * Add two-phased confirmation to the buy action.
 * @note This function should only be used on clients that need two-phased confirmation, at the moment, macOS.
 * @param buyAction The action to perform buy.
 * @param isPreorder Whether the buy is for a preorder.
 * @param metricsOptions The metrics options to use for reporting metrics actions.
 * @returns {Action} A configured confirmation action, or the original action if wrapping was not needed.
 */
export function wrapOfferInTwoPhasedConfirmationActionIfNeeded(objectGraph, buyAction, isPreorder, metricsOptions) {
    if (serverData.isNull(buyAction)) {
        return null;
    }
    if (preprocessor.GAMES_TARGET) {
        // There's a confirmation built-in to the flow in Cheer,
        // thus two-phased confirmation is not necessary.
        return buyAction;
    }
    // There's a confirmation built-in to the flow for the new payment method as well as pre-orders,
    // thus two-phased confirmation is not necessary.
    if (!objectGraph.bag.enableTwoPhaseOfferConfirmation || isPreorder) {
        return buyAction;
    }
    // Create metrics event for initiating confirmation
    const confirmationInitiationAction = new models.BlankAction();
    confirmationInitiationAction.impressionMetrics = buyAction.impressionMetrics;
    const confirmationMetricsOptions = objects.shallowCopyOf(metricsOptions);
    if (!serverData.isNull(confirmationMetricsOptions)) {
        confirmationMetricsOptions.actionType = "buyInitiate";
        confirmationMetricsOptions.targetType = "button";
        metricsHelpersClicks.addClickEventToAction(objectGraph, confirmationInitiationAction, confirmationMetricsOptions);
    }
    // Create confirmation action
    const confirmationAction = new models.OfferConfirmationAction(buyAction, confirmationInitiationAction);
    // Add accessibility confirmation action
    confirmationAction.confirmationAccessibilityAction = wrapOfferActionInConfirmAlertAction(objectGraph, buyAction);
    return confirmationAction;
}
/**
 * Configures a tvOS-only intermediate action for an offer action.
 * @param offerAction The underlying offer action to perform the buy with.
 * @param requiresGameController Whether or not the product requires a game controller.
 * @returns {OfferStateAction} The tvOS-only-wrapped action.
 */
function tvOnlyAppActionForBuyAction(objectGraph, offerAction, requiresGameController) {
    const tvOnlyBuyAlert = new models.AlertAction("default");
    tvOnlyBuyAlert.title = objectGraph.loc.string("Alert.Buy.TvOnly.Title");
    tvOnlyBuyAlert.message = objectGraph.loc.string("Alert.Buy.TvOnly.Message");
    tvOnlyBuyAlert.isCancelable = true;
    if (requiresGameController) {
        tvOnlyBuyAlert.buttonActions = [requiresGameControllerActionForBuyAction(objectGraph, offerAction)];
    }
    else {
        tvOnlyBuyAlert.buttonActions = [offerAction];
    }
    tvOnlyBuyAlert.buttonTitles = [objectGraph.loc.string("Alert.Buy.TvOnly.ButtonTitle")];
    const offerStateAction = new models.OfferStateAction(offerAction.adamId, tvOnlyBuyAlert);
    offerStateAction.title = offerAction.title;
    return offerStateAction;
}
/**
 * Configures a visionOS intermediate action for an offer action.
 * @param {AppStoreObjectGraph} objectGraph The object graph associated with this purchase.
 * @param {models.OfferAction} offerAction The underlying offer action to perform the buy with.
 * @param {mediaDataStructure.Data} data MAPI data blob describing the content model which offer belongs to.
 * @param {boolean} isFree Whether the buy is for a free product.
 * @param {boolean} isArcade Whether the buy is for an Arcade app.
 * @returns {OfferStateAction} The vision-only-wrapped action.
 */
function visionAppActionForBuyAction(objectGraph, offerAction, data, isFree, isArcade) {
    const visionOnlyBuyAlert = visionAppAlertForBuyAction(objectGraph, offerAction, data, isFree, isArcade);
    const offerStateAction = new models.OfferStateAction(offerAction.adamId, visionOnlyBuyAlert);
    offerStateAction.title = offerAction.title;
    return offerStateAction;
}
/**
 * Configures an alert action for vision-only intermediate action.
 * @param {AppStoreObjectGraph} objectGraph The object graph associated with this purchase.
 * @param {models.Action} buttonAction The underlying action to perform.
 * @param {mediaDataStructure.Data} data MAPI data blob describing the content model which offer belongs to.
 * @param {boolean} isFree Whether the buy is for a free product.
 * @param {boolean} isArcade Whether the buy is for an Arcade app.
 * @returns {AlertAction} The alert action for the vision-only buy action.
 */
function visionAppAlertForBuyAction(objectGraph, buttonAction, data, isFree, isArcade) {
    const visionOnlyBuyAlert = new models.AlertAction("default");
    visionOnlyBuyAlert.isCancelable = true;
    visionOnlyBuyAlert.buttonActions = [buttonAction];
    visionOnlyBuyAlert.imageName = "vision.pro";
    if (supportsVisionDownloadFromVisionCompanion(objectGraph, data)) {
        // Shows the purchase details from the Companion app
        if (isFree) {
            visionOnlyBuyAlert.title = objectGraph.loc.string("Alert.Buy.VisionOnly.Free.RemoteDownloads.Title");
            visionOnlyBuyAlert.message = objectGraph.loc.string("Alert.Buy.VisionOnly.Free.RemoteDownloads.Message");
            visionOnlyBuyAlert.buttonTitles = [
                objectGraph.loc.string("Alert.Buy.VisionOnly.Free.RemoteDownloads.ButtonTitle"),
            ];
        }
        else {
            visionOnlyBuyAlert.title = objectGraph.loc.string("Alert.Buy.VisionOnly.Paid.RemoteDownloads.Title");
            visionOnlyBuyAlert.message = objectGraph.loc.string("Alert.Buy.VisionOnly.Paid.RemoteDownloads.Message");
            visionOnlyBuyAlert.buttonTitles = [
                objectGraph.loc.string("Alert.Buy.VisionOnly.Paid.RemoteDownloads.ButtonTitle"),
            ];
        }
    }
    else {
        // Show the default vision only purchase.
        visionOnlyBuyAlert.title = objectGraph.loc.string("Alert.Buy.VisionOnly.Title");
        if (isArcade) {
            visionOnlyBuyAlert.message = objectGraph.loc.string("Alert.Buy.VisionOnly.Message.Arcade");
        }
        else {
            visionOnlyBuyAlert.message = objectGraph.loc.string("Alert.Buy.VisionOnly.Message");
        }
        visionOnlyBuyAlert.buttonTitles = [objectGraph.loc.string("Alert.Buy.VisionOnly.ButtonTitle")];
    }
    return visionOnlyBuyAlert;
}
/**
 * This will return true if we are running in the companion app and the purchase we are working with
 * will successfully download and run on a vision pro.
 * @param {AppStoreObjectGraph} objectGraph The object graph associated with this purchase.
 * @param {mediaDataStructure.Data} data MAPI data blob describing the content model which offer belongs to.
 * @returns {boolean} true if the data/product can be downloaded onto vision pro from within companion.
 */
export function supportsVisionPlatformForVisionCompanion(objectGraph, data) {
    if (!objectGraph.client.isCompanionVisionApp) {
        return false;
    }
    const appPlatforms = content.supportedAppPlatformsFromData(objectGraph, data);
    const purchaseSupportsVision = appPlatforms.includes("vision") || content.supportsVisionOSCompatibleIOSBinaryOnAnyClient(data);
    if (!purchaseSupportsVision) {
        return false;
    }
    return true;
}
/**
 * This will return true if we are running in the Vision companion app and the purchase we are working with
 * will successfully download and run on a vision pro and the user has associated vision pros to download
 * this purchase to.
 * @param {AppStoreObjectGraph} objectGraph The object graph associated with this purchase.
 * @param {mediaDataStructure.Data} data MAPI data blob describing the content model which offer belongs to.
 * @returns {boolean} true if the data/product can be downloaded onto vision pro from within companion.
 */
export function supportsVisionDownloadFromVisionCompanion(objectGraph, data) {
    if (!supportsVisionPlatformForVisionCompanion(objectGraph, data)) {
        return false;
    }
    return hasRemoteDownloadIdentifiers(objectGraph);
}
/**
 * This will return true if the client passed in any remoteDownloadIdentifiers and false otherwise.
 * @param {AppStoreObjectGraph} objectGraph The object graph associated with this purchase.
 * @returns {boolean} true if remoteDownloadIdentifiers contains anything.
 */
export function hasRemoteDownloadIdentifiers(objectGraph) {
    if (isNothing(objectGraph.client.remoteDownloadIdentifiers)) {
        return false;
    }
    const hasNoDevicesForRemoteDownload = objectGraph.client.remoteDownloadIdentifiers.length === 0;
    if (hasNoDevicesForRemoteDownload) {
        return false;
    }
    return true;
}
/**
 * Determines whether the product contains a macOS IPA install
 * @param objectGraph Current object graph
 * @param data The product data
 * @returns True if the product has a macOS IPA installer
 */
export function hasMacIPAPackageForData(objectGraph, data) {
    return contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "hasMacIPAPackage");
}
/**
 * Configures a game-controller-required action.
 * @param action The action to carry out on the alert confirmation.
 * @returns {AlertAction} The alert action to use for the game-controller-required dialog.
 */
function requiresGameControllerActionForBuyAction(objectGraph, action) {
    const requiresControllerAlert = new models.AlertAction("default");
    requiresControllerAlert.title = objectGraph.loc.string("Alert.Buy.TvGameControllerRequired.Title");
    requiresControllerAlert.message = objectGraph.loc.string("Alert.Buy.TvGameControllerRequired.Message");
    requiresControllerAlert.buttonTitles = [objectGraph.loc.string("Alert.Buy.TvGameControllerRequired.ButtonTitle")];
    requiresControllerAlert.isCancelable = true;
    requiresControllerAlert.buttonActions = [action];
    return requiresControllerAlert;
}
/**
 * Configures a watchOS-only intermediate action for an offer action.
 * @param offerAction The underlying offer action to perform the buy with.
 * @returns {OfferStateAction} The watchOS-only-wrapped action.
 */
function watchOnlyAppActionForBuyAction(objectGraph, offerAction) {
    const tvOnlyBuyAlert = new models.AlertAction("default");
    tvOnlyBuyAlert.title = objectGraph.loc.string("Alert.Buy.WatchOnly.Title");
    tvOnlyBuyAlert.message = objectGraph.loc.string("Alert.Buy.WatchOnly.Message");
    tvOnlyBuyAlert.isCancelable = true;
    tvOnlyBuyAlert.buttonActions = [offerAction];
    tvOnlyBuyAlert.buttonTitles = [objectGraph.loc.string("Alert.Buy.WatchOnly.ButtonTitle")];
    const offerStateAction = new models.OfferStateAction(offerAction.adamId, tvOnlyBuyAlert);
    offerStateAction.title = offerAction.title;
    return offerStateAction;
}
/**
 * Configures a watchOS update required intermediate action for an offer action.
 * @param offerAction The underlying offer action to perform the buy with.
 * @returns {OfferStateAction} The watchOS update required wrapped action.
 */
function watchUpdateRequiredActionForBuyAction(objectGraph, offerAction, minOSVersion) {
    const alert = new models.AlertAction("default");
    alert.title = objectGraph.loc
        .string("ProductPage.WatchOSUpdateRequired.Title")
        .replace("{osVersion}", minOSVersion);
    alert.message = objectGraph.loc
        .string("ProductPage.WatchOSUpdateRequired.Message")
        .replace("{osVersion}", minOSVersion);
    alert.buttonActions = [offerAction];
    alert.buttonTitles = [objectGraph.loc.string("Action.OK")];
    const offerStateAction = new models.OfferStateAction(offerAction.adamId, offerAction);
    offerStateAction.buyAction = alert;
    return offerStateAction;
}
function wrapOfferActionInConfirmAlertAction(objectGraph, action) {
    if (serverData.isNull(action)) {
        return null;
    }
    const alert = new models.AlertAction("default");
    if (serverData.isNull(action.priceFormatted)) {
        alert.title = objectGraph.loc.string("GET");
        alert.message = "Are you sure you want to get " + action.purchaseConfiguration.appName;
    }
    else {
        alert.title = "Buy App";
        alert.message = `Are you sure you want to buy ${action.purchaseConfiguration.appName} for ${action.priceFormatted}`;
    }
    alert.isCancelable = true;
    alert.buttonActions = [action];
    const offerStateAction = new models.OfferStateAction(action.adamId, action);
    offerStateAction.buyAction = alert;
    return offerStateAction;
}
/**
 * Create object describing the visual aspects of an offer action.
 *
 * @param {AppStoreObjectGraph} objectGraph Object graph.
 * @param {OfferAction} action The offer action to create display properties for.
 * @param {OfferType} type The type of offer to display.
 * @param {Data} data MAPI data blob describing the content model which offer belongs to.
 * @param {boolean} isPreorder Whether the buy is for a preorder.
 * @param {boolean} isContainedInPreorderExclusiveShelf Whether this offer is displayed in a coming soon shelf.
 * @param {OfferStyle} style The style of offer button. Returned display property may have different style, depending on filters.
 * @param {OfferEnvironment} environment The environment which offer button will be displayed in.
 * @param {JSONData} discountData The JSON blob describing discounts to visualize.
 * @param {boolean} isParentAppFree Flag indicating whether parent app was free app.
 * @param {OfferContext} context Contextual info about offer.
 * @param {boolean} shouldNavigateToProductPage Whether this offer should navigate to the product page.
 * @param {boolean} isAd Whether this offer is for an ad.
 * @param {ClientIdentifier} An optional override of the client identifier
 * @param {Data} An optional fallback of the parent App Data used when constructing IAP offers
 * @returns {OfferDisplayProperties} The display properties for offer button for given product and offer.
 *
 * @seealso `personalizedCMCDisplayPropertiesFromBuyButtonMetadata`, the display properties created from the old-school `buyButtonMetadataUrl` endpoint.
 */
export function displayPropertiesFromOfferAction(objectGraph, action, type, data, isPreorder, isContainedInPreorderExclusiveShelf, style, environment, discountData, isParentAppFree, context = "default", shouldNavigateToProductPage = false, isAd = false, clientIdentifierOverride = undefined, parentAppData = undefined, isBuyDisallowed = false) {
    if (serverData.isNull(action)) {
        return null;
    }
    return validation.context("displayPropertiesFromOfferAction", () => {
        var _a;
        let derivedStyle = style;
        // We can't prevent deep links into apps that are filtered,
        // but we should disable the buy button (if present) to prevent purchases
        if (!isBuyDisallowed && filtering.shouldFilter(objectGraph, data, 77238 /* filtering.Filter.Offers */)) {
            derivedStyle = "disabled";
        }
        // Disable preorders on unsupported OS' (if we have a buy button)
        if (!isBuyDisallowed && !lockups.deviceHasCapabilitiesFromData(objectGraph, data)) {
            derivedStyle = "disabled";
        }
        // Disable buy button when we are in companion but cannot install app onto visionOS (if we have a buy button)
        if (!isBuyDisallowed &&
            objectGraph.client.isCompanionVisionApp &&
            !supportsVisionDownloadFromVisionCompanion(objectGraph, data)) {
            derivedStyle = "disabled";
        }
        const parentData = (_a = lockups.parentDataFromInAppData(objectGraph, data)) !== null && _a !== void 0 ? _a : parentAppData;
        let parentAdamId;
        if (parentData) {
            parentAdamId = parentData.id;
        }
        let displayProperties = new models.OfferDisplayProperties(type, action.adamId, action.bundleId, derivedStyle, parentAdamId, environment);
        // Configure complementary offer label for preorders
        displayProperties.isPreorder = isPreorder;
        // If this is for an ad allow localization overrides if one is in the bag.
        const useAdsLocale = isAd && context === "default" && isSome(objectGraph.bag.adsOverrideLanguage);
        const adsOverrideLocalizer = useAdsLocale ? objectGraph.adsLoc : objectGraph.loc;
        displayProperties.useAdsLocale = useAdsLocale;
        // Arcade apps that are "Coming Soon" do not use the `preorder` label style.
        if (isPreorder) {
            if (content.isArcadeSupported(objectGraph, data)) {
                displayProperties.offerLabelStyle = "arcadeComingSoon";
                if (objectGraph.client.isVision && !isContainedInPreorderExclusiveShelf) {
                    // In visionOS, we would like lockup always display `Coming Soon` instead of release date
                    // Except for lockup within Coming Soon shelf
                    displayProperties.subtitles["expectedReleaseDate"] = objectGraph.loc.string("ARCADE_PREORDER_LOCKUP_COMING_SOON");
                }
                else {
                    displayProperties.subtitles["expectedReleaseDate"] = content.dynamicPreorderDateFromData(objectGraph, data, objectGraph.loc.string("ARCADE_PREORDER_LOCKUP_COMING_SOON"));
                }
                displayProperties.titleSymbolNames["preorderedSubscribed"] = "checkmark";
                displayProperties.titleSymbolNames["preorderedNotSubscribed"] = "bell.fill";
                // If the server-side value is an uppercase "COMING SOON" replace it with the titlecase variant for lockups.
                if (displayProperties.subtitles["expectedReleaseDate"] ===
                    objectGraph.loc.string("ARCADE_PREORDER_COMING_SOON")) {
                    displayProperties.subtitles["expectedReleaseDate"] = objectGraph.loc.string("ARCADE_PREORDER_LOCKUP_COMING_SOON");
                }
            }
            else {
                displayProperties.offerLabelStyle = "preorder";
                displayProperties.titleSymbolNames["standard"] = "checkmark";
                const expectedReleaseDate = content.dynamicPreorderDateFromData(objectGraph, data, "");
                if (isSome(expectedReleaseDate)) {
                    displayProperties.subtitles["expectedReleaseDate"] = expectedReleaseDate;
                }
            }
        }
        // Configure offer titles
        const isFree = isFreeFromOfferAction(objectGraph, action);
        displayProperties.isFree = isFree;
        let standardTitle = null;
        if (context === "default" &&
            (data.type === "app-bundles" ||
                content.isMacOSInstaller(objectGraph, data) ||
                content.isUnsupportedByCurrentCompanion(objectGraph, data) ||
                shouldNavigateToProductPage)) {
            standardTitle = objectGraph.loc.string("OfferButton.Title.View");
        }
        else if (context === "flowPreview" &&
            (data.type === "app-bundles" ||
                content.isMacOSInstaller(objectGraph, data) ||
                content.isUnsupportedByCurrentCompanion(objectGraph, data))) {
            // We do not want to support "View" in flow preview actions
            return null;
        }
        else if (context === "productPageBrowserChoice") {
            standardTitle = objectGraph.loc.string("OfferButton.Title.Select");
        }
        else if (isFree) {
            if (context === "flowPreview") {
                if (isPreorder) {
                    standardTitle = objectGraph.loc.string("OfferButton.FlowPreview.Preorder");
                }
                else {
                    standardTitle = objectGraph.loc.string("OfferButton.FlowPreview.Get");
                }
            }
            else {
                standardTitle = action.title;
            }
        }
        else if (objectGraph.client.isTV && (environment === "productPage" || environment === "arcadeProductPage")) {
            standardTitle = objectGraph.loc
                .string("OfferButton.Title.BuyWithPrice")
                .replace("{price}", action.priceFormatted);
        }
        else if (context === "flowPreview") {
            if (isPreorder) {
                standardTitle = objectGraph.loc
                    .string("OfferButton.FlowPreview.PreorderWithPrice")
                    .replace("{price}", action.priceFormatted);
            }
            else {
                standardTitle = objectGraph.loc
                    .string("OfferButton.FlowPreview.BuyWithPrice")
                    .replace("{price}", action.priceFormatted);
            }
        }
        else {
            standardTitle = action.priceFormatted;
        }
        displayProperties.titles["standard"] = standardTitle;
        displayProperties.priceFormatted = action.priceFormatted;
        // Confirmation buys are not supported in flow preview
        if (objectGraph.bag.enableTwoPhaseOfferConfirmation && context !== "flowPreview") {
            displayProperties.titles["confirmation"] = isFree
                ? objectGraph.loc.string("OfferButton.Title.ConfirmGet")
                : objectGraph.loc.string("OfferButton.Title.ConfirmBuy");
        }
        if (content.isArcadeSupported(objectGraph, data)) {
            if (context === "flowPreview") {
                const standardText = objectGraph.loc.string("OfferButton.FlowPreview.Arcade.Standard");
                displayProperties.titles["standard"] = standardText;
                displayProperties.titles["trial"] = standardText;
                displayProperties.titles["open"] = objectGraph.loc.string("OfferButton.FlowPreview.Arcade.Open");
                displayProperties.titles["notSubscribed"] = standardText;
                if (isPreorder) {
                    displayProperties.titles["preorderSubscribed"] = objectGraph.loc.string("OfferButton.FlowPreview.Arcade.PreorderSubscribed");
                    displayProperties.titles["preorderNotSubscribed"] = objectGraph.loc.string("OfferButton.FlowPreview.Arcade.PreorderNotSubscribed");
                }
            }
            else {
                const standardText = adsOverrideLocalizer.string("OfferButton.Arcade.Title.Standard");
                displayProperties.titles["standard"] = standardText;
                displayProperties.titles["trial"] = standardText;
                displayProperties.titles["open"] = adsOverrideLocalizer.string("OfferButton.Arcade.Title.Open");
                displayProperties.titles["notSubscribed"] = standardText;
                if (isPreorder) {
                    displayProperties.titles["preorderSubscribed"] = adsOverrideLocalizer.string("OfferButton.Arcade.Title.PreorderSubscribed");
                    displayProperties.titles["preorderNotSubscribed"] = adsOverrideLocalizer.string("OfferButton.Arcade.Title.PreorderNotSubscribed");
                    displayProperties.titles["preorderedSubscribed"] = adsOverrideLocalizer.string("OfferButton.Arcade.Title.PreorderedSubscribed");
                    displayProperties.titles["preorderedNotSubscribed"] = adsOverrideLocalizer.string("OfferButton.Arcade.Title.PreorderedNotSubscribed");
                }
            }
        }
        // Introductory Pricing
        // Not supported in flow preview
        if (discountData && context !== "flowPreview") {
            const discountType = serverData.asString(discountData, "modeType");
            const discountPriceFormatted = serverData.asString(discountData, "priceFormatted");
            if (serverData.isDefinedNonNull(discountPriceFormatted) && serverData.isDefinedNonNull(discountType)) {
                let discountOwnedParentTitle = null;
                let discountUnownedParentTitle = null;
                switch (discountType) {
                    case "FreeTrial":
                        if (isParentAppFree) {
                            discountOwnedParentTitle = objectGraph.loc.string("OfferButton.IntroPrice.FreeTrial");
                            discountUnownedParentTitle = objectGraph.loc.string("OfferButton.IntroPrice.FreeTrial");
                        }
                        else {
                            discountOwnedParentTitle = objectGraph.loc.string("OfferButton.IntroPrice.FreeTrial");
                            discountUnownedParentTitle = objectGraph.loc.string("OfferButton.IntroPrice.Trial");
                        }
                        break;
                    case "PayUpFront":
                        const payUpfrontPrice = objectGraph.loc
                            .string("OfferButton.IntroPrice.PaidUpfront.Trial")
                            .replace("{price}", discountPriceFormatted);
                        if (isParentAppFree) {
                            discountOwnedParentTitle = payUpfrontPrice;
                            discountUnownedParentTitle = payUpfrontPrice;
                        }
                        else {
                            discountOwnedParentTitle = payUpfrontPrice;
                            discountUnownedParentTitle = objectGraph.loc.string("OfferButton.IntroPrice.Trial");
                        }
                        break;
                    case "PayAsYouGo":
                        discountOwnedParentTitle = objectGraph.loc.string("OfferButton.IntroPrice.Trial");
                        discountUnownedParentTitle = objectGraph.loc.string("OfferButton.IntroPrice.Trial");
                        break;
                    default:
                        break;
                }
                displayProperties.titles["discountOwnedParent"] = discountOwnedParentTitle;
                displayProperties.titles["discountUnownedParent"] = discountUnownedParentTitle;
                displayProperties.subtitles["discountOwnedParent"] =
                    objectGraph.loc.string("INTRO_PRICE_OFFER_SUBTITLE");
                displayProperties.subtitles["discountUnownedParent"] =
                    objectGraph.loc.string("INTRO_PRICE_OFFER_SUBTITLE");
                // Determine whether the button title is too long and the subtitle should be pushed below.
                const maxCharacterCount = 10;
                let isWidthConstrained = false;
                for (const titleKey of Object.keys(displayProperties.titles)) {
                    const title = displayProperties.titles[titleKey];
                    if (title.length > maxCharacterCount) {
                        isWidthConstrained = true;
                        break;
                    }
                }
                if (isWidthConstrained) {
                    displayProperties = displayProperties.newOfferDisplayPropertiesChangingAppearance(false, null, "widthConstrainedLockup");
                }
            }
        }
        // iAPs
        const hasInAppPurchases = contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "hasInAppPurchases");
        const hasExternalPurchases = hasExternalPurchasesForData(objectGraph, data);
        const externalPurchasesEnabled = externalPurchasesPlacementIsEnabled(objectGraph, "lockup");
        const showExternalPurchasesSubtitle = hasExternalPurchases && externalPurchasesEnabled;
        displayProperties.hasInAppPurchases = hasInAppPurchases;
        displayProperties.hasExternalPurchases = showExternalPurchasesSubtitle;
        if (hasInAppPurchases || showExternalPurchasesSubtitle) {
            const inAppPurchasesKey = "Offer.InlineInAppPurchases";
            const subtitleKey = showExternalPurchasesSubtitle
                ? "OfferButton.ExternalPurchases.Subtitle"
                : inAppPurchasesKey;
            const standardSubtitle = adsOverrideLocalizer.string(subtitleKey);
            displayProperties.subtitles["standard"] = standardSubtitle;
            // Confirmation buys are not supported in flow preview
            if (objectGraph.bag.enableTwoPhaseOfferConfirmation && context !== "flowPreview") {
                displayProperties.subtitles["confirmation"] = standardSubtitle;
            }
        }
        // System Apps
        displayProperties.isDeletableSystemApp =
            sad.systemApps(objectGraph).isSystemAppFromData(data) &&
                !content.isUnsupportedByCurrentCompanion(objectGraph, data);
        // Content Restrictions
        const contentRating = ageRatings.value(objectGraph, data, true);
        displayProperties.contentRating = contentRating !== null && contentRating !== void 0 ? contentRating : undefined;
        // OS Eligibility
        displayProperties.appCapabilities = action.purchaseConfiguration.appCapabilities;
        // App bundles
        // Not supported in flow preview
        if (data.type === "app-bundles" && context !== "flowPreview") {
            displayProperties.offerToken = {
                offerAction: action,
                offerDisplayProperties: objects.shallowCopyOf(displayProperties),
            };
        }
        return displayProperties;
    });
}
export function macFileSizeInBytesFromData(objectGraph, data) {
    const offerData = offerDataFromData(objectGraph, data);
    if (serverData.isNull(offerData)) {
        return null;
    }
    const allAssets = serverData.asArrayOrEmpty(offerData, "assets");
    if (!allAssets.length) {
        return null;
    }
    for (const assetData of allAssets) {
        const flavor = serverData.asString(assetData, "flavor");
        if (flavor === "macSoftware") {
            return serverData.asNumber(assetData, "size");
        }
    }
    return null;
}
// region CMC Personalization
/**
 * Determine the type of offer type given offer metadata from `buyButtonMetadata` endpoint.
 *
 * @param {JSONData} buyButtonMetadata Metadata for offer returned from personalized offer endpoint.
 * @returns {PersonalizedOfferType} Type of personalized offer.
 */
export function personalizedOfferTypeFromBuyButtonMetadata(objectGraph, buyButtonMetadata) {
    return validation.context("personalizedOfferTypeFromBuyButtonMetadata", () => {
        const offers = serverData.asArrayOrEmpty(buyButtonMetadata, "offers");
        if (offers.length === 0) {
            return null;
        }
        for (const offer of offers) {
            const type = serverData.asString(offer, "type");
            if (type) {
                return type;
            }
        }
        return "none";
    });
}
/**
 * Create a personalized offer action specific for CMC given personalized offer metadata and original offer.
 *
 * @param {JSONData} buyButtonMetadata The personalized offer metadata to build offer with.
 * @param {OfferAction} originalOfferAction The original offer action to borrow metrics from.
 * @returns {OfferAction} A offer action with complete my bundle personalization applied.
 */
export function personalizedCMCOfferActionFromBuyButtonMetadata(objectGraph, buyButtonMetadata, originalOfferAction, isPreorder) {
    return validation.context("personalizedCMCOfferActionFromBuyButtonMetadata", () => {
        const offers = serverData.asArrayOrEmpty(buyButtonMetadata, "offers");
        if (offers.length === 0) {
            return null;
        }
        let type;
        let personalizedOfferData = null;
        for (const offer of offers) {
            type = serverData.asString(offer, "type");
            switch (type) {
                case "complete":
                case "purchased":
                    personalizedOfferData = offer;
                    break;
                default:
                    personalizedOfferData = null;
                    break;
            }
        }
        if (!personalizedOfferData) {
            return null;
        }
        /* Buy Params */
        let buyParams = serverData.asString(personalizedOfferData, "buyParams");
        if (type === "complete" && serverData.isNull(buyParams)) {
            validation.unexpectedNull("ignoredValue", "string", "item.offer.buyParams");
            return null;
        }
        else if (!buyParams) {
            buyParams = "";
        }
        /* Purchase Configuration */
        const originalConfiguration = originalOfferAction.purchaseConfiguration;
        const purchaseConfiguration = new models.PurchaseConfiguration(buyParams, originalConfiguration.vendor, originalConfiguration.appName, originalConfiguration.bundleId, originalConfiguration.appPlatforms, originalConfiguration.isPreorder, originalConfiguration.excludeAttribution, originalConfiguration.metricsPlatformDisplayStyle, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, originalConfiguration.appCapabilities, originalConfiguration.isDefaultBrowser, originalConfiguration.remoteDownloadIdentifiers, originalConfiguration.hasMacIPAPackage, originalConfiguration.contentRating);
        purchaseConfiguration.pageInformation = originalConfiguration.pageInformation;
        const action = internalOfferActionFromOfferData(objectGraph, personalizedOfferData, originalOfferAction.adamId, purchaseConfiguration, false);
        metricsHelpersClicks.addBuyEventToOfferActionInheritingMetrics(objectGraph, action, originalOfferAction, isPreorder);
        return action;
    });
}
/**
 * Create offer display properties specific for CMC offers from personalized action and original display properties.
 *
 * @param {OfferAction} personalizedAction The personalized CMC offer action created via `personalizedCMCOfferActionFromBuyButtonMetadata`.
 * @param {OfferType} type The type of offer to display.
 * @param {OfferDisplayProperties} originalOfferDisplayProperties The original offer display property to inherit some values from.
 * @param {OfferStyle} style The style of offer button. Returned display property may have different style, depending on filters.
 * @returns {OfferDisplayProperties} Offer display properties for a personalized CMC action.
 *
 * @note This code is similar to `media_displayPropertiesFromOfferAction`, but inserts on old-school non-MAPI data from
 * `buyButtonMetadataUrl` endpoint to original offer display properties. It assumes that bundles cannot:
 * - Be preordered
 * - Have introductory pricing
 * - Be filtered
 * - Have parent apps
 * - Be a deletable system app
 *
 * @seealso `media_displayPropertiesFromOfferAction`, for how display properties are created through MAPI data.
 */
export function personalizedCMCDisplayPropertiesFromBuyButtonMetadata(objectGraph, personalizedAction, type, originalOfferDisplayProperties, style) {
    if (serverData.isNull(personalizedAction)) {
        return null;
    }
    return validation.context("personalizedCMCDisplayPropertiesFromBuyButtonMetadata", () => {
        const displayProperties = new models.OfferDisplayProperties(type, personalizedAction.adamId, personalizedAction.bundleId, style);
        // Configure offer titles
        const isFree = isFreeFromOfferAction(objectGraph, personalizedAction);
        displayProperties.isFree = isFree;
        let standardTitle = null;
        if (isFree) {
            standardTitle = personalizedAction.title;
        }
        else {
            standardTitle = personalizedAction.priceFormatted;
        }
        displayProperties.titles["standard"] = standardTitle;
        displayProperties.priceFormatted = personalizedAction.priceFormatted;
        // iAPs
        const hasInAppPurchases = originalOfferDisplayProperties.hasInAppPurchases;
        const hasExternalPurchases = originalOfferDisplayProperties.hasExternalPurchases;
        displayProperties.hasInAppPurchases = hasInAppPurchases;
        displayProperties.hasExternalPurchases = hasExternalPurchases;
        if (hasExternalPurchases) {
            const standardSubtitle = objectGraph.loc.string("OfferButton.ExternalPurchases.Subtitle");
            displayProperties.subtitles["standard"] = standardSubtitle;
        }
        else if (hasInAppPurchases) {
            const standardSubtitle = objectGraph.loc.string("Offer.InlineInAppPurchases");
            displayProperties.subtitles["standard"] = standardSubtitle;
        }
        // Content Restrictions
        displayProperties.contentRating = originalOfferDisplayProperties.contentRating;
        // OS Eligibility
        displayProperties.appCapabilities = personalizedAction.purchaseConfiguration.appCapabilities;
        return displayProperties;
    });
}
// endregion
//# sourceMappingURL=offers.js.map