import * as validation from "@jet/environment/json/validation";
import { isNothing, isSome, unwrapOptional as unwrap } from "@jet/environment/types/optional";
import * as models from "../../api/models";
import * as modelsBase from "../../api/models/base";
import * as modelsShelves from "../../api/models/shelves";
import { ads } from "../../api/typings/constants";
import * as derivedData from "../../foundation/json-parsing/derived-data";
import * as serverData from "../../foundation/json-parsing/server-data";
import * as mediaAttributes from "../../foundation/media/attributes";
import * as mediaDataFetching from "../../foundation/media/data-fetching";
import * as mediaPlatformAttributes from "../../foundation/media/platform-attributes";
import * as mediaRelationship from "../../foundation/media/relationships";
import * as mediaUrlBuilder from "../../foundation/media/url-builder";
import { Parameters, Path, Protocol } from "../../foundation/network/url-constants";
import * as color from "../../foundation/util/color-util";
import * as dateUtil from "../../foundation/util/date-util";
import { unreachable } from "../../foundation/util/errors";
import { isDefinedNonNullNonEmpty } from "@apple-media-services/media-api";
import { editorialCardFromData } from "../../foundation/media/associations";
import * as client from "../../foundation/wrappers/client";
import * as videoDefaults from "../constants/video-constants";
import * as filtering from "../filtering";
import * as lockups from "../lockups/lockups";
import * as metricsHelpersImpressions from "../metrics/helpers/impressions";
import * as metricsHelpersLocation from "../metrics/helpers/location";
import * as metricsHelpersMedia from "../metrics/helpers/media";
import * as offers from "../offers/offers";
import * as productPageVariants from "../product-page/product-page-variants";
import * as artwork from "./artwork/artwork";
import * as contentAttributes from "./attributes";
import * as contentDeviceFamily from "./device-family";
import * as sad from "./sad";
import { isFeatureEnabledForCurrentUser } from "../util/lottery";
class RunnabilityInfo {
    constructor() {
        this.runsOnIntel = true;
        this.runsOnAppleSilicon = true;
        this.requiresRosetta = false;
    }
}
/**
 * Determines a reasonable artwork use case from a given shelf style
 *
 * @param shelfStyle The shelf style to consider
 */
export function artworkUseCaseFromShelfStyle(objectGraph, shelfStyle) {
    switch (shelfStyle) {
        case "inAppPurchaseLockup":
        case "appShowcase":
        case "smallLockup": {
            return 1 /* ArtworkUseCase.LockupIconSmall */;
            break;
        }
        case "mediumLockup": {
            return 2 /* ArtworkUseCase.LockupIconMedium */;
            break;
        }
        case "largeLockup": {
            return 3 /* ArtworkUseCase.LockupIconLarge */;
            break;
        }
        default: {
            return 0 /* ArtworkUseCase.Default */;
        }
    }
}
/**
 * Convert an API artwork object into an Artwork model object.
 * @param artwork The artwork in API format.
 * @returns An `Artwork` object.
 */
export function artworkFromApiArtwork(objectGraph, artworkData, options) {
    return validation.context("artworkFromApiArtwork", () => {
        var _a, _b, _c;
        const allowingTransparency = serverData.isDefinedNonNull(options.allowingTransparency)
            ? options.allowingTransparency
            : false;
        const useJoeColorDefault = objectGraph.client.isVision || objectGraph.client.isWeb;
        const withJoeColorPlaceholder = serverData.isDefinedNonNull(options.withJoeColorPlaceholder)
            ? options.withJoeColorPlaceholder
            : useJoeColorDefault;
        const artworkUrl = serverData.asString(artworkData, "url");
        if (serverData.isNull(artworkUrl)) {
            return null;
        }
        // Whether wide gamut is supported
        const supportsWideGamut = serverData.asBooleanOrFalse(artworkData, "hasP3");
        // Add base variant
        const variants = [
            artwork.createArtworkVariantForClient(objectGraph, allowingTransparency, supportsWideGamut, options.useCase),
        ];
        // Add layered image variant
        const supportsLayeredImage = serverData.asBooleanOrFalse(artworkData, "supportsLayeredImage");
        if (supportsLayeredImage && (objectGraph.client.isTV || objectGraph.client.isVision)) {
            variants.push(artwork.createArtworkVariantForFormat(objectGraph, "lcr", supportsWideGamut, options.useCase));
        }
        // Artwork Placeholder Color
        // If we indicate the image could be transparent then we don't want a placeholder background
        let placeholderBackgroundColor = null;
        if (allowingTransparency) {
            placeholderBackgroundColor = color.named("clear");
        }
        else if (withJoeColorPlaceholder) {
            const joeColorHexSet = joeColorHexSetFromData(artworkData);
            const placeholderColorHex = (_b = (_a = options.joeColorPlaceholderSelectionLogic) === null || _a === void 0 ? void 0 : _a.call(options, joeColorHexSet)) !== null && _b !== void 0 ? _b : serverData.asString(artworkData, "bgColor");
            const apiBackgroundColor = color.fromHex(placeholderColorHex);
            if (!serverData.isNull(apiBackgroundColor)) {
                placeholderBackgroundColor = apiBackgroundColor;
            }
        }
        // If we don't want clear, joe color, or joe color fails to parse then fall back to default background
        if (serverData.isNull(placeholderBackgroundColor) && !objectGraph.client.isVision) {
            placeholderBackgroundColor = color.named("placeholderBackground");
        }
        const textColorKey = (_c = options.overrideTextColorKey) !== null && _c !== void 0 ? _c : "textColor1";
        const apiTextColor = color.fromHex(serverData.asString(artworkData, textColorKey));
        const artworkModel = new modelsBase.Artwork(artworkUrl, options.overrideWidth || serverData.asNumber(artworkData, "width"), options.overrideHeight || serverData.asNumber(artworkData, "height"), variants);
        artworkModel.backgroundColor = placeholderBackgroundColor;
        artworkModel.checksum = serverData.asString(artworkData, "checksum");
        if (serverData.isDefinedNonNull(apiTextColor)) {
            artworkModel.textColor = apiTextColor;
        }
        if (serverData.isDefinedNonNull(options.style)) {
            artworkModel.style = options.style;
        }
        if (serverData.isDefinedNonNull(options.cropCode)) {
            artworkModel.crop = options.cropCode;
        }
        if (serverData.isDefinedNonNull(options.contentMode)) {
            artworkModel.contentMode = options.contentMode;
        }
        return artworkModel;
    });
}
export function impressionableAppIconFromData(objectGraph, data, metricsOptions, artworkOptions) {
    return validation.context("impressionableAppIconFromData", () => {
        const rawArtwork = iconFromData(objectGraph, data, artworkOptions);
        if (!serverData.isDefinedNonNull(rawArtwork)) {
            return null;
        }
        const icon = new models.ImpressionableArtwork(rawArtwork);
        const title = mediaAttributes.attributeAsString(data, "name");
        const metricsImpressionOptions = metricsHelpersImpressions.impressionOptions(objectGraph, data, title, metricsOptions);
        metricsHelpersImpressions.addImpressionFields(objectGraph, icon, metricsImpressionOptions);
        return icon;
    });
}
/**
 * Batch method for `impressionableAppIconFromData`. Doesn't push location stack or increment location counter, matching other behavior with other icon grids.
 * @param dataCollection Data container array with app data.
 * @param metricsOptions Metrics blob containing information about page and location.
 * @returns Array of `ImpressionableArtwork`
 */
export function impressionableAppIconsFromDataCollection(objectGraph, dataCollection, metricsOptions, artworkOptions) {
    return validation.context("impressionableAppIconFromData", () => {
        const icons = [];
        if (serverData.isNullOrEmpty(metricsOptions.targetType)) {
            metricsOptions.targetType = "artwork";
        }
        for (const data of dataCollection) {
            const icon = impressionableAppIconFromData(objectGraph, data, metricsOptions, artworkOptions);
            if (icon) {
                icons.push(icon);
                metricsHelpersLocation.nextPosition(metricsOptions.locationTracker);
            }
        }
        return icons;
    });
}
/**
 * Defines possible use cases for SearchChartOrCategoryBrick.
 */
export var SearchChartOrCategoryBrickUseCase;
(function (SearchChartOrCategoryBrickUseCase) {
    SearchChartOrCategoryBrickUseCase[SearchChartOrCategoryBrickUseCase["seeAllPage"] = 0] = "seeAllPage";
    SearchChartOrCategoryBrickUseCase[SearchChartOrCategoryBrickUseCase["categoryBreakout"] = 1] = "categoryBreakout";
    SearchChartOrCategoryBrickUseCase[SearchChartOrCategoryBrickUseCase["other"] = 2] = "other";
})(SearchChartOrCategoryBrickUseCase || (SearchChartOrCategoryBrickUseCase = {}));
/**
 * Gets all possible artwork that this chart or category can show
 * @param objectGraph
 * @param data
 * @param isForSeeAllPage Whether or not the chart or category is on the see-all page or not;
 * this is because the see-all page should always have the `Density1` style
 * @param style The style of the chart or category that will be rendered
 * @returns All permutations of artowrk that the chart or category can show
 */
export function searchChartOrCategoryArtworkFromData(objectGraph, data, useCase, style) {
    const artworkPath = "editorialArtwork.searchCategoryBrick";
    const artworkData = contentAttributes.contentAttributeAsDictionary(objectGraph, data, artworkPath);
    if (serverData.isNullOrEmpty(artworkData)) {
        return null;
    }
    let artworkStyle = style;
    if (useCase === SearchChartOrCategoryBrickUseCase.seeAllPage) {
        artworkStyle = models.GenericSearchPageShelfDisplayStyleDensity.Density1;
    }
    /// Crops = [LTR crop, RTL crop]
    /// ContentModes = [ContentMode for LTR crop, ContentMode for RTL crop]
    /// Note: These must be the same length
    let crops = [];
    let contentModes = [];
    switch (artworkStyle) {
        /// Tile
        case models.GenericSearchPageShelfDisplayStyleDensity.Density1:
            const width = useCase === SearchChartOrCategoryBrickUseCase.categoryBreakout ? "1191" : "2350";
            artworkData["width"] = width;
            artworkData["height"] = "670";
            crops = ["SCB.ApSCBL01", "SCB.ApSCBL03"];
            contentModes = [modelsBase.ArtworkContentMode.right, modelsBase.ArtworkContentMode.left];
            break;
        /// Pill
        case models.GenericSearchPageShelfDisplayStyleDensity.Density2:
            artworkData["width"] = "2482";
            artworkData["height"] = "670";
            crops = ["SCB.ApSCBS01", "SCB.ApSCBS02"];
            contentModes = [modelsBase.ArtworkContentMode.left, modelsBase.ArtworkContentMode.right];
            break;
        /// Round
        case models.GenericSearchPageShelfDisplayStyleDensity.Density3:
            artworkData["width"] = "670";
            artworkData["height"] = "670";
            crops = ["cc"];
            contentModes = [modelsBase.ArtworkContentMode.scaleAspectFit];
            break;
        default:
            break;
    }
    return crops.map((crop, index) => {
        return artworkFromApiArtwork(objectGraph, artworkData, {
            cropCode: crop,
            contentMode: index < contentModes.length ? contentModes[index] : null,
            useCase: 0 /* ArtworkUseCase.Default */,
            withJoeColorPlaceholder: true,
        });
    });
}
/**
 * Create an icon artwork from the provided data.
 * @param objectGraph The object graph.
 * @param data The data object to pull icon data from.
 * @param artworkOptions The options for creating the artwork.
 * @param clientIdentifierOverride A client identifier override.
 * @param productVariantData The product variant data to use to select the icon.
 * @param attributePlatformOverride An override platform, from which to fetch the icon.
 * @returns An `Artwork` object representing the icon.
 */
export function iconFromData(objectGraph, data, artworkOptions, clientIdentifierOverride, productVariantData, attributePlatformOverride = undefined) {
    return validation.context("iconFromData", () => {
        if (!data) {
            validation.unexpectedNull("ignoredValue", "data");
            return null;
        }
        const attributePlatform = attributePlatformOverride !== null && attributePlatformOverride !== void 0 ? attributePlatformOverride : iconAttributePlatform(objectGraph, data, clientIdentifierOverride);
        const usePrerenderedIconArtwork = shouldUsePrerenderedIconArtwork(objectGraph);
        // The preferred client identifier to use when selecting the artwork.
        // This client identifier here ensures that we always prefer pill artwork for messages and circular artwork for watch / vision.
        // Unless there's an override specified where another artwork type needs to be used (for example, in developer pages).
        const preferredClientIdentifier = clientIdentifierOverride || objectGraph.host.clientIdentifier;
        // Watch
        const watchIcon = watchIconFromData(objectGraph, data, artworkOptions, preferredClientIdentifier, usePrerenderedIconArtwork, attributePlatform);
        if (isSome(watchIcon)) {
            return watchIcon;
        }
        // Messages
        const messagesIcon = messagesIconFromData(objectGraph, data, artworkOptions, preferredClientIdentifier, attributePlatform);
        if (isSome(messagesIcon)) {
            return messagesIcon;
        }
        // In-App Purchases
        const iapIcon = inAppPurchaseIconFromData(objectGraph, data, artworkOptions);
        if (isSome(iapIcon)) {
            return iapIcon;
        }
        // Bundles
        const bundlesIcon = bundlesIconFromData(objectGraph, data, artworkOptions, usePrerenderedIconArtwork);
        if (isSome(bundlesIcon)) {
            return bundlesIcon;
        }
        // Calculate variant data if one wasn't provided from caller.
        if (serverData.isNull(productVariantData)) {
            productVariantData = productPageVariants.productVariantDataForData(objectGraph, data);
        }
        const artworkData = contentAttributes.customAttributeAsDictionary(objectGraph, data, productVariantData, "artwork", attributePlatform);
        // tvOS
        const tvIcon = tvIconFromData(objectGraph, artworkData, artworkOptions, preferredClientIdentifier, attributePlatform);
        if (isSome(tvIcon)) {
            return tvIcon;
        }
        // visionOS
        const visionIcon = visionIconFromData(objectGraph, artworkData, artworkOptions, preferredClientIdentifier, attributePlatform);
        if (isSome(visionIcon)) {
            return visionIcon;
        }
        // macOS & iOS
        return macOSOriOSIconFromData(objectGraph, data, artworkData, artworkOptions, usePrerenderedIconArtwork, productVariantData, attributePlatform);
    });
}
/**
 * Determines if a client is capable of showing pre-rendered icon artwork, and if the relevant
 * feature / bag flags are enabled.
 * @param objectGraph Current object graph
 * @returns True if we should use prerendered icon artwork.
 */
export function shouldUsePrerenderedIconArtwork(objectGraph) {
    const clientSupportsPrerenderedIconArtwork = objectGraph.client.isWatch || objectGraph.client.isiOS || objectGraph.client.isMac || objectGraph.client.isWeb;
    const isEnabledForUser = isFeatureEnabledForCurrentUser(objectGraph, objectGraph.bag.iconArtworkRolloutRate);
    return (isEnabledForUser &&
        objectGraph.bag.enableIconArtwork &&
        objectGraph.client.isIconArtworkCapable &&
        clientSupportsPrerenderedIconArtwork);
}
function watchIconFromData(objectGraph, data, artworkOptions, clientIdentifier, usePrerenderedIconArtwork, attributePlatform) {
    if (clientIdentifier !== client.watchIdentifier &&
        !contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "isStandaloneForWatchOS") &&
        !objectGraph.client.isWatch) {
        return null;
    }
    // Attempt to use pre-rendered circular icon artwork first, if applicable
    if (usePrerenderedIconArtwork) {
        const iconArtworkData = mediaPlatformAttributes.platformAttributeAsDictionary(data, attributePlatform, "circularIconArtwork");
        if (isSome(iconArtworkData)) {
            return artworkFromApiArtwork(objectGraph, iconArtworkData, {
                ...artworkOptions,
                style: "roundPrerendered",
                cropCode: "bb",
                withJoeColorPlaceholder: true,
            });
        }
    }
    // Fallback to the legacy icon artwork
    const artworkData = mediaPlatformAttributes.platformAttributeAsDictionary(data, attributePlatform, "circularArtwork");
    if (isSome(artworkData)) {
        const style = usePrerenderedIconArtwork ? "roundPrerendered" : "round";
        const cropCode = usePrerenderedIconArtwork ? "ic" : undefined;
        return artworkFromApiArtwork(objectGraph, artworkData, {
            ...artworkOptions,
            style: style,
            cropCode: cropCode,
            withJoeColorPlaceholder: true,
        });
    }
    return null;
}
function messagesIconFromData(objectGraph, data, artworkOptions, clientIdentifier, attributePlatform) {
    const isHiddenFromSpringboard = isHiddenFromSpringboardFromData(objectGraph, data);
    const hasMessagesExtension = hasMessagesExtensionFromData(objectGraph, data);
    const shouldShowMessagesIcon = hasMessagesExtension && (clientIdentifier === client.messagesIdentifier || isHiddenFromSpringboard);
    const artworkData = mediaPlatformAttributes.platformAttributeAsDictionary(data, attributePlatform, "ovalArtwork");
    if (shouldShowMessagesIcon && serverData.isDefinedNonNull(artworkData)) {
        return artworkFromApiArtwork(objectGraph, artworkData, {
            ...artworkOptions,
            style: "pill",
        });
    }
    return null;
}
function inAppPurchaseIconFromData(objectGraph, data, artworkOptions) {
    if (data.type !== "in-apps") {
        return null;
    }
    const artworkData = mediaAttributes.attributeAsDictionary(data, "artwork");
    if (isSome(artworkData)) {
        return artworkFromApiArtwork(objectGraph, artworkData, {
            ...artworkOptions,
            style: "iap",
        });
    }
    return null;
}
function bundlesIconFromData(objectGraph, data, artworkOptions, usePrerenderedIconArtwork) {
    if (data.type !== "app-bundles") {
        return null;
    }
    // Attempt to use pre-rendered icon artwork first, if applicable
    if (usePrerenderedIconArtwork) {
        const iconArtworkData = mediaAttributes.attributeAsDictionary(data, "iconArtwork");
        if (isSome(iconArtworkData)) {
            return artworkFromApiArtwork(objectGraph, iconArtworkData, {
                ...artworkOptions,
                style: "roundedRectPrerendered",
                cropCode: "bb",
            });
        }
    }
    // Fallback to the legacy icon artwork
    const artworkData = mediaAttributes.attributeAsDictionary(data, "artwork");
    if (isSome(artworkData)) {
        const style = usePrerenderedIconArtwork ? "roundedRectPrerendered" : "roundedRect";
        const cropCode = usePrerenderedIconArtwork ? "ia" : undefined;
        return artworkFromApiArtwork(objectGraph, artworkData, {
            ...artworkOptions,
            style: style,
            cropCode: cropCode,
            allowingTransparency: true,
        });
    }
    return null;
}
function tvIconFromData(objectGraph, artworkData, artworkOptions, clientIdentifier, attributePlatform) {
    if (attributePlatform !== "appletvos" && clientIdentifier !== client.tvIdentifier) {
        return null;
    }
    return artworkFromApiArtwork(objectGraph, artworkData, {
        ...artworkOptions,
        style: "tvRect",
    });
}
function visionIconFromData(objectGraph, artworkData, artworkOptions, clientIdentifier, attributePlatform) {
    if (attributePlatform !== "xros" && clientIdentifier !== "VisionAppStore" /* ClientIdentifier.VisionAppStore */) {
        return null;
    }
    return artworkFromApiArtwork(objectGraph, artworkData, {
        ...artworkOptions,
        style: "round",
    });
}
function macOSOriOSIconFromData(objectGraph, data, artworkData, artworkOptions, usePrerenderedIconArtwork, productVariantData, attributePlatform) {
    const isMac = attributePlatform === "osx";
    const allowTransparency = isMac && !preprocessor.GAMES_TARGET;
    // Attempt to use pre-rendered icon artwork first, if applicable
    if (usePrerenderedIconArtwork) {
        const iconArtworkData = contentAttributes.customAttributeAsDictionary(objectGraph, data, productVariantData, "iconArtwork", attributePlatform);
        if (isSome(iconArtworkData)) {
            return artworkFromApiArtwork(objectGraph, iconArtworkData, {
                ...artworkOptions,
                style: "roundedRectPrerendered",
                cropCode: "bb",
                allowingTransparency: allowTransparency,
            });
        }
    }
    // Fallback to the standard icon artwork
    let style;
    let cropCode;
    if (usePrerenderedIconArtwork) {
        style = "roundedRectPrerendered";
        cropCode = isMac ? "ib" : "ia";
    }
    else {
        style = isMac ? "unadorned" : "roundedRect";
        cropCode = "bb";
    }
    return artworkFromApiArtwork(objectGraph, artworkData, {
        ...artworkOptions,
        style: style,
        cropCode: cropCode,
        allowingTransparency: allowTransparency,
    });
}
/**
 * Determines the best attribute platform to use for the icon.
 * @param objectGraph Current object graph
 * @param data The product data
 * @param clientIdentifierOverride The client identifier override to use, if any
 * @returns
 */
export function iconAttributePlatform(objectGraph, data, clientIdentifierOverride) {
    switch (clientIdentifierOverride) {
        case client.watchIdentifier:
        case client.messagesIdentifier: {
            return "ios";
        }
        case client.tvIdentifier: {
            return "appletvos";
        }
        case "VisionAppStore" /* ClientIdentifier.VisionAppStore */: {
            return "xros";
        }
        default: {
            return contentAttributes.bestAttributePlatformFromData(objectGraph, data, clientIdentifierOverride);
        }
    }
}
/**
 * Determine the media platform, given the app platform and screenshot type.
 * @param appPlatform The app platform specific to this media.
 * @param type The response screenshot type, which is applicable for both screenshots and trailers, for this media.
 * @param supplementaryAppPlatforms
 * @returns {MediaPlatform} The configured media platform object.
 * TODO: legacy_export
 */
export function mediaPlatformForTypeAndAppPlatform(objectGraph, appPlatform, type, supplementaryAppPlatforms) {
    if (!appPlatform) {
        return null;
    }
    const systemImageName = systemImageNameForAppPlatform(appPlatform);
    const deviceCornerRadius = deviceCornerRadiusFactorForMediaType(objectGraph, type);
    const deviceBorderThickness = deviceBorderThicknessForMediaType(objectGraph, type);
    const outerDeviceCornerRadius = deviceOuterCornerRadiusFactorForMediaType(objectGraph, type);
    return new modelsBase.MediaPlatform(appPlatform, type, systemImageName, supplementaryAppPlatforms, deviceCornerRadius, deviceBorderThickness, outerDeviceCornerRadius);
}
/**
 * Configures the trailers object from the platform data.
 * @param data The platform data to use.
 * @param videoConfiguration config to use for the trailers
 * @param metricsOptions The metrics options to use.
 * @param adamId The adamId for the lockup.
 * @param isAd Whether the trailers are for an ad lockup. Defaults to false.
 * @param cropCode The crop code to use for the video preview.
 * @returns {Trailers} The configured trailers object.
 */
export function trailersFromData(objectGraph, data, videoConfiguration, metricsOptions, adamId, isAd = false, cropCode) {
    const platformVideos = platformVideoPreviewFromData(objectGraph, data, videoConfiguration, null, null, isAd, cropCode);
    if (!platformVideos) {
        return null;
    }
    const videoPreviews = platformVideos.videos;
    const trailerVideos = [];
    if (videoPreviews && videoPreviews.length > 0) {
        for (const trailerVideo of videoPreviews) {
            metricsHelpersMedia.addMetricsEventsToVideo(objectGraph, trailerVideo, {
                ...metricsOptions,
                id: adamId,
            });
            trailerVideos.push(trailerVideo);
        }
    }
    let trailers = null;
    if (trailerVideos.length > 0) {
        trailers = new modelsShelves.Trailers();
        trailers.videos = trailerVideos;
        trailers.mediaPlatform = platformVideos.mediaPlatform;
    }
    return trailers;
}
/**
 * A convenience class for encapsulating a `Video` that is tied to a specific `MediaPlatform`.
 */
class PlatformVideos {
    constructor(videos, mediaPlatform) {
        this.videos = videos;
        this.mediaPlatform = mediaPlatform;
    }
}
/**
 * Finds the best platform video previews to use for the given parameters.
 * @param data The data from which to derive the platform videos.
 * @param videoConfiguration A video configuration to use for the videos
 * @param includedAppPlatforms If provided, restricts the resulting platform videos to only these platforms
 * @param productVariantData
 * @param isAd Whether the video preview data is for an ad. Defaults to false.
 * @param cropCode The crop code to use for the preview artwork.
 * @returns The best available platform videos.
 */
export function platformVideoPreviewFromData(objectGraph, data, videoConfiguration, includedAppPlatforms = null, productVariantData = null, isAd = false, cropCode) {
    return validation.context("platformVideoPreviewFromData", () => {
        if (serverData.isNull(productVariantData)) {
            productVariantData = productPageVariants.productVariantDataForData(objectGraph, data); // create variant data if not provided.
        }
        const videoPreviewsByTypeData = videoPreviewsByTypeFromData(objectGraph, data, productVariantData, isAd);
        const videoPreviewsByType = {};
        if (!videoPreviewsByTypeData) {
            return null;
        }
        let sortedAppPlatforms = sortedAppPlatformsFromData(objectGraph, data, objectGraph.host.clientIdentifier, objectGraph.client.deviceType);
        if (serverData.isDefinedNonNull(includedAppPlatforms)) {
            // If we have a restricted set of included app platforms, use those platforms
            // to build our sortedAppPlatforms array in the proper sort order
            const includedSortedAppPlatforms = [];
            for (const appPlatform of sortedAppPlatforms) {
                if (includedAppPlatforms.includes(appPlatform)) {
                    includedSortedAppPlatforms.push(appPlatform);
                }
            }
            sortedAppPlatforms = includedSortedAppPlatforms;
        }
        if (sortedAppPlatforms.length === 0) {
            return null;
        }
        for (const appPlatform of sortedAppPlatforms) {
            const types = mediaTypesForAppPlatform(objectGraph, appPlatform, objectGraph.client.screenSize);
            for (const type of Object.keys(videoPreviewsByTypeData)) {
                const videosDataForType = serverData.asArrayOrEmpty(videoPreviewsByTypeData, type);
                const videosForType = [];
                for (const video of videosDataForType) {
                    const previewFrame = serverData.asDictionary(video, "previewFrame");
                    if (!previewFrame) {
                        validation.unexpectedNull("ignoredValue", "object", `videoPreviewsByType.${type}.previewFrame`);
                        continue;
                    }
                    const videoUrl = serverData.asString(video, "video");
                    if (!videoUrl) {
                        validation.unexpectedNull("ignoredValue", "string", `videoPreviewsByType.${type}.video`);
                        continue;
                    }
                    const preview = artwork.createArtworkForResource(objectGraph, serverData.asString(previewFrame, "url"), serverData.asNumber(previewFrame, "width"), serverData.asNumber(previewFrame, "height"), null, null, serverData.asString(previewFrame, "checksum"));
                    if (serverData.isDefinedNonNull(cropCode)) {
                        preview.crop = cropCode;
                    }
                    videosForType.push(new modelsBase.Video(videoUrl, preview, videoConfiguration));
                }
                videoPreviewsByType[type] = videosForType;
            }
            for (const type of types) {
                if (videoPreviewsByType[type]) {
                    return new PlatformVideos(videoPreviewsByType[type], mediaPlatformForTypeAndAppPlatform(objectGraph, appPlatform, type));
                }
            }
        }
        return null;
    });
}
/**
 * Configures the videos from some platform data.
 * @param data  The store platform data.
 * @returns A list of `Video` objects.
 */
export function videoPreviewsFromData(objectGraph, data) {
    return validation.context("videoPreviewsFromApiPlatformData", () => {
        const platformVideos = platformVideoPreviewFromData(objectGraph, data, videoDefaults.defaultVideoConfiguration(objectGraph));
        if (platformVideos) {
            return platformVideos.videos;
        }
        else {
            return [];
        }
    });
}
/**
 * Determines the `AppPlatform` to use, in order to determine appropriate `MediaType` for media.
 * @param {AppPlatform} appPlatform The underlying `AppPlatform`.
 * @returns {AppPlatform} The `AppPlatform` that we map to in order to select the appropriate `MediaType`.
 */
function selectionAppPlatformFromAppPlatform(objectGraph, appPlatform) {
    if (appPlatform === "messages") {
        switch (objectGraph.client.deviceType) {
            case "pad": {
                return "pad";
            }
            default: {
                return "phone";
            }
        }
    }
    return appPlatform;
}
/**
 * Provide the caller with an ordered array of screenshots for a given context. The first object can be used in search
 * lockups, and the array will only contain screenshots for the supported app platforms.
 *
 * @param data The api product data (containing supported app platforms and screenshots)
 * @param useCase
 * @param includedAppPlatforms Optionally, a list of app platforms to confine the screenshots to.
 * @param clientIdentifierOverride
 * @param productVariantData
 * @param isAd Whether the screenshots are being gathered for an ad lockup. Defaults to false.
 * @returns An ordered array of screenshots for display on a product page
 * */
export function screenshotsFromData(objectGraph, data, useCase, includedAppPlatforms = null, clientIdentifierOverride, productVariantData, isAd = false, cropCode) {
    return validation.context("screenshotsFromData", () => {
        const screenshots = [];
        if (serverData.isNull(productVariantData)) {
            productVariantData = productPageVariants.productVariantDataForData(objectGraph, data); // resolve if not resolved by caller.
        }
        let sortedAppPlatforms = includedAppPlatforms;
        if (!sortedAppPlatforms || sortedAppPlatforms.length === 0) {
            const preferredClientIdentifier = clientIdentifierOverride || objectGraph.host.clientIdentifier;
            let preferredDeviceType = objectGraph.client.deviceType;
            if (preferredClientIdentifier === client.watchIdentifier) {
                preferredDeviceType = "watch";
            }
            if (clientIdentifierOverride === "VisionAppStore" /* ClientIdentifier.VisionAppStore */ ||
                clientIdentifierOverride === "com.apple.visionproapp" /* ClientIdentifier.VisionCompanion */) {
                preferredDeviceType = "vision";
            }
            sortedAppPlatforms = sortedAppPlatformsFromData(objectGraph, data, preferredClientIdentifier, preferredDeviceType);
        }
        for (const appPlatform of sortedAppPlatforms) {
            const supplementaryAppPlatforms = [];
            let screenshotData;
            if (appPlatform === "messages") {
                screenshotData = messagesScreenshotsFromData(objectGraph, data, "ios");
                if (supportsFunCameraFromData(objectGraph, data, "ios")) {
                    supplementaryAppPlatforms.push("faceTime");
                }
            }
            else if (appPlatform === "tv" && !objectGraph.host.isTV) {
                // For tvOS screenshots displayed on other platforms.
                screenshotData = screenshotsByTypeFromData(objectGraph, data, productVariantData, isAd, "appletvos");
            }
            else if (appPlatform === "vision" && !objectGraph.host.isVision) {
                // For visionOS screenshots displayed on other platforms.
                screenshotData = screenshotsByTypeFromData(objectGraph, data, productVariantData, isAd, "xros");
            }
            else if (appPlatform === "mac" && !objectGraph.host.isMac) {
                // For Mac screenshots displayed on other platforms.
                screenshotData = screenshotsByTypeFromData(objectGraph, data, productVariantData, isAd, "osx");
            }
            else if ((appPlatform === "phone" || appPlatform === "pad" || appPlatform === "watch") &&
                !objectGraph.host.isiOS &&
                !objectGraph.host.isWatch) {
                // For iPhone / iPad / watch screenshots displayed on other platforms.
                screenshotData = screenshotsByTypeFromData(objectGraph, data, productVariantData, isAd, "ios");
            }
            else {
                screenshotData = screenshotsByTypeFromData(objectGraph, data, productVariantData, isAd);
            }
            if (!screenshotData) {
                continue;
            }
            const bestScreenshots = bestScreenshotData(objectGraph, screenshotData, appPlatform, useCase, supplementaryAppPlatforms, cropCode);
            if (bestScreenshots) {
                screenshots.push(bestScreenshots);
            }
        }
        return screenshots;
    });
}
/**
 *  Creates an array of product media from the given screenshots. If videos are desired
 *  to be inserted in the same media row, this must be done elsewhere.
 *  @param objectGraph The object graph.
 *  @param data Apps resource data.
 *  @param screenshots The screenshots objects with which to configure the media.
 *  @return A list of product media objects.
 */
function productMediaFromScreenshots(objectGraph, data, screenshots) {
    const allMedia = [];
    if (screenshots && screenshots.length > 0) {
        const allPlatforms = screenshots.map((platformScreenshots) => {
            return platformScreenshots.mediaPlatform;
        });
        for (const screenshotsForPlatform of screenshots) {
            // Create media items from all the screenshots.
            const screenshotMediaItems = [];
            for (const screenshotArtwork of screenshotsForPlatform.artwork) {
                const screenshotItem = new modelsShelves.ProductMediaItem();
                screenshotItem.screenshot = screenshotArtwork;
                screenshotMediaItems.push(screenshotItem);
            }
            const platform = screenshotsForPlatform.mediaPlatform;
            const productMedia = new modelsShelves.ProductMedia(screenshotMediaItems, platform, allPlatforms, descriptionOfMediaPlatform(objectGraph, platform), descriptionOfAllMediaPlatforms(objectGraph, data, allPlatforms), placementOfAllMediaPlatformsDescription(objectGraph, data, allPlatforms));
            allMedia.push(productMedia);
        }
    }
    return allMedia;
}
/**
 * Build a set of of `ProductMedia` from apps resource
 * @param data Apps resource data
 * @param useCase Artwork use case
 * @param includedAppPlatforms What platforms are included.
 * @param productVariantData A variant to use. This can be populated as an optimization to avoid re-resolving the same variant data, e.g. in a product page.
 * @param clientIdentifierOverride
 */
export function productMediaFromData(objectGraph, data, useCase, includedAppPlatforms = null, productVariantData = null, clientIdentifierOverride) {
    const screenshots = screenshotsFromData(objectGraph, data, useCase, includedAppPlatforms, clientIdentifierOverride, productVariantData);
    return productMediaFromScreenshots(objectGraph, data, screenshots);
}
/**
 * Finds the best screenshot data from a response to use for the given parameters.
 * @param data The data from which to derive the screenshots.
 * @param appPlatform The app platform to which the screenshots belong.
 * @param supplementaryAppPlatforms
 * @returns The best available screenshots.
 */
function bestScreenshotData(objectGraph, data, appPlatform, useCase, supplementaryAppPlatforms, cropCode) {
    const selectionPlatform = selectionAppPlatformFromAppPlatform(objectGraph, appPlatform);
    const screenshotTypes = mediaTypesForAppPlatform(objectGraph, selectionPlatform, objectGraph.client.screenSize);
    let bestScreenshot = null;
    let bestScreenshotType;
    for (let i = 0; i < screenshotTypes.length && !serverData.isDefinedNonNullNonEmpty(bestScreenshot); i++) {
        bestScreenshot = serverData.asArrayOrEmpty(data, screenshotTypes[i]);
        bestScreenshotType = screenshotTypes[i];
    }
    if (serverData.isDefinedNonNullNonEmpty(bestScreenshot)) {
        const artworks = bestScreenshot.map(function (screenshotArtwork) {
            return artworkFromApiArtwork(objectGraph, screenshotArtwork, {
                useCase: useCase,
                cropCode: cropCode,
            });
        });
        const platform = mediaPlatformForTypeAndAppPlatform(objectGraph, appPlatform, bestScreenshotType, supplementaryAppPlatforms);
        const screenshots = new modelsBase.Screenshots(artworks, platform);
        return screenshots;
    }
    return null;
}
/**
 * Returns a list of sorted app platforms for displaying screenshots. This contains the sorting logic for screenshots.
 *
 * @param data Server data for the app
 * @param clientIdentifier Identifier of the current client.
 * @param deviceType Type of the current device.
 * @returns A sorted list of AppPlatform values to use when displaying
 * */
export function sortedAppPlatformsFromData(objectGraph, data, clientIdentifier, deviceType) {
    return derivedData.value(data, `sortedAppPlatformsFromData.${clientIdentifier}.${deviceType}`, () => {
        var _a;
        const supportedAppPlatforms = supportedAppPlatformsFromData(objectGraph, data);
        const excludedAppPlatforms = [];
        let sortedAppPlatforms = [];
        const addAppPlatformIfPossible = function (appPlatform, excludePlatform) {
            if (sortedAppPlatforms.indexOf(appPlatform) !== -1) {
                return;
            }
            if (excludedAppPlatforms.indexOf(appPlatform) !== -1) {
                return;
            }
            if (supportedAppPlatforms.indexOf(appPlatform) !== -1) {
                sortedAppPlatforms.push(appPlatform);
                if (excludePlatform) {
                    excludedAppPlatforms.push(excludePlatform);
                }
            }
        };
        // If there is an `AppPlatform` associated with the active `Intent`, give
        // that first priority
        if ((_a = objectGraph.activeIntent) === null || _a === void 0 ? void 0 : _a.appPlatform) {
            addAppPlatformIfPossible(objectGraph.activeIntent.appPlatform);
        }
        if (clientIdentifier === "VisionAppStore" /* ClientIdentifier.VisionAppStore */ ||
            clientIdentifier === "com.apple.visionproapp" /* ClientIdentifier.VisionCompanion */) {
            addAppPlatformIfPossible("vision");
        }
        // Next, priority is given to the client
        switch (clientIdentifier) {
            case client.watchIdentifier: {
                addAppPlatformIfPossible("watch");
                break;
            }
            case client.messagesIdentifier: {
                addAppPlatformIfPossible("messages");
                break;
            }
            default: {
                break;
            }
        }
        // Next the current device type
        switch (deviceType) {
            case "phone": {
                addAppPlatformIfPossible("phone");
                break;
            }
            case "pad": {
                addAppPlatformIfPossible("pad");
                break;
            }
            case "tv": {
                addAppPlatformIfPossible("tv");
                break;
            }
            case "watch": {
                addAppPlatformIfPossible("watch");
                break;
            }
            case "mac": {
                addAppPlatformIfPossible("mac");
                break;
            }
            case "vision": {
                addAppPlatformIfPossible("vision");
                break;
            }
            default: {
                break;
            }
        }
        // For Apple Silicon and visionOS, prefer iPad platform over iPhone
        if (clientIdentifier === "VisionAppStore" /* ClientIdentifier.VisionAppStore */ ||
            clientIdentifier === "com.apple.visionproapp" /* ClientIdentifier.VisionCompanion */) {
            addAppPlatformIfPossible("pad");
            addAppPlatformIfPossible("phone");
        }
        if (objectGraph.appleSilicon.isSupportEnabled || objectGraph.client.isVision) {
            addAppPlatformIfPossible("pad");
            addAppPlatformIfPossible("phone");
        }
        else {
            addAppPlatformIfPossible("phone");
            addAppPlatformIfPossible("pad");
        }
        addAppPlatformIfPossible("mac");
        addAppPlatformIfPossible("vision");
        addAppPlatformIfPossible("tv");
        addAppPlatformIfPossible("watch");
        addAppPlatformIfPossible("messages");
        sortedAppPlatforms = sortedAppPlatforms.filter(function (appPlatform) {
            return excludedAppPlatforms.indexOf(appPlatform) === -1;
        });
        return sortedAppPlatforms;
    });
}
/**
 * For a given server data, this will return the Game Center features that the app supports
 *
 * @param data Server data for the app
 * @returns An array of supported Game Center features
 * */
export function supportedGameCenterFeaturesFromData(data) {
    var _a;
    if (isNothing(data)) {
        return undefined;
    }
    return ((_a = derivedData.value(data, "supportedGameCenterFeaturesFromData", () => {
        const features = [];
        const supportedGameCenterFeatures = serverData.asArrayOrEmpty(data, "attributes.supportedGameCenterFeatures");
        if (supportedGameCenterFeatures.includes("achievements")) {
            features.push("achievements");
        }
        if (supportedGameCenterFeatures.includes("challenges")) {
            features.push("challenges");
        }
        if (supportedGameCenterFeatures.includes("leaderboards")) {
            features.push("leaderboards");
        }
        if (supportedGameCenterFeatures.includes("multiplayer-activities")) {
            features.push("multiplayer-activities");
        }
        return features;
    })) !== null && _a !== void 0 ? _a : undefined);
}
/**
 * For a given server data, returns whether the game is eligible for the Games App
 * This will default to true since we generally expect apps we view in the Games app to be games.
 * It will be unusual that this is evaluated to `false`.
 *
 * @param data Server data for the app
 * @returns A boolean indicating whether game is eligible for display
 * */
export function isEligibleForGamesApp(data) {
    var _a;
    if (isNothing(data)) {
        return true;
    }
    return (_a = serverData.asBoolean(data, "attributes.isEligibleForGamesApp")) !== null && _a !== void 0 ? _a : true;
}
/**
 * For a given server data, this will return the platforms that the app supports
 *
 * @param data Server data for the app
 * @returns An array of supported AppPlatforms
 * */
export function supportedAppPlatformsFromData(objectGraph, data) {
    if (!data) {
        return null;
    }
    return derivedData.value(data, "supportedAppPlatformsFromData", () => {
        const hasMessagesExtension = hasMessagesExtensionFromData(objectGraph, data, "ios");
        const isHiddenFromSpringboard = isHiddenFromSpringboardFromData(objectGraph, data);
        const isAppleWatchSupported = isAppleWatchSupportedFromData(objectGraph, data);
        const serverDeviceFamilies = mediaAttributes.attributeAsArrayOrEmpty(data, "deviceFamilies");
        const appPlatforms = [];
        for (const serverDeviceFamily of serverDeviceFamilies) {
            switch (serverDeviceFamily) {
                case "iphone":
                    if (!isHiddenFromSpringboard) {
                        appPlatforms.push("phone");
                    }
                    break;
                case "ipad":
                    if (!isHiddenFromSpringboard) {
                        appPlatforms.push("pad");
                    }
                    break;
                case "tvos":
                    appPlatforms.push("tv");
                    break;
                case "watch":
                    appPlatforms.push("watch");
                    break;
                case "realityDevice":
                    appPlatforms.push("vision");
                    break;
                default:
                    break;
            }
        }
        if (hasMessagesExtension) {
            appPlatforms.push("messages");
        }
        if (isAppleWatchSupported) {
            appPlatforms.push("watch");
        }
        if (contentDeviceFamily.dataHasDeviceFamily(objectGraph, data, "mac")) {
            appPlatforms.push("mac");
        }
        return appPlatforms;
    });
}
/**
 * Returns a localized, user-friendly description of all media platforms. This may be a comma delimited
 * list of the platforms (including supplementary platforms), or it may be 'Only for ___', depending
 * on the context.
 *
 * The localization keys used by this function are defined natively, and are updated using
 * `tools/platform-media-localizations.py`. If the key doesn't exist, then the script needs to
 * be updated to add the new combination/order of platforms.
 *
 * For failed attempts to localize the string, this function will fallback to a default order that is
 * guaranteed to exist.
 *
 * @param objectGraph The object graph.
 * @param data Apps resource data.
 * @param allPlatforms The list of platforms to describe.
 * @returns The friendly description of all platforms.
 */
export function descriptionOfAllMediaPlatforms(objectGraph, data, allPlatforms) {
    if (shouldShowOnlyForPlatformDescription(objectGraph, data, allPlatforms)) {
        const platform = allPlatforms[0];
        const platformKey = platform.appPlatform.toUpperCase();
        return objectGraph.loc.string(`ONLY_FOR_${platformKey}_APP`);
    }
    // Flatten all platform partial keys, including their supplementary platforms
    let keys = allPlatforms.reduce((partialResult, platform) => partialResult.concat(platformLocalizationKeys(platform)), []);
    try {
        // Attempt to localize the constructed key
        return objectGraph.loc.tryString(`PLATFORMS_${keys.join("_")}`);
    }
    catch (error) {
        // If the key does not exist, a best attempt fallback string will be provided.
        const fallbackOrder = ["PHONE", "PAD", "MAC", "VISION", "TV", "WATCH", "MESSAGES", "FACETIME"];
        keys = fallbackOrder.filter((key) => keys.includes(key));
        return objectGraph.loc.string(`PLATFORMS_${keys.join("_")}`);
    }
}
/**
 * Determines where to place the all platforms description, which is visible when the product media is collapsed, or there is only one platform.
 * This is only used by iOS, visionOS & macOS. For tvOS & watchOS, we always put the media description at the bottom.
 *
 * @param objectGraph The object graph.
 * @param data Apps resource data.
 * @param allPlatforms The list of platforms to describe.
 * @returns Where to place the all platforms description.
 */
export function placementOfAllMediaPlatformsDescription(objectGraph, data, allPlatforms) {
    if (shouldShowOnlyForPlatformDescription(objectGraph, data, allPlatforms)) {
        return "top";
    }
    else {
        return "bottom";
    }
}
/**
 * Determines whether we want to use the 'Only for ___' text to describe `allPlatforms`.
 *
 * @param objectGraph The object graph.
 * @param data Apps resource data.
 * @param allPlatforms The list of platforms to describe.
 * @returns Whether we want to use 'Only for ___' text to describe `allPlatforms`.
 */
function shouldShowOnlyForPlatformDescription(objectGraph, data, allPlatforms) {
    if (allPlatforms.length === 1) {
        const platform = allPlatforms[0];
        const supportsMacOSCompatibleIOSBinary = supportsMacOSCompatibleIOSBinaryFromData(objectGraph, data, objectGraph.appleSilicon.isSupportEnabled);
        const supportsVisionOSCompatibleIOSBinary = supportsVisionOSCompatibleIOSBinaryFromData(objectGraph, data);
        const runnableAppPlatforms = runnableAppPlatformsForDevice(objectGraph, objectGraph.client.deviceType, supportsMacOSCompatibleIOSBinary, supportsVisionOSCompatibleIOSBinary);
        const isRunnableOnCurrentDevice = supportsPlatform(runnableAppPlatforms, platform.appPlatform);
        const noSupplementaryPlatforms = platform.supplementaryAppPlatforms.length === 0;
        const isForDifferentDevice = platform.appPlatform !== objectGraph.client.deviceType;
        if (noSupplementaryPlatforms && isForDifferentDevice && !isRunnableOnCurrentDevice) {
            return true;
        }
    }
    return false;
}
/**
 * Returns a localized description of a media platform, including any supplementary platforms.
 * e.g. 'Mac' or 'iMessage, FaceTime'.
 *
 * @param objectGraph Object graph, used for localizing the string.
 * @param allPlatforms The platform to describe.
 * @returns The friendly description of the platform.
 */
export function descriptionOfMediaPlatform(objectGraph, platform) {
    const keys = platformLocalizationKeys(platform);
    return objectGraph.loc.string(`PLATFORMS_${keys.join("_")}`);
}
/**
 * Returns an array of partial loc keys that represent a media platform.
 * This consists the media's app platform + any supplementary platforms.
 * e.g. ["MAC"] or ["MESSAGES", "FACETIME"]
 *
 * @param platform The media platform.
 * @returns The list of partial loc key that represent the media platform.
 */
function platformLocalizationKeys(platform) {
    const appPlatformKey = platform.appPlatform.toUpperCase();
    const supplementaryPlatformKeys = platform.supplementaryAppPlatforms.map((supplementaryPlatform) => supplementaryPlatform.toUpperCase());
    return [appPlatformKey].concat(supplementaryPlatformKeys);
}
/**
 * Determines if a given app has a compatible iOS binary for this client.
 *
 * @param {mediaDataStructure.Data} data    The product data to use.
 * @param {boolean} doesClientSupportMacOSCompatibleIOSBinary   Whether the client supports macOS compatible iOS binaries
 * @returns {boolean}                       True when the app and device are halva.
 */
export function supportsMacOSCompatibleIOSBinaryFromData(objectGraph, data, doesClientSupportMacOSCompatibleIOSBinary) {
    let isIOSBinaryMacOSCompatible = mediaAttributes.attributeAsBooleanOrFalse(data, "isIOSBinaryMacOSCompatible");
    // Override for News in Moltres on Mac
    if (preprocessor.GAMES_TARGET && data.id === "1066498020" && objectGraph.client.deviceType === "mac") {
        isIOSBinaryMacOSCompatible = true;
    }
    return doesClientSupportMacOSCompatibleIOSBinary && isIOSBinaryMacOSCompatible;
}
/**
 * Determines if a given app has a compatible iOS binary for the current client.
 *
 * @param {mediaDataStructure.Data} data The product data to use.
 * @returns {boolean} True when the app and device are visionOS.
 */
export function supportsVisionOSCompatibleIOSBinaryFromData(objectGraph, data) {
    return (objectGraph.client.isVision &&
        mediaPlatformAttributes.platformAttributeAsBooleanOrFalse(data, "ios", "isXROSCompatible"));
}
/**
 * Determines if a given app has a compatible iOS binary for arbitrary clients.
 *
 * @param {mediaDataStructure.Data} data The product data to use.
 * @returns {boolean} True when the app can run on visionOS.
 */
export function supportsVisionOSCompatibleIOSBinaryOnAnyClient(data) {
    return mediaPlatformAttributes.platformAttributeAsBooleanOrFalse(data, "ios", "isXROSCompatible");
}
/**
 * Determines app binary traits.
 *
 * @param {mediaDataStructure.Data} data The product data to use.
 * @returns {string[]} The app binary traits.
 */
export function appBinaryTraitsFromData(objectGraph, data) {
    if (!objectGraph.client.isiOS) {
        return undefined;
    }
    let appBinaryTraits;
    if (objectGraph.isAvailable(ads) &&
        ["debug", "internal"].includes(objectGraph.client.buildType) &&
        isSome(objectGraph.ads.fetchAppBinaryTraitsOverride)) {
        // use client override for debugging internal builds
        appBinaryTraits = objectGraph.ads.fetchAppBinaryTraitsOverride();
    }
    if (isNothing(appBinaryTraits)) {
        // parse from server response
        appBinaryTraits = mediaPlatformAttributes.platformAttributeAsArrayOrEmpty(data, "ios", "appBinaryTraits");
    }
    return appBinaryTraits;
}
/**
 * Determines whether the product has external browser engine.
 * @param objectGraph Current object graph
 * @param data The product data
 * @returns True if the product has external browser engine
 */
export function hasExternalBrowserForData(objectGraph, data) {
    var _a;
    const appBinaryTraits = appBinaryTraitsFromData(objectGraph, data);
    const externalBrowserTraits = new Set(["uses-non-webkit-browser-engine", "is-custom-browser-engine-app"]);
    return (_a = appBinaryTraits === null || appBinaryTraits === void 0 ? void 0 : appBinaryTraits.some((trait) => externalBrowserTraits.has(trait))) !== null && _a !== void 0 ? _a : false;
}
/**
 * Determines minimum os version
 *
 * @param {mediaDataStructure.Data} data      The product data to use.
 * @param {boolean} isClientHalva             Whether the client is halva.
 * @returns {string}                          The minimum OS version.
 */
export function minimumOSVersionFromData(objectGraph, data, isClientHalva) {
    const supportsMacOSCompatibleIOSBinary = supportsMacOSCompatibleIOSBinaryFromData(objectGraph, data, isClientHalva);
    const supportsVisionOSCompatibleIOSBinary = supportsVisionOSCompatibleIOSBinaryFromData(objectGraph, data);
    if (supportsMacOSCompatibleIOSBinary) {
        const minimumOSVersion = contentAttributes.contentAttributeAsString(objectGraph, data, "minimumMacOSVersion");
        if (serverData.isDefinedNonNullNonEmpty(minimumOSVersion)) {
            return minimumOSVersion;
        }
    }
    else if (supportsVisionOSCompatibleIOSBinary) {
        const minimumOSVersion = contentAttributes.contentAttributeAsString(objectGraph, data, "minimumXROSVersion");
        if (serverData.isDefinedNonNullNonEmpty(minimumOSVersion)) {
            return minimumOSVersion;
        }
    }
    const attributePlatform = contentAttributes.bestAttributePlatformFromData(objectGraph, data);
    return mediaPlatformAttributes.platformAttributeAsString(data, attributePlatform, "minimumOSVersion");
}
/**
 * Determines required capabilities for device.
 *
 * @param {mediaDataStructure.Data} data      The product data to use.
 * @param {boolean} isClientHalva             Whether the client is halva.
 * @returns {string}                          The device capabilities to use.
 */
export function requiredCapabilitiesFromData(objectGraph, data, isClientHalva) {
    const supportsMacOSCompatibleIOSBinary = supportsMacOSCompatibleIOSBinaryFromData(objectGraph, data, isClientHalva);
    const supportsVisionOSCompatibleIOSBinary = supportsVisionOSCompatibleIOSBinaryFromData(objectGraph, data);
    if (supportsMacOSCompatibleIOSBinary) {
        return contentAttributes.contentAttributeAsString(objectGraph, data, "macRequiredCapabilities");
    }
    else if (supportsVisionOSCompatibleIOSBinary) {
        return contentAttributes.contentAttributeAsString(objectGraph, data, "requiredCapabilitiesForRealityDevice");
    }
    else {
        return contentAttributes.contentAttributeAsString(objectGraph, data, "requiredCapabilities");
    }
}
/**
 * Returns the app platforms you can buy for on the given device.
 *
 * @param objectGraph The current object graph
 * @param data The data for the app in question
 * @param device The device type to check
 * @param supportsMacOSCompatibleIOSBinary Whether device and app supports macOS compatible iOS binary
 * @param supportsVisionOSCompatibleIOSBinary Whether device and app supports visionOS compatible iOS binary
 * @returns An array of supported app platforms
 */
function buyableAppPlatformsForDevice(objectGraph, data, device, supportsMacOSCompatibleIOSBinary, supportsVisionOSCompatibleIOSBinary) {
    let systemApps;
    switch (device) {
        case "phone":
            systemApps = sad.systemApps(objectGraph);
            if (isSome(data) && systemApps.isSystemAppFromData(data)) {
                return ["phone", "watch", "messages"];
            }
            else {
                return ["phone", "watch", "messages", "tv", "vision"];
            }
        case "pad":
            systemApps = sad.systemApps(objectGraph);
            if (isSome(data) && systemApps.isSystemAppFromData(data)) {
                return ["phone", "pad", "messages"];
            }
            else {
                return ["phone", "pad", "messages", "tv", "vision"];
            }
        case "tv":
            return ["tv"];
        case "watch":
            return ["watch"];
        case "mac":
            if (supportsMacOSCompatibleIOSBinary) {
                return ["mac", "phone", "pad"];
            }
            else {
                return ["mac"];
            }
        case "vision":
            if (supportsVisionOSCompatibleIOSBinary) {
                return ["vision", "phone", "pad"];
            }
            else {
                return ["vision"];
            }
        default:
            return [];
    }
}
/**
 * Returns the app platforms you can preorder on for the given device.
 *
 * @param {DeviceType} device    The device type to check
 * @param {boolean} supportsMacOSCompatibleIOSBinary Whether device and app are support macOS compatible iOS binary
 * @param {boolean} supportsVisionOSCompatibleIOSBinary    Whether device and app supports visionOS compatible iOS binary
 * @returns {models.AppPlatform[]}      An array of supported app platforms
 */
function preorderableAppPlatformsForDevice(objectGraph, device, supportsMacOSCompatibleIOSBinary, supportsVisionOSCompatibleIOSBinary) {
    switch (device) {
        case "phone":
            return ["phone", "watch", "messages"];
        case "pad":
            return ["phone", "pad", "messages"];
        case "tv":
            return ["tv"];
        case "watch":
            return ["watch"];
        case "mac":
            if (supportsMacOSCompatibleIOSBinary) {
                return ["mac", "phone", "pad"];
            }
            else {
                return ["mac"];
            }
        case "vision":
            if (supportsVisionOSCompatibleIOSBinary) {
                return ["vision", "phone", "pad"];
            }
            else {
                return ["vision"];
            }
        default:
            return [];
    }
}
/**
 * Returns the app platforms you can run on the given device.
 *
 * @param {DeviceType} device    The device type to check
 * @param {boolean} supportsMacOSCompatibleIOSBinary    Whether device and app are support macOS compatible iOS binary
 * @param {boolean} supportsVisionOSCompatibleIOSBinary    Whether device and app supports visionOS compatible iOS binary
 * @returns {models.AppPlatform[]}      An array of supported app platforms
 */
export function runnableAppPlatformsForDevice(objectGraph, device, supportsMacOSCompatibleIOSBinary, supportsVisionOSCompatibleIOSBinary) {
    switch (device) {
        case "phone":
            return ["phone", "messages"];
        case "pad":
            return ["phone", "pad", "messages"];
        case "tv":
            return ["tv"];
        case "watch":
            return ["watch"];
        case "mac":
            if (supportsMacOSCompatibleIOSBinary) {
                return ["mac", "phone", "pad"];
            }
            else {
                return ["mac"];
            }
        case "vision":
            if (supportsVisionOSCompatibleIOSBinary) {
                return ["vision", "phone", "pad"];
            }
            else {
                return ["vision"];
            }
        default:
            return [];
    }
}
/**
 * Determines if a given piece of content supports the provided app platform
 *
 * @param {models.AppPlatform[]} appPlatforms The app platforms supported by the content
 * @param {AppPlatform} platform The platform to check
 * @returns {boolean} True if the platform is supported, false if not
 */
export function supportsPlatform(appPlatforms, platform) {
    return appPlatforms.indexOf(platform) !== -1;
}
/**
 * Determines if a given piece of content is buyable on the provided device.
 *
 * @param {models.AppPlatform[]} appPlatforms The app platforms supported by the content
 * @param {DeviceType} device The device type to check
 * @param {boolean} supportsMacOSCompatibleIOSBinary Whether app and device support macOS compatible iOS binary.
 * @param {boolean} supportsVisionOSCompatibleIOSBinary    Whether device and app supports visionOS compatible iOS binary
 * @param {boolean} isMacOSAppBuyableOnDevice Whether a macOS app is buyable on this device (this enables additional criteria for Apple Silicon).
 * @returns {boolean} True if any of the app platforms are buyable on the given device, false if not
 */
export function buyableOnDevice(objectGraph, data, appPlatforms, device, supportsMacOSCompatibleIOSBinary, supportsVisionOSCompatibleIOSBinary, isMacOSAppBuyableOnDevice = true) {
    const platforms = buyableAppPlatformsForDevice(objectGraph, data, device, supportsMacOSCompatibleIOSBinary, supportsVisionOSCompatibleIOSBinary);
    // Do any of the platforms supported by the device match any of the content's app platforms?
    if (!platforms.some((platform) => supportsPlatform(appPlatforms, platform))) {
        return false;
    }
    if (objectGraph.client.isMac && platforms.includes("mac")) {
        return isMacOSAppBuyableOnDevice;
    }
    return true;
}
/**
 * Determines macOS runnability info for apps and bundles on macOS.
 */
function macOSRunnabilityInfoFromData(objectGraph, data) {
    var _a;
    const runnabilityInfo = new RunnabilityInfo();
    // Return most permissible runnability for non-macOS platforms.
    if (objectGraph.client.deviceType !== "mac") {
        return runnabilityInfo;
    }
    // Use media API attributes for non-bundles.
    if (data.type !== "app-bundles") {
        runnabilityInfo.runsOnIntel =
            (_a = contentAttributes.contentAttributeAsBoolean(objectGraph, data, "runsOnIntel", contentAttributes.defaultAttributePlatform(objectGraph))) !== null && _a !== void 0 ? _a : true;
        runnabilityInfo.runsOnAppleSilicon = contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "runsOnAppleSilicon", contentAttributes.defaultAttributePlatform(objectGraph));
        runnabilityInfo.requiresRosetta = contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "requiresRosetta", contentAttributes.defaultAttributePlatform(objectGraph));
        return runnabilityInfo;
    }
    const bundleAppsData = mediaRelationship.relationshipCollection(data, "apps");
    // Return most permissible runnability when there no children available
    if (bundleAppsData.length === 0) {
        return runnabilityInfo;
    }
    // Synthesize runnability info from bundle apps
    for (const appData of bundleAppsData) {
        if (serverData.isNull(appData.attributes)) {
            continue;
        }
        const appRunnabilityInfo = macOSRunnabilityInfoFromData(objectGraph, appData);
        runnabilityInfo.runsOnIntel = runnabilityInfo.runsOnIntel && appRunnabilityInfo.runsOnIntel;
        runnabilityInfo.runsOnAppleSilicon =
            runnabilityInfo.runsOnAppleSilicon && appRunnabilityInfo.runsOnAppleSilicon;
        runnabilityInfo.requiresRosetta = runnabilityInfo.requiresRosetta || appRunnabilityInfo.requiresRosetta;
    }
    return runnabilityInfo;
}
/**
 * Determines if a given macOS app is buyable on this device.
 *
 */
export function isMacOSAppBuyableAndRunnableFromData(objectGraph, data, isAppleSiliconSupportEnabled, isRosettaAvailable) {
    const runnabilityInfo = macOSRunnabilityInfoFromData(objectGraph, data);
    if (isAppleSiliconSupportEnabled) {
        return (runnabilityInfo.runsOnAppleSilicon &&
            (!runnabilityInfo.requiresRosetta || (runnabilityInfo.requiresRosetta && isRosettaAvailable)));
    }
    else {
        return runnabilityInfo.runsOnIntel;
    }
}
/**
 * Determines if a given piece of content is preorderable on the provided device.
 *
 * @param {models.AppPlatform[]} appPlatforms The app platforms supported by the content
 * @param {DeviceType} device The device type to check
 * @param {boolean} supportsMacOSCompatibleIOSBinary Whether app and device support macOS compatible iOS binary.
 * @param {boolean} supportsVisionOSCompatibleIOSBinary Whether app and device support visionOS compatible iOS binary.
 * @returns {boolean} True if any of the app platforms are buyable on the given device, false if not
 */
export function preorderableOnDevice(objectGraph, appPlatforms, device, supportsMacOSCompatibleIOSBinary, supportsVisionOSCompatibleIOSBinary) {
    const platforms = preorderableAppPlatformsForDevice(objectGraph, device, supportsMacOSCompatibleIOSBinary, supportsVisionOSCompatibleIOSBinary);
    // Do any of the platforms supported by the device match any of the content's app platforms?
    return platforms.some((platform) => supportsPlatform(appPlatforms, platform));
}
/**
 * Determines if any of a given array of app platforms can be run on the provided device.
 *
 * @param appPlatforms The app platforms supported by a piece of content.
 * @param device The device type to check.
 * @param {boolean} supportsMacOSCompatibleIOSBinary Whether app and device support macOS compatible iOS binary.
 * @param {boolean} supportsVisionOSCompatibleIOSBinary Whether app and device support visionOS compatible iOS binary.
 * @returns `true` if any of the app platforms can be run on the given device; `false` otherwise.
 */
export function runnableOnDevice(objectGraph, appPlatforms, device, supportsMacOSCompatibleIOSBinary, supportsVisionOSCompatibleIOSBinary, isMacOSAppRunnableOnDevice = true) {
    const runnablePlatforms = runnableAppPlatformsForDevice(objectGraph, device, supportsMacOSCompatibleIOSBinary, supportsVisionOSCompatibleIOSBinary);
    // Do any of the platforms supported by the device match any of the content's app platforms?
    if (!runnablePlatforms.some((platform) => supportsPlatform(appPlatforms, platform))) {
        return false;
    }
    if (objectGraph.client.isMac && appPlatforms.includes("mac")) {
        return isMacOSAppRunnableOnDevice;
    }
    return true;
}
/**
 * Determines if a given piece of content is runnable on the provided device.
 *
 * @param data  The product data to use.
 * @param device
 * @param {boolean} doesClientSupportMacOSCompatibleIOSBinary Whether the client supports macOS compatible iOS binaries
 * @returns {boolean} True if the product can be run on the provided device, false if not
 */
export function runnableOnDeviceWithData(objectGraph, data, device, doesClientSupportMacOSCompatibleIOSBinary) {
    // (1) Required capabilities mismatch
    if (!lockups.deviceHasCapabilitiesFromData(objectGraph, data)) {
        return false;
    }
    // (2) 32-bit only, unsupported deletable system app, doesn't meet minimum OS requirements, or doesn't support current platform
    // Note that Filter.UnsupportedPlatform only checks if the product is buyable, not runnable
    const filter = 2 /* filtering.Filter.ThirtyTwoBit */ |
        4 /* filtering.Filter.UnsupportedSystemDeletableApps */ |
        512 /* filtering.Filter.MinimumOSRequirement */ |
        128 /* filtering.Filter.UnsupportedPlatform */ |
        8192 /* filtering.Filter.MacOSRosetta */;
    if (filtering.shouldFilter(objectGraph, data, filter)) {
        return false;
    }
    // (3) Finally, check if any of the product platforms are supported on this device
    const supportsMacOSCompatibleIOSBinary = supportsMacOSCompatibleIOSBinaryFromData(objectGraph, data, doesClientSupportMacOSCompatibleIOSBinary);
    const supportsVisionOSCompatibleIOSBinary = supportsVisionOSCompatibleIOSBinaryFromData(objectGraph, data);
    const runnableAppPlatforms = runnableAppPlatformsForDevice(objectGraph, device, supportsMacOSCompatibleIOSBinary, supportsVisionOSCompatibleIOSBinary);
    const productAppPlatforms = supportedAppPlatformsFromData(objectGraph, data);
    return runnableAppPlatforms.some((platform) => supportsPlatform(productAppPlatforms, platform));
}
/**
 * Determines which screenshot keys (MediaType) we need to use to pull the appropriate screenshots
 * from the server data.
 *
 * @param appPlatform The app platform requested
 * @param screenSize The size of the screen being used to display the screenshots
 * @returns An array of ScreenshotType strings that can be used on the server data
 * */
export function mediaTypesForAppPlatform(objectGraph, appPlatform, screenSize) {
    switch (appPlatform) {
        case "mac": {
            return ["mac"];
        }
        case "watch": {
            if (screenSize.isEqualTo(screenSizeWatchUltra) || screenSize.isEqualTo(screenSizeN230)) {
                // 2022 is the preferred dropwell for Ultra devices
                return ["appleWatch_2022", "appleWatch_2024", "appleWatch_2021", "appleWatch_2018", "appleWatch"];
            }
            else {
                return ["appleWatch_2024", "appleWatch_2022", "appleWatch_2021", "appleWatch_2018", "appleWatch"];
            }
        }
        case "tv": {
            return ["appleTV"];
        }
        case "vision": {
            return ["appleVisionPro"];
        }
        case "pad": {
            const types = [];
            if ((screenSize.isEqualTo(screenSizeIPadPro2018) ||
                screenSize.isEqualTo(screenSizeIPadPro2018Landscape) ||
                screenSize.isEqualTo(screenSizeJ720) ||
                screenSize.isEqualTo(screenSizeJ720Landscape)) &&
                objectGraph.client.screenCornerRadius > 0.0) {
                types.push("ipadPro_2018");
                types.push("ipad_11");
                types.push("ipadPro");
                types.push("ipad_10_5");
                types.push("ipad");
            }
            else if (screenSize.isEqualTo(screenSizeIPadPro)) {
                types.push("ipadPro");
                types.push("ipadPro_2018");
                types.push("ipad_11");
                types.push("ipad_10_5");
                types.push("ipad");
            }
            else if (screenSize.isEqualTo(screenSizeIPad11) ||
                screenSize.isEqualTo(screenSizeIPad11Landscape) ||
                screenSize.isEqualTo(screenSizeIPadJ310) ||
                screenSize.isEqualTo(screenSizeIPadJ310Landscape) ||
                screenSize.isEqualTo(screenSizeJ717) ||
                screenSize.isEqualTo(screenSizeJ717Landscape)) {
                types.push("ipad_11");
                types.push("ipadPro_2018");
                types.push("ipadPro");
                types.push("ipad_10_5");
                types.push("ipad");
            }
            else if (screenSize.isEqualTo(screenSizeIPad105)) {
                types.push("ipad_10_5");
                types.push("ipad");
                types.push("ipad_11");
                types.push("ipadPro");
                types.push("ipadPro_2018");
            }
            else if (screenSize.isEqualTo(screenSizeIPadAir2020)) {
                types.push("ipad_11");
                types.push("ipadPro");
                types.push("ipadPro_2018");
                types.push("ipad_10_5");
                types.push("ipad");
            }
            else if (screenSize.isEqualTo(screenSizeIPad102)) {
                types.push("ipad");
                types.push("ipad_10_5");
                types.push("ipad_11");
                types.push("ipadPro");
                types.push("ipadPro_2018");
            }
            else {
                // Regardless of screen size match, we should add on 'some' iPad.
                types.push("ipadPro_2018");
                types.push("ipad_11");
                types.push("ipad");
                types.push("ipad_10_5");
                types.push("ipadPro");
            }
            return types;
        }
        case "phone": {
            /**                 Phone Best Match Policy                     **

             The best match is given by |B| + |L| + |S|, where:
             B: Exact type match
             L: All types larger than the exact type, in increasing order
             S: All types smaller than the exact type, in decreasing order

             Example:
             Types for iphone6 == [iphone6, iphone6+, iphone_5_8, iphone5, iphone]
             Types for iphone5 == [iphone5, iphone6, iphone6+, iphone_5_8, iphone]

             **                                                             */
            // Grab the exact match.
            let perfectMatch;
            if (screenSize.isEqualTo(screenSizeIphone65) || screenSize.isEqualTo(screenSizeIPhone134)) {
                perfectMatch = "iphone_6_5";
            }
            else if (screenSize.isEqualTo(screenSizeIPhone58) ||
                screenSize.isEqualTo(screenSizeIPhone131) ||
                screenSize.isEqualTo(screenSizeIPhone132)) {
                perfectMatch = "iphone_5_8";
            }
            else if (screenSize.isEqualTo(screenSizeIPhoneOriginal)) {
                perfectMatch = "iphone";
            }
            else if (screenSize.isEqualTo(screenSizeIPhone5)) {
                perfectMatch = "iphone5";
            }
            else if (screenSize.isEqualTo(screenSizeIPhone6)) {
                perfectMatch = "iphone6";
            }
            else if (screenSize.isEqualTo(screenSizeIPhone6Plus)) {
                perfectMatch = "iphone6+";
            }
            else if (screenSize.isEqualTo(screenSizeIPhone61) || screenSize.isEqualTo(screenSizeD93)) {
                perfectMatch = "iphone_d73";
            }
            else if (screenSize.isEqualTo(screenSizeIPhone67) ||
                screenSize.isEqualTo(screenSizeD94) ||
                screenSize.isEqualTo(screenSizeD23)) {
                perfectMatch = "iphone_d74";
            }
            else {
                perfectMatch = "iphone_5_8";
            }
            // Append remaining types to our exact match.
            const perfectMatchIndex = decreasingPhoneTypes.indexOf(perfectMatch);
            const largerTypes = decreasingPhoneTypes.slice(0, perfectMatchIndex);
            largerTypes.reverse();
            const smallerTypes = decreasingPhoneTypes.slice(perfectMatchIndex + 1);
            const perfectMatchArray = [perfectMatch];
            return perfectMatchArray.concat(largerTypes, smallerTypes);
        }
        default: {
            return [];
        }
    }
}
export function combinedFileSizeFromData(objectGraph, data) {
    var _a;
    if (serverData.isNull(data)) {
        return null;
    }
    // This background asset information is for the work done in SydneyB
    const backgroundAssetsInfo = contentAttributes.contentAttributeAsDictionary(objectGraph, data, "backgroundAssetsInfo");
    // This background asset information is for the work done in SydneyE
    const backgroundAssetsInfoWithOptional = contentAttributes.contentAttributeAsDictionary(objectGraph, data, "backgroundAssetsInfoWithOptional");
    const isIOSBinaryCompatibleWithMac = supportsMacOSCompatibleIOSBinaryFromData(objectGraph, data, true);
    const isMacOnly = contentDeviceFamily.dataOnlyHasDeviceFamily(objectGraph, data, "mac");
    const isWebViewingMac = objectGraph.client.isWeb && ((_a = objectGraph.activeIntent) === null || _a === void 0 ? void 0 : _a.platform) === "mac";
    if ((objectGraph.client.isMac || isWebViewingMac || isMacOnly) && !isIOSBinaryCompatibleWithMac) {
        const macFileSize = objectGraph.bag.enableProductPageInstallSize
            ? macInstallSizeInBytesFromData(objectGraph, data)
            : offers.macFileSizeInBytesFromData(objectGraph, data);
        if (serverData.isDefinedNonNullNonEmpty(backgroundAssetsInfoWithOptional)) {
            const maxEssentialInstallSizeInBytes = serverData.asNumber(backgroundAssetsInfoWithOptional, "maxEssentialInstallSizeInBytes");
            return new modelsBase.CombinedFileSize(macFileSize, null, null, maxEssentialInstallSizeInBytes);
        }
        else if (serverData.isDefinedNonNullNonEmpty(backgroundAssetsInfo)) {
            const maxDownloadSizeInBytes = serverData.asNumber(backgroundAssetsInfo, "maxDownloadSizeInBytes");
            const maxInstallSizeInBytes = serverData.asNumber(backgroundAssetsInfo, "maxInstallSizeInBytes");
            return new modelsBase.CombinedFileSize(macFileSize, maxDownloadSizeInBytes, maxInstallSizeInBytes, null);
        }
        return new modelsBase.CombinedFileSize(macFileSize, null, null, null);
    }
    else {
        /* File Size: Our policy is to rely on thinned variant, device model, and universal (in that order). */
        const fileSizeByDevice = mediaAttributes.attributeAsDictionary(data, "fileSizeByDevice");
        if (fileSizeByDevice) {
            /* thinnedApplicationVariantIdentifier can contain two device names. The preferred device, and a compatible device. */
            let fileSizeKeys = [];
            if (objectGraph.client.thinnedApplicationVariantIdentifier) {
                fileSizeKeys = objectGraph.client.thinnedApplicationVariantIdentifier.split(" ");
            }
            fileSizeKeys = fileSizeKeys.concat([objectGraph.host.deviceModel, "universal"]);
            for (const key of fileSizeKeys) {
                const fileSizeValue = serverData.asNumber(fileSizeByDevice[key]);
                if (fileSizeValue) {
                    if (serverData.isDefinedNonNullNonEmpty(backgroundAssetsInfoWithOptional)) {
                        const maxEssentialInstallSizeInBytes = serverData.asNumber(backgroundAssetsInfoWithOptional, "maxEssentialInstallSizeInBytes");
                        return new modelsBase.CombinedFileSize(fileSizeValue, null, null, maxEssentialInstallSizeInBytes);
                    }
                    else if (serverData.isDefinedNonNullNonEmpty(backgroundAssetsInfo)) {
                        const maxDownloadSizeInBytes = serverData.asNumber(backgroundAssetsInfo, "maxDownloadSizeInBytes");
                        const maxInstallSizeInBytes = serverData.asNumber(backgroundAssetsInfo, "maxInstallSizeInBytes");
                        return new modelsBase.CombinedFileSize(fileSizeValue, maxDownloadSizeInBytes, maxInstallSizeInBytes, null);
                    }
                    else {
                        return new modelsBase.CombinedFileSize(fileSizeValue, null, null, null);
                    }
                }
            }
        }
    }
    return null;
}
/**
 * Extract the file size and unit from a CombinedFileSize object.
 * @param objectGraph Current object graph
 * @param combinedFileSize The combined file size object
 * @returns A FileSizeAndUnit object
 */
export function fileSizeAndUnitFromCombinedFileSize(objectGraph, combinedFileSize) {
    let totalFileSize;
    if (isSome(combinedFileSize.maxEssentialInstallSizeInBytes)) {
        totalFileSize = combinedFileSize.fileSizeByDevice + combinedFileSize.maxEssentialInstallSizeInBytes;
    }
    else if (isSome(combinedFileSize.maxInstallSizeInBytes)) {
        totalFileSize = combinedFileSize.fileSizeByDevice + combinedFileSize.maxInstallSizeInBytes;
    }
    else {
        totalFileSize = combinedFileSize.fileSizeByDevice;
    }
    if (totalFileSize <= 0) {
        return null;
    }
    // We split using all whitespace characters because in some locs a non-breaking space is used.
    const parts = objectGraph.loc.fileSize(totalFileSize).trim().split(/\s+/);
    if (parts.length !== 2) {
        return null;
    }
    return {
        size: parts[0],
        unit: parts[1],
    };
}
/**
 * Extracts the install size for a macOS app.
 * @param objectGraph Current object graph
 * @param data Product page data
 * @returns The install size for the Mac binary, in bytes
 */
function macInstallSizeInBytesFromData(objectGraph, data) {
    const deviceData = mediaPlatformAttributes.platformAttributeAsDictionary(data, "osx", "installSizeByDeviceInBytes");
    if (isNothing(deviceData)) {
        return null;
    }
    // macOS does not support app thinning, so there is only ever one macOS device in this list. Unfortunately
    // there is no known API that gives us this device name, so we resort to hard-coding for now.
    const installSizeInBytes = deviceData["Mac"];
    if (isNothing(installSizeInBytes)) {
        return null;
    }
    return serverData.asNumber(installSizeInBytes);
}
/**
 * Determines the primary langauge locale, from a given list of locales.
 * @param objectGraph Current object graph
 * @param locales The list of locales
 * @returns A single LanguageLocale object, or null
 */
export function primaryLanguageLocaleFromLocales(objectGraph, locales) {
    const languageCount = locales.length;
    if (languageCount <= 0) {
        return null;
    }
    return {
        tag: serverData.asString(serverData.traverse(locales, "0.tag")).split("-")[0].toUpperCase(),
        name: serverData.asString(serverData.traverse(locales, "0.name")),
    };
}
/**
 * Determines the uber artwork for the product, if there is any.
 * @param {Data} The data for the product.
 * @returns {models.Artwork} The artwork for the uber, or `null` if there is none.
 * null.
 */
export function productUberFromData(objectGraph, data, options) {
    let uberArtworkData;
    let uberArtworkPath = null;
    let fallbackUberArtworkPath = null;
    let cropCode = null;
    let fallbackCropCode = null;
    switch (objectGraph.client.deviceType) {
        case "mac":
            if (options.supportsArcade) {
                uberArtworkPath = "editorialArtwork.splashFullScreen";
                cropCode = "sr";
            }
            else {
                uberArtworkPath = "editorialArtwork.centeredFullscreenBackground";
                cropCode = "ep";
            }
            break;
        case "tv":
            if (options.presentedInTopShelf) {
                uberArtworkPath = "editorialArtwork.topShelf";
                cropCode = "sr";
            }
            else {
                uberArtworkPath = "editorialArtwork.splashFullScreen";
                cropCode = "ta";
                fallbackUberArtworkPath = "editorialArtwork.fullscreenBackground";
                fallbackCropCode = "sr";
            }
            break;
        case "vision":
            uberArtworkPath = "editorialArtwork.productUberStatic16x9";
            cropCode = "sr";
            break;
        default:
            if (options.supportsArcade) {
                if (options.prefersCompactVariant || objectGraph.client.isPhone) {
                    uberArtworkPath = "editorialArtwork.splashTall";
                    cropCode = "oc";
                }
                else {
                    uberArtworkPath = "editorialArtwork.splashFullScreen";
                    cropCode = "oh";
                }
            }
            else {
                uberArtworkPath = "editorialArtwork.bannerUber";
                cropCode = "sr";
            }
            break;
    }
    uberArtworkData = contentAttributes.contentAttributeAsDictionary(objectGraph, data, uberArtworkPath);
    // If we don't have the desired artwork, we sometimes attempt to use other artwork as a fallback.
    if (fallbackUberArtworkPath !== null && serverData.isNullOrEmpty(uberArtworkData)) {
        uberArtworkData = contentAttributes.contentAttributeAsDictionary(objectGraph, data, fallbackUberArtworkPath);
        // Use the fallback crop if it's available.
        if (fallbackCropCode !== null) {
            cropCode = fallbackCropCode;
        }
    }
    if (serverData.isDefinedNonNull(uberArtworkData) && serverData.isDefinedNonNull(cropCode)) {
        return artworkFromApiArtwork(objectGraph, uberArtworkData, {
            cropCode,
            useCase: 21 /* ArtworkUseCase.Uber */,
            withJoeColorPlaceholder: true,
            overrideHeight: null,
            overrideWidth: null,
        });
    }
    return null;
}
/**
 * Determines the logo artwork for the product, if there is any.
 * @param {Data} The data for the product.
 * @returns {models.Artwork} The artwork for the uber, or `null` if there is none.
 * null.
 */
export function productLogoArtworkFromData(objectGraph, data) {
    let artworkPath = null;
    let cropCode = null;
    switch (objectGraph.client.deviceType) {
        case "tv":
            artworkPath = "editorialArtwork.contentLogoTrimmed";
            cropCode = "bb";
            break;
        default:
            return null;
    }
    const artworkData = contentAttributes.contentAttributeAsDictionary(objectGraph, data, artworkPath);
    if (serverData.isDefinedNonNull(artworkData) && serverData.isDefinedNonNull(cropCode)) {
        return artworkFromApiArtwork(objectGraph, artworkData, {
            cropCode,
            useCase: 0 /* ArtworkUseCase.Default */,
            withJoeColorPlaceholder: true,
        });
    }
    return null;
}
/**
 * Determines the editorial video for the product, if there is any.
 * @returns {models.Video} The editorial video for the product, or `null` if there is none.
 * null.
 * @param data
 * @param useCase
 * @param preferredFlavorsOverride
 */
export function productEditorialVideoFromData(objectGraph, data, useCase, preferredFlavorsOverride, videoPreviewOverride) {
    let preferredFlavors = [];
    if (serverData.isDefinedNonNullNonEmpty(preferredFlavorsOverride)) {
        preferredFlavors = preferredFlavorsOverride;
    }
    else {
        switch (objectGraph.client.deviceType) {
            case "mac":
            case "tv":
                preferredFlavors = ["splashVideo16x9"];
                break;
            case "pad":
                preferredFlavors = ["splashVideo4x3"];
                break;
            case "vision":
                preferredFlavors = ["productUberMotion16x9"];
                break;
            default:
                preferredFlavors = ["splashVideo3x4"];
        }
    }
    let uberEditorialVideoData = null;
    let videoPreviewData = null;
    for (const videoFlavor of preferredFlavors) {
        uberEditorialVideoData = contentAttributes.contentAttributeAsDictionary(objectGraph, data, [
            "editorialVideo",
            videoFlavor,
        ]);
        videoPreviewData = contentAttributes.contentAttributeAsDictionary(objectGraph, data, [
            "editorialVideo",
            videoFlavor,
            "previewFrame",
        ]);
        if (serverData.isDefinedNonNullNonEmpty(uberEditorialVideoData)) {
            break;
        }
    }
    // Video Preview based on data, or externally provided override if any.
    const videoPreview = videoPreviewOverride !== null && videoPreviewOverride !== void 0 ? videoPreviewOverride : artworkFromApiArtwork(objectGraph, videoPreviewData, {
        useCase: useCase,
        withJoeColorPlaceholder: true,
        cropCode: "sr",
    });
    if (serverData.isDefinedNonNull(uberEditorialVideoData)) {
        const videoUrl = serverData.asString(uberEditorialVideoData, "video");
        if (serverData.isNull(videoUrl)) {
            return null;
        }
        let playbackControls;
        let autoplayPlaybackControls;
        if (objectGraph.host.isiOS || objectGraph.host.isMac || objectGraph.host.isTV) {
            playbackControls = videoDefaults.standardControls(objectGraph);
            autoplayPlaybackControls = {
                muteUnmute: true,
            };
        }
        else {
            playbackControls = {};
            autoplayPlaybackControls = {};
        }
        const configuration = {
            allowsAutoPlay: true,
            looping: true,
            canPlayFullScreen: false,
            playbackControls: playbackControls,
            autoPlayPlaybackControls: autoplayPlaybackControls,
        };
        return new models.Video(videoUrl, videoPreview, configuration);
    }
    return null;
}
/**
 * Determines the video for the poster lockup, if there is any.
 * @param {Data} The data for the lockup.
 * @param {useCase} The use case for this artwork.
 * @returns {models.Video} The video for the poster lockup, or `null` if there is none.
 * null.
 */
export function posterEditorialVideoFromData(objectGraph, data, useCase) {
    const editorialVideoData = contentAttributes.contentAttributeAsDictionary(objectGraph, data, [
        "editorialVideo",
        "posterCardVideo16x9",
    ]);
    const videoPreviewData = contentAttributes.contentAttributeAsDictionary(objectGraph, data, [
        "editorialVideo",
        "posterCardVideo16x9",
        "previewFrame",
    ]);
    const cropCode = "sr";
    const videoPreview = artworkFromApiArtwork(objectGraph, videoPreviewData, {
        useCase: useCase,
        withJoeColorPlaceholder: true,
        cropCode: cropCode,
    });
    if (serverData.isDefinedNonNull(editorialVideoData)) {
        const videoUrl = serverData.asString(editorialVideoData, "video");
        if (serverData.isNull(videoUrl)) {
            return null;
        }
        const configuration = {
            allowsAutoPlay: true,
            looping: true,
            canPlayFullScreen: false,
            playbackControls: videoDefaults.noControls(objectGraph),
            autoPlayPlaybackControls: videoDefaults.noControls(objectGraph),
        };
        return new models.Video(videoUrl, videoPreview, configuration);
    }
    return null;
}
/**
 * Determines the artwork for the poster lockup, if there is any.
 * @param {Data} The data for the lockup.
 * @returns {models.Artwork} The artwork for the poster lockup, or `null` if there is none.
 * null.
 */
export function posterArtworkFromData(objectGraph, data) {
    const artworkData = contentAttributes.contentAttributeAsDictionary(objectGraph, data, "editorialArtwork.postCard");
    const cropCode = "sr";
    if (serverData.isDefinedNonNull(artworkData)) {
        return artworkFromApiArtwork(objectGraph, artworkData, {
            cropCode,
            useCase: 0 /* ArtworkUseCase.Default */,
            withJoeColorPlaceholder: true,
        });
    }
    return null;
}
/**
 * Determines the artwork for the epic heading on a poster lockup, if there is any.
 * @param {Data} The data for the product.
 * @returns {models.Artwork} The artwork for the epic heading, or `null` if there is none.
 * null.
 */
export function posterEpicHeadingArtworkFromData(objectGraph, data) {
    const artworkData = contentAttributes.contentAttributeAsDictionary(objectGraph, data, "editorialArtwork.epicHeading");
    const cropCode = "bb";
    if (serverData.isDefinedNonNull(artworkData) && serverData.isDefinedNonNull(cropCode)) {
        const epicHeadingArtwork = artworkFromApiArtwork(objectGraph, artworkData, {
            cropCode,
            useCase: 0 /* ArtworkUseCase.Default */,
        });
        if (objectGraph.client.isVision) {
            epicHeadingArtwork.backgroundColor = color.named("clear");
        }
        return epicHeadingArtwork;
    }
    return null;
}
/**
 * Fetch the most-landscape media from data. Hoisted from Arcade See All.
 * Used for:
 * - Arcade See All Media Lockups
 * - Continue Playing Lockups
 */
export function editorialSplashVideoFromData(objectGraph, data, videoPreviewOverride) {
    let preferredEditorialVideoFlavors = null;
    switch (objectGraph.client.deviceType) {
        case "mac":
        case "tv":
        case "phone":
        case "vision":
            preferredEditorialVideoFlavors = ["splashVideo16x9", "splashVideo4x3", "splashVideo3x4"];
            break;
        default:
            preferredEditorialVideoFlavors = ["splashVideo4x3", "splashVideo16x9", "splashVideo3x4"];
    }
    return productEditorialVideoFromData(objectGraph, data, 21 /* ArtworkUseCase.Uber */, preferredEditorialVideoFlavors, videoPreviewOverride);
}
/**
 * Determines the URL to use for the developer page.
 * @param {Data} developerData The data for the "developer" relationship.
 * @returns {string} The string form of the URL for the developer page, or `null` if the developer data is undefined or
 * null.
 */
export function developerUrlFromDeveloperData(objectGraph, developerData) {
    if (!serverData.isDefinedNonNull(developerData)) {
        return null;
    }
    if (objectGraph.client.isWeb) {
        return mediaAttributes.attributeAsString(developerData, "url");
    }
    return `${Protocol.internal}:/${Path.developer}/${Path.href}?${Parameters.href}=${developerData.href}`;
}
/**
 * Determines the URL to use for the Charts page.
 * @param {Data} data The data for the product.
 * @returns {string} The string form of the URL for the charts page, or `null` if the data is undefined or
 * null.
 */
export function chartUrlFromData(objectGraph, genre, chart) {
    const request = new mediaDataFetching.Request(objectGraph)
        .forType("charts")
        .addingQuery("types", "apps")
        .addingQuery("chart", chart)
        .addingQuery("genre", genre)
        .includingMacOSCompatibleIOSAppsWhenSupported(true);
    return mediaUrlBuilder.buildURLFromRequest(objectGraph, request).toString();
}
/**
 * Returns the key into the chart-position badge data for the given client name.
 * @param clientIdentifier Identifier of the current client.
 * @returns {string} The relevant key in the chart-position badge JSON data.
 */
export function badgeChartKeyForClientIdentifier(objectGraph, clientIdentifier) {
    switch (clientIdentifier) {
        case client.appStoreIdentifier:
        case client.productPageExtensionIdentifier:
            return "appStore";
        case client.watchIdentifier:
            return "watch";
        case client.messagesIdentifier:
            return "messages";
        case client.tvIdentifier:
            return "appletv";
        default:
            return null;
    }
}
/**
 * Internal function returning the name and asset name representing the
 * storefront content rating for the provided rank.
 * @param objectGraph The App Store object graph.
 * @param rank A content rating rank from CX.
 * @returns A tuple containing the name and asset name representing the rank,
 * or `undefined` if rank is unknown/invalid.
 */
function storefrontContentRatingInfoForRank(objectGraph, rank) {
    switch (rank) {
        // Brazil Self-Rated
        case 6:
            return ["L", "br.l"];
        case 7:
            return ["10", "br.10"];
        case 8:
            return ["12", "br.12"];
        case 9:
            return ["14", "br.14"];
        case 10:
            return ["16", "br.16"];
        case 11:
            return ["18", "br.18"];
        // Brazil Official
        case 12:
            return ["AL", "br.l.official"];
        case 13:
            return ["A10", "br.10.official"];
        case 14:
            return ["A12", "br.12.official"];
        case 15:
            return ["A14", "br.14.official"];
        case 16:
            return ["A16", "br.16.official"];
        case 17:
            return ["A18", "br.18.official"];
        // Korea
        case 20:
            return ["All", "kr.all"];
        case 21:
            return ["12", "kr.12"];
        case 22:
            return ["15", "kr.15"];
        // Australia
        case 31:
            return ["15+", "AgeRating-AU-15"];
        case 32:
            return ["R 18+", "AgeRating-AU-18"];
        // France
        case 47:
            return ["18+", "AgeRating-FR-18"];
        default:
            return undefined;
    }
}
/// Returns a localized title for the given app platform.
export function appPlatformTitle(objectGraph, appPlatform) {
    switch (appPlatform) {
        case "phone":
            return objectGraph.loc.string("AppPlatform.Phone");
        case "pad":
            return objectGraph.loc.string("AppPlatform.Pad");
        case "vision":
            return objectGraph.loc.string("AppPlatform.Vision");
        case "tv":
            return objectGraph.loc.string("AppPlatform.TV");
        case "watch":
            return objectGraph.loc.string("AppPlatform.Watch");
        case "messages":
            return objectGraph.loc.string("AppPlatform.Messages");
        case "mac":
            return objectGraph.loc.string("AppPlatform.Mac");
        default:
            return "";
    }
}
/**
 * Provides the name of the asset representing the storefront content rating
 * for the provided `rank`.
 * @param objectGraph The App Store object graph.
 * @param rank A content rating rank from CX.
 * @returns The asset name representing the `rank`, corresponding to a file on
 * device, or `undefined` if rank is unknown/invalid.
 */
export function storefrontContentRatingResourceForRank(objectGraph, rank) {
    var _a;
    return (_a = storefrontContentRatingInfoForRank(objectGraph, rank)) === null || _a === void 0 ? void 0 : _a[1];
}
/**
 * Provides a textual representation of the storefront content rating for the
 * provided `rank`, e.g. "18+". This should match the main text displayed in
 * the content rating pictogram from `storefrontContentRatingResourceForRank`.
 * @param objectGraph The App Store object graph.
 * @param rank A content rating rank from CX.
 * @returns The textual version of the storefront content rating representing
 * the `rank`, or `undefined` if rank is unknown/invalid.
 */
export function storefrontContentRatingNameForRank(objectGraph, rank) {
    var _a;
    return (_a = storefrontContentRatingInfoForRank(objectGraph, rank)) === null || _a === void 0 ? void 0 : _a[0];
}
export function promotionalTextFromData(objectGraph, data, productVariantData) {
    return contentAttributes.customAttributeAsString(objectGraph, data, productVariantData, "promotionalText");
}
export function hasMessagesExtensionFromData(objectGraph, data, attributePlatform) {
    return contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "hasMessagesExtension", attributePlatform);
}
export function supportsFunCameraFromData(objectGraph, data, attributePlatform) {
    return contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "supportsFunCamera", attributePlatform);
}
export function isHiddenFromSpringboardFromData(objectGraph, data) {
    return contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "isHiddenFromSpringboard");
}
function isAppleWatchSupportedFromData(objectGraph, data) {
    return contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "isAppleWatchSupported");
}
function messagesScreenshotsFromData(objectGraph, data, attributePlatform) {
    return contentAttributes.contentAttributeAsDictionary(objectGraph, data, "messagesScreenshots", attributePlatform);
}
function screenshotsByTypeFromData(objectGraph, data, productVariantData, isAd, attributePlatform) {
    const attributeKey = isAd ? "customScreenshotsByTypeForAd" : "screenshotsByType";
    return contentAttributes.customAttributeAsDictionary(objectGraph, data, productVariantData, attributeKey, attributePlatform);
}
function videoPreviewsByTypeFromData(objectGraph, data, productVariantData, isAd, attributePlatform) {
    const attributeKey = isAd ? "customVideoPreviewsByTypeForAd" : "videoPreviewsByType";
    return contentAttributes.customAttributeAsDictionary(objectGraph, data, productVariantData, attributeKey, attributePlatform);
}
/**
 * Whether Arcade is supported, based on the provided data.
 * @param objectGraph The App Store object graph.
 * @param data The data blob to check for Arcade support.
 * @param attributePlatformOverride An override platform, from which to fetch the attribute.
 * @returns A boolean indicating if Arcade is supported.
 */
export function isArcadeSupported(objectGraph, data, attributePlatformOverride = undefined) {
    return contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "supportsArcade", attributePlatformOverride);
}
/**
 * Try to get notes for some piece of content, giving preference to the enrichedEditorialnotes, falling back to editorialNotes,
 * then finally to itunesNotes. For some data the notes are stored in the attributes not the platformAttributes.
 * @param {Data} data
 * @param {string} key
 * @param {boolean} enableEditorialCardOverrides This means we will also check for editorial-cards as well before chcking the default notes locations
 * * @param attributePlatformOverride An override platform, from which to fetch the attribute.
 * @returns {string}
 */
export function notesFromData(objectGraph, data, key, enableEditorialCardOverrides = false, attributePlatformOverride = undefined) {
    var _a, _b;
    if (isNothing(data)) {
        return null;
    }
    let note;
    if (enableEditorialCardOverrides) {
        const editorialCard = editorialCardFromData(data);
        if (mediaAttributes.hasAttributes(editorialCard)) {
            note = contentAttributes.contentAttributeAsString(objectGraph, editorialCard, ["editorialNotes", key], attributePlatformOverride);
        }
    }
    note =
        (_b = (_a = note !== null && note !== void 0 ? note : contentAttributes.contentAttributeAsString(objectGraph, data, ["enrichedEditorialNotes", key], attributePlatformOverride)) !== null && _a !== void 0 ? _a : contentAttributes.contentAttributeAsString(objectGraph, data, ["editorialNotes", key], attributePlatformOverride)) !== null && _b !== void 0 ? _b : contentAttributes.contentAttributeAsString(objectGraph, data, ["itunesNotes", key], attributePlatformOverride);
    return note;
}
/**
 * Try and get notes for some piece of content editorialNotes
 * @param {Data} data
 * @param {string} key
 * @param {boolean} enableEditorialCardOverrides This means we will also check for editorial-cards as well before chcking the default notes locations
 * @returns {string}
 */
export function editorialNotesFromData(objectGraph, data, key, enableEditorialCardOverrides = false) {
    var _a;
    let note;
    if (enableEditorialCardOverrides) {
        const editorialCard = editorialCardFromData(data);
        if (mediaAttributes.hasAttributes(editorialCard)) {
            note = contentAttributes.contentAttributeAsString(objectGraph, editorialCard, ["editorialNotes", key]);
        }
    }
    note =
        (_a = note !== null && note !== void 0 ? note : contentAttributes.contentAttributeAsString(objectGraph, data, ["enrichedEditorialNotes", key])) !== null && _a !== void 0 ? _a : contentAttributes.contentAttributeAsString(objectGraph, data, ["editorialNotes", key]);
    return note;
}
/**
 * Determines whether the provided data is for a macOS installer.
 * @param data The data against which to check for a macOS installer.
 */
export function isMacOSInstaller(objectGraph, data) {
    return derivedData.value(data, "isMacOSInstaller", () => {
        const isMac = objectGraph.client.isMac;
        if (!isMac) {
            return false;
        }
        const bundleId = contentAttributes.contentAttributeAsString(objectGraph, data, "bundleId");
        if (!serverData.isDefinedNonNull(bundleId)) {
            return false;
        }
        return bundleId.startsWith("com.apple.InstallAssistant");
    });
}
/**
 * Check whether an app is unsupported by the current companion configuration.
 * @param data The data representing an app listing.
 */
export function isUnsupportedByCurrentCompanion(objectGraph, data) {
    const deletableApps = sad.systemApps(objectGraph);
    if (objectGraph.host.isWatch) {
        // AppConduit will handle determining if SAD apps are supported
        if (deletableApps.isUnsupportedDeletableSystemAppFromData(data)) {
            return true;
        }
        else if (objectGraph.client.isTinkerWatch) {
            return (!contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "isStandaloneWithCompanionForWatchOS") && !contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "isStandaloneForWatchOS"));
        }
        return false;
    }
    else {
        if (isUnsupportedDeletableSystemAppFromData(objectGraph, data, objectGraph.client.isTinkerWatch)) {
            return true;
        }
        else if (objectGraph.client.isTinkerWatch) {
            if (deletableApps.isSystemAppFromData(data)) {
                // We don't consider whether an app is marked as standalone with companion
                // when running in standalone mode. We always want SAD apps to be installable.
                return (!contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "isDeliveredInIOSAppForWatchOS") &&
                    !contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "isStandaloneForWatchOS"));
            }
            else {
                return (!contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "isStandaloneWithCompanionForWatchOS") &&
                    !contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "isStandaloneForWatchOS"));
            }
        }
        else {
            // We only allow standalone system apps to be installed when the watch
            // is not running in standalone mode. This simplifies things for other teams.
            return (objectGraph.client.isWatch &&
                deletableApps.isSystemAppFromData(data) &&
                !contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "isStandaloneForWatchOS"));
        }
    }
}
/**
 * @deprecated Use sad.isUnsupportedDeletableSystemAppFromData instead.
 * Check whether a SAD app is unsupported by the current companion configuration.
 *
 * @param data The data representing an app listing.
 * @param isTinkerWatch Whether the current device is a tinker watch
 */
export function isUnsupportedDeletableSystemAppFromData(objectGraph, data, isTinkerWatch) {
    if (isTinkerWatch && sad.systemApps(objectGraph).isSystemAppFromData(data)) {
        const watchBundleId = mediaAttributes.attributeAsString(data, "watchBundleId");
        if (serverData.isDefinedNonNullNonEmpty(watchBundleId)) {
            switch (watchBundleId) {
                // rdar://63111354 (On Tinker device, able to attempt to download non-Tinker 1st and 3rd party app)
                // These apps should prevented from installing on a Tinker device
                case "com.apple.mobilemail.watchkitapp":
                case "com.apple.news.watchkitapp":
                case "com.apple.iBooks.watchkitapp":
                    return true;
                default:
                    return false;
            }
        }
    }
    return false;
}
/**
 *  Device Sizes
 *
 *  Please do not use these constants for anything but screenshots.
 *  Our code should not depend on absolute screen sizes for anything
 *  not related to selecting the correct screenshots to display. -km
 */
/// The screen size of iPhone 6.5" devices.
export const screenSizeIphone65 = new modelsBase.Size(414.0, 896.0);
/// The screen size of iPhone 5.8" devices.
export const screenSizeIPhone58 = new modelsBase.Size(375.0, 812.0);
/// The screen size of iPhone 6+ like devices.
export const screenSizeIPhone6Plus = new modelsBase.Size(414.0, 736.0);
/// The screen size of iPhone 6 like devices.
export const screenSizeIPhone6 = new modelsBase.Size(375.0, 667.0);
/// The screen size of iPhone 5 like devices.
export const screenSizeIPhone5 = new modelsBase.Size(320.0, 568.0);
/// The screen size of original iPhone like devices.
export const screenSizeIPhoneOriginal = new modelsBase.Size(320.0, 480.0);
/// The screen size of iPad and iPad mini devices.
export const screenSizeIPad = new modelsBase.Size(768.0, 1024.0);
/// The screen size of 7th and 8th gen 10.2" iPads.
export const screenSizeIPad102 = new modelsBase.Size(810.0, 1080.0);
/// The screen size of iPad pro 10.5" devices.
export const screenSizeIPad105 = new modelsBase.Size(834.0, 1112.0);
/// The screen size of iPad pro 11" devices.
export const screenSizeIPad11 = new modelsBase.Size(834.0, 1194.0);
/// The screen size of iPad Pro 11" devices in landscape orientation.
/// See `screenSizeIPadJ310Landscape` for info about the landscape orientation.
export const screenSizeIPad11Landscape = new modelsBase.Size(1194.0, 834.0);
/// The screen size of iPad pro 12.9" devices.
export const screenSizeIPadPro = new modelsBase.Size(1024.0, 1366.0);
/// The screen size of iPad pro 12.9" devices, with rounded corners.
export const screenSizeIPadPro2018 = new modelsBase.Size(1024.0, 1366.0);
/// The screen size of iPad pro 12.9" devices, with rounded corners, in landscape orientation.
/// See `screenSizeIPadJ310Landscape` for info about the landscape orientation.
export const screenSizeIPadPro2018Landscape = new modelsBase.Size(1366.0, 1024.0);
// The screen size of the J310 iPad device.
export const screenSizeIPadJ310 = new modelsBase.Size(744.0, 1133.0);
// The screen size of the J310 iPad device, in landscape orientation.
// rdar: //83176176 (J310: kMGQMainScreenCanvasSizes reports width as largest dimension, contrary to all other iPads and UIKit)
export const screenSizeIPadJ310Landscape = new modelsBase.Size(1133.0, 744.0);
/// The screen size of J720/J721 devices.
export const screenSizeJ720 = new modelsBase.Size(1032.0, 1376.0);
/// The screen size of J720/J721, in landscape orientation.
/// See `screenSizeIPadJ310Landscape` for info about the landscape orientation.
export const screenSizeJ720Landscape = new modelsBase.Size(1376.0, 1032.0);
/// The screen size of J717/J718 devices.
export const screenSizeJ717 = new modelsBase.Size(834.0, 1210.0);
/// The screen size of J717/J718, in landscape orientation.
/// See `screenSizeIPadJ310Landscape` for info about the landscape orientation.
export const screenSizeJ717Landscape = new modelsBase.Size(1210.0, 834.0);
/// The screen size of the 42mm Apple Watch devices.
export const screenSizeWatch = new modelsBase.Size(312.0, 390.0);
/// The screen size of large 2018 Apple Watch devices.
export const screenSizeWatch2018 = new modelsBase.Size(368.0, 448.0);
// The screen size of the large 2021 Apple Watch devices.
export const screenSizeWatch2021 = new modelsBase.Size(396.0, 484.0);
// The screen size for the 2022 Apple Watch devices.
export const screenSizeWatch2022 = new modelsBase.Size(410.0, 502.0);
// The screen size for the 2024 Apple Watch devices.
export const screenSizeWatch2024 = new modelsBase.Size(416.0, 496.0);
// The screen size for the Apple Watch Ultra / Ultra 2 devices.
export const screenSizeWatchUltra = new modelsBase.Size(410.0, 502.0);
/// The screen size for iPad device.
export const screenSizeIPadAir2020 = new modelsBase.Size(820.0, 1180.0);
/// The screen size for iPhone devices.
export const screenSizeIPhone131 = new modelsBase.Size(360.0, 780.0);
export const screenSizeIPhone132 = new modelsBase.Size(390.0, 844.0);
export const screenSizeIPhone134 = new modelsBase.Size(428.0, 926.0);
// The screen size for a 6.1" D73-style device.
export const screenSizeIPhone61 = new modelsBase.Size(393.0, 852.0);
// The screen size for a 6.7" D74-style device.
export const screenSizeIPhone67 = new modelsBase.Size(430.0, 932.0);
/// The screen size for a D93 device.
export const screenSizeD93 = new modelsBase.Size(402.0, 874.0);
/// The screen size for a D94 device.
export const screenSizeD94 = new modelsBase.Size(440.0, 956.0);
/// The screen size for a D23 device.
export const screenSizeD23 = new modelsBase.Size(420.0, 912.0);
/// The screen size for a N230 device.
export const screenSizeN230 = new modelsBase.Size(422.0, 514.0);
/// All phone types, in order of decreasing size.
const decreasingPhoneTypes = [
    "iphone_d74",
    "iphone_6_5",
    "iphone_d73",
    "iphone_5_8",
    "iphone6+",
    "iphone6",
    "iphone5",
    "iphone",
];
// region Device Corner Radius
/**
 * The reason we need to hardcode these is because we may want to display screenshots with rounding for a device that is
 * not the one the user is currently browsing the store with. For example, imagine that the user is browsing the store
 * with an iPhone 8 but ends up looking at an app that only has D22 screenshots. They should see the D22 screenshots
 * according to the D22 corner rounding, and using the current client's `screenCornerRadius` would not give us the
 * proper value.
 */
/// The device corner radius of iPad pro 12.9" devices from 2018.
const deviceCornerRadiusIpadPro2018 = 18.0;
/// The device corner radius of iPad pro 11" devices.
const deviceCornerRadiusIpad11 = 18.0;
/// The device corner radius of iPhone 6.5" devices.
const deviceCornerRadiusIphone65 = 41.5;
/// The device corner radius of iPhone 5.8" devices.
const deviceCornerRadiusIphone58 = 39.0;
/// The device corner radius of iPhone 6.1" devices.
const deviceCornerRadiusIphone61 = 55.0;
/// The device corner radius of iPhone 6.7" devices.
const deviceCornerRadiusIphone67 = 55.0;
/// The device corner radius of large 2018 Apple Watch devices.
const deviceCornerRadiusWatch2018 = 34.0;
/// The outer device corner radius of large Apple Watch devices.
const outerDeviceCornerRadiusWatch = 30.0;
/// The device border thickness for Apple Watch.
const deviceBorderThicknessWatch = 13.0;
/// The device border thickness for 2018 Apple Watch.
const deviceBorderThicknessWatch2018 = 11.0;
/// The device corner radius for 2021 Apple Watch.
const deviceCornerRadiusWatch2021 = 55;
/// The device border thickness for 2021 Apple Watch.
const deviceBorderThicknessWatch2021 = 5.5;
/// The device corner radius for 2022/2024 Apple Watch.
const deviceCornerRadiusWatch2022 = 108;
/// The outer device corner radius for 2022/2024 Apple Watch.
const deviceOuterCornerRadiusWatch2022 = 112.5;
/// The device border thickness for 2022/2024 Apple Watch.
const deviceBorderThicknessWatch2022 = 4.5;
export function currentAppPlatform(objectGraph) {
    var _a;
    switch (objectGraph.client.deviceType) {
        case "web":
            return unwrap((_a = objectGraph.activeIntent) === null || _a === void 0 ? void 0 : _a.appPlatform);
        default:
            return objectGraph.client.deviceType;
    }
}
/// The SF Symbol name that represents the media type.
export function systemImageNameForAppPlatform(appPlatform) {
    switch (appPlatform) {
        case "phone":
            return "iphone";
        case "pad":
            return "ipad";
        case "tv":
            return "tv";
        case "watch":
            return "applewatch";
        case "mac":
            return "macbook";
        case "messages":
            return "message";
        case "vision":
            return "visionpro";
        default:
            unreachable(appPlatform);
    }
}
/**
 * Returns the factor by which to multiply an artwork's portrait-equivalent width, in order to compute the artwork's
 * device-rounded corner radius. This is useful because we want to display screenshots for device-rounded screenshots
 * with a corner radius that is scaled to that of the device screen.
 *
 *                          r = w * r'
 *                          r' = R / W
 *
 *                      Where:
 *                          r: scaled radius
 *                          r': The return value of this function.
 *                          R: device corner radius
 *                          w: width at which the screenshot will be displayed
 *                          W: device width
 *
 * We need to have this value here in the JS because we may need to display device-rounded screenshots on a device that
 * does not have a device corner radius; there is no native API for querying the corner radius of various devices and,
 * even if there were, we want to avoid specific screen-size checks in the native code.
 * @param type The screenshot type vended by the server.
 * @returns {number} The device corner radius factor, or null if the device does not have a corner radius.
 */
function deviceCornerRadiusFactorForMediaType(objectGraph, type) {
    // Let's only bridge over and access client's properties if we need to.
    switch (type) {
        case "ipadPro_2018":
            return deviceCornerRadiusIpadPro2018 / screenSizeIPadPro2018.width;
        case "ipad_11":
            return deviceCornerRadiusIpad11 / screenSizeIPad11.width;
        case "iphone_6_5":
            return deviceCornerRadiusIphone65 / screenSizeIphone65.width;
        case "iphone_5_8":
            return deviceCornerRadiusIphone58 / screenSizeIPhone58.width;
        case "iphone_d73":
            return deviceCornerRadiusIphone61 / screenSizeIPhone61.width;
        case "iphone_d74":
            return deviceCornerRadiusIphone67 / screenSizeIPhone67.width;
        case "appleWatch_2018":
            return deviceCornerRadiusWatch2018 / screenSizeWatch2018.width;
        case "appleWatch_2021":
            return deviceCornerRadiusWatch2021 / screenSizeWatch2021.width;
        case "appleWatch_2022":
            return deviceCornerRadiusWatch2022 / screenSizeWatch2022.width;
        case "appleWatch_2024":
            return deviceCornerRadiusWatch2022 / screenSizeWatch2024.width;
        default:
            return null;
    }
}
function deviceOuterCornerRadiusFactorForMediaType(objectGraph, type) {
    switch (type) {
        case "appleWatch":
            return outerDeviceCornerRadiusWatch / screenSizeWatch.width;
        case "appleWatch_2022":
            return deviceOuterCornerRadiusWatch2022 / screenSizeWatch2022.width;
        case "appleWatch_2024":
            return deviceOuterCornerRadiusWatch2022 / screenSizeWatch2024.width;
        default:
            return deviceCornerRadiusFactorForMediaType(objectGraph, type);
    }
}
function deviceBorderThicknessForMediaType(objectGraph, type) {
    switch (type) {
        case "appleWatch":
            return deviceBorderThicknessWatch / screenSizeWatch.width;
        case "appleWatch_2018":
            return deviceBorderThicknessWatch2018 / screenSizeWatch2018.width;
        case "appleWatch_2021":
            return deviceBorderThicknessWatch2021 / screenSizeWatch2021.width;
        case "appleWatch_2022":
            return deviceBorderThicknessWatch2022 / screenSizeWatch2022.width;
        case "appleWatch_2024":
            return deviceBorderThicknessWatch2022 / screenSizeWatch2024.width;
        default:
            return null;
    }
}
// endregion
/**
 Returns a boolean indicating if the client's operating
 system is the same or later than the specified version.

 @param version    The full version number to check against
 @returns true if the operating system is the same or newer than the specified version; false otherwise.
 */
export function isOSAtLeastVersion(objectGraph, version) {
    if (serverData.isNull(version) || version.length === 0) {
        return true;
    }
    const versionComponents = version.split(".");
    const majorVersion = serverData.asNumber(versionComponents[0]) || 0;
    const minorVersion = serverData.asNumber(versionComponents[1]) || 0;
    const patchVersion = serverData.asNumber(versionComponents[2]) || 0;
    return objectGraph.host.isOSAtLeast(majorVersion, minorVersion, patchVersion);
}
/**
 Returns a boolean indicating if the system version of the active, paired watch (if any) is
 at least the provided version number.

 @param version    The full version number to check against
 @returns true if an active, paired watch exists, and its operating system  version is the same or newer than the specified version; false otherwise.
 */
export function isActivePairedWatchOSAtLeastVersion(objectGraph, version) {
    if (serverData.isNull(version) || version.length === 0) {
        return true;
    }
    const versionComponents = version.split(".");
    const majorVersion = serverData.asNumber(versionComponents[0]) || 0;
    const minorVersion = serverData.asNumber(versionComponents[1]) || 0;
    const patchVersion = serverData.asNumber(versionComponents[2]) || 0;
    return objectGraph.client.isActivePairedWatchSystemVersionAtLeastMajorVersionMinorVersionPatchVersion(majorVersion, minorVersion, patchVersion);
}
/**
 * Check whether the active paired device's OS is the same or greater than a given version.
 */
export function isActivePairedDeviceAtLeastVersion(objectGraph, version) {
    if (serverData.isNull(version) || version.length === 0) {
        return true;
    }
    return objectGraph.client.isPairedSystemVersionAtLeast(version);
}
/**
 * Check whether the active paired device's OS is below a given version.
 */
export function isActivePairedWatchOSBelowVersion(objectGraph, version) {
    if (serverData.isNull(version) || version.length === 0) {
        return false;
    }
    return objectGraph.client.isActivePairedWatchSystemVersionBelow(version);
}
export function shelfContentTypeCanDisplayArcadeOfferButtons(objectGraph, shelfStyle) {
    if (objectGraph.client.isTV) {
        switch (shelfStyle) {
            case "upsellBreakout":
                return true;
            default:
                return false;
        }
    }
    else {
        switch (shelfStyle) {
            case "smallLockup":
            case "mediumLockup":
            case "appTrailerLockup":
            case "screenshotsLockup":
            case "mixedMediaLockup":
            case "upsellBreakout":
            case "arcadeShowcase":
                return true;
            default:
                return false;
        }
    }
}
export function shelfDisplayStyleCanDisplayArcadeOfferButtons(objectGraph, displayStyle) {
    switch (displayStyle) {
        case "LockupSmall":
        case "LockupLarge":
        case "BreakoutLarge":
        case "Hero":
        case "EditorialLockupLarge":
        case "EditorialLockupLargeVariant":
        case "EditorialLockupMedium":
        case "EditorialLockupMediumVariant":
        case "StoryMedium":
            return true;
        default:
            return false;
    }
}
/**
 * The dynamic date string used by apps coming soon.
 */
export function dynamicPreorderDateFromData(objectGraph, data, fallbackLabel) {
    const preorderOffer = offers.offerDataFromData(objectGraph, data);
    const isPreorder = serverData.asString(preorderOffer, "type") === "preorder";
    if (isPreorder) {
        const releaseDateRaw = serverData.asString(preorderOffer, "expectedReleaseDate");
        const dateDisplayFormat = contentAttributes.contentAttributeAsString(objectGraph, data, "expectedReleaseDateDisplayFormat");
        if (serverData.isDefinedNonNullNonEmpty(dateDisplayFormat)) {
            if (serverData.isDefinedNonNullNonEmpty(releaseDateRaw)) {
                const releaseDate = dateUtil.parseDateOmittingTimeFromString(releaseDateRaw);
                const tokenFormatMap = {
                    "@@expectedDateMY@@": objectGraph.loc.string("PreOrder.Date.MonthYear"),
                    "@@expectedDateMDY@@": objectGraph.loc.string("PreOrder.Date.MonthDayYear"),
                };
                for (const [serverToken, dateFormat] of Object.entries(tokenFormatMap)) {
                    if (dateDisplayFormat.includes(serverToken)) {
                        let formattedDate = objectGraph.loc.formatDateWithContext(dateFormat, releaseDate, "middleOfSentence");
                        if (objectGraph.client.isTV) {
                            formattedDate = formattedDate.replace(/ /g, "\u00a0");
                        }
                        return dateDisplayFormat.replace(serverToken, formattedDate);
                    }
                }
            }
            return dateDisplayFormat;
        }
    }
    // There was no dynamic date to display
    return fallbackLabel;
}
/**
 * The primary content for an editorial item.
 * @param data The data from which to derive the primary content.
 */
export function primaryContentForData(objectGraph, data) {
    const primaryContent = mediaRelationship.relationshipData(objectGraph, data, "primary-content");
    if (serverData.isDefinedNonNullNonEmpty(primaryContent)) {
        return primaryContent;
    }
    // If an EI has canvasData, then in MAPI response its "primary-content" relationship will not include the
    // primary content meta data. Instead, the primary content data will be included in the "card-contents" relationship.
    if (contentAttributes.contentAttributeAsBooleanOrFalse(objectGraph, data, "isCanvasAvailable")) {
        return mediaRelationship.relationshipData(objectGraph, data, "card-contents");
    }
    return null;
}
const grayColorHex = "#9BA9BD";
export const grayColorCriteria = {
    colorHex: grayColorHex,
    maxSaturation: 4,
    maxBrightness: 9,
};
/**
 * The list of tag background colors we can use to match an app icon background color against, based on saturation and brightness
 */
const saturationBrightnessBasedTagColorBuckets = [
    grayColorCriteria, // Gray
];
/**
 * The list of tag background colors we can use to match an app icon background color against
 */
const hueBasedTagColorBuckets = [
    { colorHex: "#F7816F", minHue: 0, maxHue: 16 },
    { colorHex: "#FF9034", minHue: 17, maxHue: 33 },
    { colorHex: "#E3B059", minHue: 34, maxHue: 59 },
    { colorHex: "#74BD66", minHue: 60, maxHue: 129 },
    { colorHex: "#72C792", minHue: 130, maxHue: 169 },
    { colorHex: "#61BFE2", minHue: 170, maxHue: 209 },
    { colorHex: "#6EA3E9", minHue: 210, maxHue: 239 },
    { colorHex: "#7D69FA", minHue: 240, maxHue: 259 },
    { colorHex: "#B363F7", minHue: 260, maxHue: 289 },
    { colorHex: "#EE7CBD", minHue: 290, maxHue: 360 }, // Pink
];
/**
 * Find the matching tag color for an icon's background color
 *
 * @param iconBackgroundColor The bgColor from the icon artwork data
 * @returns The closest matching color from the HI provided set of tag background colors for this icon color
 */
export function closestTagBackgroundColorForIcon(iconBackgroundColor) {
    var _a;
    return ((_a = color.findColorBucketForColor(iconBackgroundColor, saturationBrightnessBasedTagColorBuckets, hueBasedTagColorBuckets)) !== null && _a !== void 0 ? _a : color.fromHex(grayColorHex));
}
/**
 * Parses a JoeColorSet out of a MAPI EditorialArtwork JSON object
 * @param data An EditorialArtwork JSON object from MAPI
 * @returns A JoeColorSet from parsing the input RGB values
 */
export function joeColorHexSetFromData(data) {
    var _a, _b, _c, _d, _e;
    const textGradient = [];
    for (const gradientColorHex of serverData.asArrayOrEmpty(data, "textGradient")) {
        if (isSome(gradientColorHex) && serverData.isString(gradientColorHex)) {
            textGradient.push(gradientColorHex);
        }
    }
    return {
        bgColor: (_a = serverData.asString(data, "bgColor")) !== null && _a !== void 0 ? _a : undefined,
        textColor1: (_b = serverData.asString(data, "textColor1")) !== null && _b !== void 0 ? _b : undefined,
        textColor2: (_c = serverData.asString(data, "textColor2")) !== null && _c !== void 0 ? _c : undefined,
        textColor3: (_d = serverData.asString(data, "textColor3")) !== null && _d !== void 0 ? _d : undefined,
        textColor4: (_e = serverData.asString(data, "textColor4")) !== null && _e !== void 0 ? _e : undefined,
        textGradient: isDefinedNonNullNonEmpty(textGradient) ? textGradient : undefined,
    };
}
/**
 * Parses a JoeColorSet out of a MAPI EditorialArtwork JSON object
 * @param data An EditorialArtwork JSON object from MAPI
 * @returns A JoeColorSet from parsing the input RGB values
 */
export function joeColorSetFromData(data) {
    var _a, _b, _c, _d, _e, _f;
    const joeColorHexSet = joeColorHexSetFromData(data);
    const textGradient = [];
    for (const gradientColorHex of (_a = joeColorHexSet.textGradient) !== null && _a !== void 0 ? _a : []) {
        const gradientColor = color.fromHex(gradientColorHex);
        if (isSome(gradientColor)) {
            textGradient.push(gradientColor);
        }
    }
    return {
        bgColor: (_b = color.fromHex(joeColorHexSet.bgColor)) !== null && _b !== void 0 ? _b : undefined,
        textColor1: (_c = color.fromHex(joeColorHexSet.textColor1)) !== null && _c !== void 0 ? _c : undefined,
        textColor2: (_d = color.fromHex(joeColorHexSet.textColor2)) !== null && _d !== void 0 ? _d : undefined,
        textColor3: (_e = color.fromHex(joeColorHexSet.textColor3)) !== null && _e !== void 0 ? _e : undefined,
        textColor4: (_f = color.fromHex(joeColorHexSet.textColor4)) !== null && _f !== void 0 ? _f : undefined,
        textGradient: isDefinedNonNullNonEmpty(textGradient) ? textGradient : undefined,
    };
}
/**
 * Attempt to find the first non-gray placeholder color
 * @param joeColorSet The joe color set for a given icon
 * @returns The color hext value to use for the joe color placeholder
 */
export function bestJoeColorPlaceholderSelectionLogic(joeColorSet) {
    const joeColorKeys = [
        "bgColor",
        "textColor1",
        "textColor2",
        "textColor3",
        "textColor4",
    ];
    for (const joeColorKey of joeColorKeys) {
        const joeColorHex = joeColorSet[joeColorKey];
        if (!serverData.isString(joeColorHex)) {
            continue;
        }
        const isGrayColor = color.doesColorMeetCriteria(color.fromHex(joeColorHex), grayColorCriteria);
        if (!isGrayColor) {
            return joeColorHex;
        }
    }
    return null;
}
//# sourceMappingURL=content.js.map