/**
 * Created by keithpk on 3/21/17.
 */
import { isNothing, isSome } from "@jet/environment";
import * as validation from "@jet/environment/json/validation";
import * as models from "../../api/models";
import * as serverData from "../../foundation/json-parsing/server-data";
import * as mediaAttributes from "../../foundation/media/attributes";
import * as mediaAugment from "../../foundation/media/augment";
import * as mediaDataStructure from "../../foundation/media/data-structure";
import * as mediaNetwork from "../../foundation/media/network";
import * as mediaRelationships from "../../foundation/media/relationships";
import * as urls from "../../foundation/network/urls";
import * as color from "../../foundation/util/color-util";
import { PageID } from "../../gameservicesui/src/common/id-builder";
import * as gamesComponentBuilder from "../../gameservicesui/src/editorial-page/editorial-component-builder";
import * as appPromotionsShelf from "../app-promotions/app-promotions-shelf";
import * as arcadeCommon from "../arcade/arcade-common";
import * as arcadeUpsell from "../arcade/arcade-upsell";
import * as breakoutsCommon from "../arcade/breakouts-common";
import * as videoDefaults from "../constants/video-constants";
import * as artworkBuilder from "../content/artwork/artwork";
import * as content from "../content/content";
import { EditorialMediaPlacement } from "../editorial-pages/editorial-media-util";
import { buildSmallStoryCardShelf } from "../editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-story-card-collection-shelf-builder";
import { buildStoryCard } from "../editorial-pages/editorial-page-shelf-builder/editorial-page-collection-shelf-builder/editorial-page-story-card-utils";
import { createBaseShelfToken } from "../editorial-pages/editorial-page-shelf-token";
import { CollectionShelfDisplayStyle } from "../editorial-pages/editorial-page-types";
import * as externalDeepLink from "../linking/external-deep-link";
import * as links from "../linking/os-update-links";
import * as lockups from "../lockups/lockups";
import * as metricsHelpersClicks from "../metrics/helpers/clicks";
import * as metricsHelpersImpressions from "../metrics/helpers/impressions";
import * as metricsHelpersLocation from "../metrics/helpers/location";
import * as metricsHelpersMedia from "../metrics/helpers/media";
import * as metricsHelpersPage from "../metrics/helpers/page";
import * as metricsHelpersUtil from "../metrics/helpers/util";
import * as sharing from "../sharing";
import { crossLinkSubtitleFromData, defaultTodayCardConfiguration, fallbackWatchTodayCardFromData, todayCardFromData, } from "./today-card-util";
import * as todayHorizontalCardUtil from "./today-horizontal-card-util";
import { todayCardPreviewUrlForTodayCard } from "./today-parse-util";
import { HeroMediaDisplayContext, TodayCardDisplayStyle, TodayParseContext, } from "./today-types";
export const iAPBackgroundColor = color.named("componentBackgroundStandout");
const appShowcaseBackgroundColor = color.named("componentBackgroundStandout");
const arcadeShowcaseShelfBackgroundColor = color.named("componentBackgroundStandout");
/**
 * Resolves the article module's app media platform to an `AppPlatform` to use for screenshots.
 * @param {AppMediaPlatform} appMediaPlatform The server-dictated media platform to use for the module.
 * @returns {AppPlatform} The app platform that is appropriate for this media platform, taking into account our device.
 */
function appPlatformFromAppMediaPlatform(objectGraph, appMediaPlatform) {
    switch (appMediaPlatform) {
        case "Watch":
            return "watch";
        case "iOS":
            if (objectGraph.client.isPad) {
                return "pad";
            }
            else {
                return "phone";
            }
        case "tvOS":
            return "tv";
        case "Messages":
            return "messages";
        case "visionOS":
            return "vision";
        default:
            return null;
    }
}
export class ArticleParseContext {
    constructor() {
        // The index of the current module
        this.index = 0;
        // The reco metrics from the shelf on the today page
        this.todayShelfRecoMetricsData = {};
        /// Whether there are any focusable elements (for touch mode)
        this.hasFocusableElements = false;
        /// Whether there are any non-focusable elements (for touch mode)
        this.hasNonFocusableElements = false;
        /// Whether there is a resilient deep link.
        this.isResilientDeepLink = false;
        /// Whether or not to allow app event previews, used by editorial to preview app event stories before they are published
        this.allowUnpublishedAppEventPreviews = false;
    }
}
function todayCardConfigFromArticleContext(objectGraph, articleContext) {
    if (!serverData.isDefinedNonNull(articleContext)) {
        return null;
    }
    if (isSome(articleContext.todayCardConfig)) {
        return articleContext.todayCardConfig;
    }
    const config = defaultTodayCardConfiguration(objectGraph);
    config.enableListCardToMultiAppFallback = false;
    config.clientIdentifierOverride = articleContext.clientIdentifierOverride;
    config.useOTDTextStyle = false;
    config.allowUnpublishedAppEventPreviews = articleContext.allowUnpublishedAppEventPreviews;
    config.currentRowIndex = undefined;
    switch (objectGraph.client.deviceType) {
        case "mac":
            config.prevailingCropCodes = { defaultCrop: "en" };
            config.coercedCollectionTodayCardDisplayStyle = TodayCardDisplayStyle.Grid;
            config.heroDisplayContext = HeroMediaDisplayContext.Article;
            break;
        case "tv":
            config.prevailingCropCodes = {
                "defaultCrop": "ek",
                "editorialArtwork.storyCenteredStatic16x9": "SCS.ApDHXL01",
            };
            config.coercedCollectionTodayCardDisplayStyle = TodayCardDisplayStyle.Grid;
            config.heroDisplayContext = HeroMediaDisplayContext.Article;
            break;
        case "web":
            config.coercedCollectionTodayCardDisplayStyle = TodayCardDisplayStyle.River;
            config.prevailingCropCodes = {
                "defaultCrop": "sr",
                "editorialArtwork.dayCard": "grav.west",
            };
            break;
        default:
            break;
    }
    return config;
}
export function articlePageFromResponse(objectGraph, articleResponse, context) {
    return validation.context("articlePageWithResponse", () => {
        var _a;
        const articleData = mediaDataStructure.dataFromDataContainer(objectGraph, articleResponse);
        context.metricsPageInformation = metricsHelpersPage.metricsPageInformationFromMediaApiResponse(objectGraph, "editorialItem", articleData.id, articleResponse);
        context.metricsLocationTracker = metricsHelpersLocation.newLocationTracker();
        context.pageId = articleData.id;
        // Bridge over article contexts to today's metrics context and card config
        const todayParseContext = new TodayParseContext(context.metricsPageInformation, context.metricsLocationTracker, context.refreshController);
        const todayCardConfig = todayCardConfigFromArticleContext(objectGraph, context);
        // Render the top card
        let todayCard = todayCardFromData(objectGraph, articleData, todayCardConfig, todayParseContext);
        let editorialStoryCard = null;
        const todayCardMedia = todayCard === null || todayCard === void 0 ? void 0 : todayCard.media;
        if (objectGraph.client.isVision || preprocessor.GAMES_TARGET) {
            editorialStoryCard = buildStoryCard(objectGraph, articleData, EditorialMediaPlacement.StoryDetail, {
                pageInformation: context.metricsPageInformation,
                locationTracker: context.metricsLocationTracker,
            }, CollectionShelfDisplayStyle.StoryMedium, false);
            todayCard = null;
        }
        if (isNothing(todayCard)) {
            todayCard = fallbackWatchTodayCardFromData(objectGraph, articleData, todayCardConfig, todayParseContext);
        }
        // Get the title for metrics purposes.
        const title = (_a = todayCard === null || todayCard === void 0 ? void 0 : todayCard.title) !== null && _a !== void 0 ? _a : editorialStoryCard === null || editorialStoryCard === void 0 ? void 0 : editorialStoryCard.title;
        const editorialItemKind = mediaAttributes.attributeAsString(articleData, "kind");
        // Configure subtitle for cross link
        context.crossLinkSubtitle = crossLinkSubtitleFromData(objectGraph, articleData);
        // Bridge today config back into articles, now that cards are created.
        // Now we've created the card, reference the clientIdentifierOverride it used for the rest of the article.
        context.clientIdentifierOverride = todayCardConfig.clientIdentifierOverride;
        // Start a metrics location
        metricsHelpersLocation.pushContentLocation(objectGraph, {
            pageInformation: context.metricsPageInformation,
            locationTracker: context.metricsLocationTracker,
            targetType: "article",
            id: context.pageId,
            idType: "its_id",
        }, title);
        // Render the article itself
        const shelves = renderArticle(objectGraph, articleData, todayCardMedia, context);
        const lastShelf = shelves[shelves.length - 1];
        // Sharing
        const shareAction = objectGraph.client.isTV ||
            objectGraph.client.isWeb ||
            context.isResilientDeepLink ||
            preprocessor.GAMES_TARGET ||
            editorialItemKind === "OfferItem"
            ? null
            : shareSheetActionFromData(objectGraph, articleData, todayCardConfig);
        if (serverData.isDefinedNonNull(shareAction)) {
            // Add click event
            metricsHelpersClicks.addClickEventToAction(objectGraph, shareAction, {
                targetType: "button",
                id: context.pageId,
                actionType: "share",
                pageInformation: context.metricsPageInformation,
                locationTracker: context.metricsLocationTracker,
            });
            const isLastModuleFullWidth = isArticleShelfFullWidth(objectGraph, lastShelf, context.module);
            const shareButtonShelf = createShareShelf(objectGraph, shareAction, context, isLastModuleFullWidth);
            if (shareButtonShelf) {
                shelves.push(shareButtonShelf);
            }
        }
        const page = new models.ArticlePage(todayCard, shelves, shareAction);
        page.editorialStoryCard = editorialStoryCard;
        page.title = todayCard === null || todayCard === void 0 ? void 0 : todayCard.title;
        page.subtitle = todayCard === null || todayCard === void 0 ? void 0 : todayCard.inlineDescription;
        addFooterLockupForPageIfNeeded(objectGraph, page, articleData, context);
        if (objectGraph.client.isTV) {
            if (context.hasFocusableElements && !context.hasNonFocusableElements) {
                page.touchMode = "focus";
            }
            else if (!context.hasFocusableElements && context.hasNonFocusableElements) {
                page.touchMode = "pan";
            }
            else {
                page.touchMode = "auto";
            }
        }
        // Map whether the article should terminate on close.
        page.shouldTerminateOnClose = context.isResilientDeepLink;
        metricsHelpersPage.addMetricsEventsToPageWithInformation(objectGraph, page, context.metricsPageInformation, (fields) => {
            let additionalValue = title;
            if ((todayCard === null || todayCard === void 0 ? void 0 : todayCard.media) instanceof models.TodayCardMediaBrandedSingleApp &&
                (todayCard === null || todayCard === void 0 ? void 0 : todayCard.overlay) instanceof models.TodayCardLockupOverlay) {
                const lockupOverlay = todayCard === null || todayCard === void 0 ? void 0 : todayCard.overlay;
                additionalValue = lockupOverlay.lockup.title;
            }
            if (!additionalValue) {
                return;
            }
            let pageDetails = serverData.asString(serverData.asJSONValue(fields["pageDetails"]), "coercible");
            pageDetails = pageDetails || serverData.asString(serverData.asJSONValue(fields["pageId"]));
            if (pageDetails) {
                fields["pageDetails"] = `${pageDetails}_${additionalValue}`;
            }
            else {
                fields["pageDetails"] = `unknown_${additionalValue}`;
            }
        });
        page.canonicalURL = mediaAttributes.attributeAsString(articleData, "url");
        if (isSome(articleData)) {
            const articleUrl = mediaAttributes.attributeAsString(articleData, "url");
            if (isSome(articleUrl)) {
                page.viewArticleAction = new models.ExternalUrlAction(articleUrl, true);
            }
        }
        return page;
    });
}
function renderArticle(objectGraph, articleData, cardMedia, context) {
    return validation.context("renderArticle", () => {
        var _a;
        const shelves = [];
        const canvas = (_a = mediaRelationships.relationshipCollection(articleData, "canvas")) !== null && _a !== void 0 ? _a : [];
        for (const storyModule of canvas) {
            context.module = mediaAttributes.attributeAsString(storyModule, "displayType");
            context.subStyle = null;
            const shelfIndex = shelves.length;
            const shelvesToRender = renderModule(objectGraph, storyModule, articleData, context, shelfIndex);
            if (shelvesToRender.length > 0) {
                for (const shelf of shelvesToRender) {
                    shelf.title = context.titleForNextShelf;
                    if (objectGraph.client.isTV) {
                        // Skip unsupported tvOS shelves
                        if (shelf.contentType === "editorialLink") {
                            continue;
                        }
                    }
                    else if (objectGraph.client.isWatch) {
                        // Skip unsupported watchOS shelves
                        if (shelf.contentType === "editorialLink") {
                            continue;
                        }
                    }
                    shelves.push(shelf);
                    context.titleForNextShelf = null;
                }
            }
            context.index++;
            metricsHelpersLocation.nextPosition(context.metricsLocationTracker);
        }
        // If we we're showing the fallback list card type on the today page, we're going to show
        // the lockups as a list shelf underneath so we can still display the list contents.
        // If we're on watchOS, we also want to hit this codepath so that lockup lists do not
        // show as empty pages.
        if ((context.showingFallbackMediaInline ||
            objectGraph.client.isWatch ||
            objectGraph.client.isVision ||
            objectGraph.client.isWeb ||
            preprocessor.GAMES_TARGET) &&
            shelves.length === 0) {
            const fallbackShelf = createFallbackListShelf(objectGraph, cardMedia);
            if (serverData.isDefinedNonNull(fallbackShelf)) {
                shelves.push(fallbackShelf);
            }
        }
        return shelves;
    });
}
// region Data Augmenting
/**
 * Article specific entrypoint for page response augmenting. See `augment.ts`.
 * @param response Response to augment.
 */
export async function fetchAdditionalDataForInitialResponse(objectGraph, response) {
    return await mediaAugment.fetchAugmentedData(objectGraph, response, findAdditionalDataKeysForArticleResponse, fetchDataForArticleDataKey);
}
/**
 * Determine the set of data, expressed as an set of `ArticleAdditionalDataKey`s, that need to be fetched for given article response to be displayed.
 * This is equivalent to `AbstractMediaApiPageBuilder.additionalDataKeysNeededForData`, but for article builder which doesn't adopt the `builder` API.
 *
 * @param articleResponse Initial response to determine additional data requirements for.
 * @returns {Set<ArticleAdditionalDataKey>} Additional data needed expressed as set of `ArticleAdditionalDataKey`
 */
function findAdditionalDataKeysForArticleResponse(objectGraph, articleResponse) {
    /**
     * Keys for requested requirements determined by:
     * - Modules in canvas only now :)
     */
    const allAdditionalDataKeySet = new Set();
    // Requirements based on canvas items:
    const articleData = mediaDataStructure.dataFromDataContainer(objectGraph, articleResponse);
    const canvasModules = mediaRelationships.relationshipCollection(articleData, "canvas");
    for (const storyModule of canvasModules) {
        // Determine additional requests and add to `allRequirementsSet`
        const dataKeysForModule = additionalDataKeysForArticleModule(objectGraph, storyModule, articleData);
        if (serverData.isDefinedNonNullNonEmpty(dataKeysForModule)) {
            for (const requirement of dataKeysForModule) {
                allAdditionalDataKeySet.add(requirement);
            }
        }
    }
    return allAdditionalDataKeySet;
}
/**
 * Builds a promise that will fetch data fulfilling given requirement. Note that these promises will return `null` when they fail,
 * and their failure should not cause the entire page to fail.
 * This is equivalent to `AbstractMediaApiPageBuilder.fetchAdditionalDataForKey`, but for article builder which doesn't adopt the `builder` API.
 *
 * @param dataKey Corresponding data key to fetch data for.
 */
// eslint-disable-next-line @typescript-eslint/promise-function-async
function fetchDataForArticleDataKey(objectGraph, dataKey) {
    let request;
    if (dataKey === "upsellForNonacquisitionCanvas") {
        // Use `editorialItem` matching context of that would've otherwise been joined if this story was an acquisition story.
        request = arcadeCommon.arcadeUpsellRequest(objectGraph, models.marketingItemContextFromString("editorialItemCanvas"));
    }
    if (dataKey === "arcadeIcons") {
        // Require 10 for now.
        request = arcadeCommon.arcadeAppsRequestForIcons(objectGraph, 10);
    }
    if (serverData.isNull(request)) {
        return null;
    }
    // Failable data fetch, either resolving to valid response or `null`.
    return mediaNetwork.fetchData(objectGraph, request).catch(() => null);
}
/**
 * Determine the requirements for single article module as determined by it's type.
 * @param storyModule The module to fetch additional requirements for.
 * @param articleData The article that contains `storyModule` in its canvas.
 * @returns {ArticleAdditionalDataKey[] | undefined} Set of data keys if any are needed for rendering given module.
 */
export function additionalDataKeysForArticleModule(objectGraph, storyModule, articleData) {
    // Only `AppMarker` has additional requirements.
    const moduleType = mediaAttributes.attributeAsString(storyModule, "displayType");
    if (moduleType !== "AppMarker") {
        return null;
    }
    const markerType = mediaAttributes.attributeAsString(storyModule, "appMarkerType");
    // <rdar://problem/55919205> In story Arcade acquisition module dropping from stories
    // Editorial wants to use the acquisition module in non-acquisition stories, but the `upsell` relationship is only joined for EIs marked with the acquisition flag.
    // When an article is missing the upsell relationship, we'll fetch it separately if we have modules that need it...
    const articleDataIsMissingUpsell = serverData.isNull(arcadeCommon.upsellFromRelationshipOf(objectGraph, articleData));
    /**
     * Acquisition AppMarker, i.e. `ArcadeShowcase` needs:
     * 1. Upsell data for text data, e.g. editorial notes and breakoutCallToAction label, provided this data isn't already provided as part of original page.
     * 2. Assortment of Arcade App Icons (iOS Only).
     */
    const additionalDataKeysForModule = [];
    if (markerType === "Acquisition") {
        // iOS needs icon dependency
        if (objectGraph.host.isiOS || objectGraph.client.isVision) {
            additionalDataKeysForModule.push("arcadeIcons");
        }
        // All platform needs upsell to render acquisition modules, add it if missing.
        if (articleDataIsMissingUpsell) {
            additionalDataKeysForModule.push("upsellForNonacquisitionCanvas");
        }
    }
    return additionalDataKeysForModule;
}
// endregion
/**
 * Create a shelf model representing a single module within article pages.
 * @param storyModule Module server data to build shelf and contents from.
 * @param articleData The data for article that contains `storyModule` above.
 * @param context Global parse context updated while entire sets of modules are being parsed.
 * @returns an array of `Shelf` or `null` if building fails for given module.
 */
function renderModule(objectGraph, storyModule, articleData, context, shelfIndex) {
    return validation.catchingContext(`module: ${context.module}`, () => {
        var _a;
        const shelves = [];
        switch (context.module) {
            case "Header": {
                context.titleForNextShelf = mediaAttributes.attributeAsString(storyModule, "editorialCopy");
                break;
            }
            case "TextBlock": {
                const textBlockShelf = createParagraph(objectGraph, storyModule, context);
                if (isSome(textBlockShelf)) {
                    shelves.push(textBlockShelf);
                    context.hasNonFocusableElements = true;
                }
                break;
            }
            case "CollectionLockup": {
                const appListShelf = createAppList(objectGraph, storyModule, context);
                if (isSome(appListShelf)) {
                    shelves.push(appListShelf);
                    context.hasFocusableElements = true;
                }
                break;
            }
            case "InlineImage": {
                const inlineImageShelf = createImage(objectGraph, storyModule, context);
                if (isSome(inlineImageShelf)) {
                    shelves.push(inlineImageShelf);
                    context.hasNonFocusableElements = true;
                }
                break;
            }
            case "AppLockup": {
                const appLockupShelf = createAppLockup(objectGraph, storyModule, context);
                if (isSome(appLockupShelf)) {
                    shelves.push(appLockupShelf);
                    context.hasFocusableElements = true;
                }
                break;
            }
            case "TipBlock": {
                const tipShelf = createTip(objectGraph, storyModule, context);
                if (isSome(tipShelf)) {
                    shelves.push(tipShelf);
                    context.hasNonFocusableElements = true;
                }
                break;
            }
            case "PullQuote": {
                const pullQuoteShelf = createPullQuote(objectGraph, storyModule, context);
                if (isSome(pullQuoteShelf)) {
                    shelves.push(pullQuoteShelf);
                    context.hasNonFocusableElements = true;
                }
                break;
            }
            case "HorizontalRule": {
                const horizontalRuleShelf = createHorizontalRule(objectGraph, storyModule, context);
                if (isSome(horizontalRuleShelf)) {
                    shelves.push(horizontalRuleShelf);
                    context.hasNonFocusableElements = true;
                }
                break;
            }
            case "InlineVideo": {
                const inlineVideoShelf = createVideo(objectGraph, storyModule, context);
                if (isSome(inlineVideoShelf)) {
                    shelves.push(inlineVideoShelf);
                    context.hasFocusableElements = true;
                }
                break;
            }
            case "AppMedia": {
                const appMediaShelf = createAppMedia(objectGraph, storyModule, context);
                if (isSome(appMediaShelf)) {
                    shelves.push(appMediaShelf);
                    context.hasFocusableElements = true;
                }
                break;
            }
            case "LinkBlock": {
                const linkBlockShelf = createLink(objectGraph, storyModule, context);
                if (isSome(linkBlockShelf)) {
                    shelves.push(linkBlockShelf);
                    context.hasFocusableElements = true;
                }
                break;
            }
            case "TextList": {
                const textListShelf = createTextList(objectGraph, storyModule, context);
                if (isSome(textListShelf)) {
                    shelves.push(textListShelf);
                    context.hasNonFocusableElements = true;
                }
                break;
            }
            case "IAPLockup": {
                const iapLockupShelf = createIAPLockup(objectGraph, storyModule, context);
                if (isSome(iapLockupShelf)) {
                    shelves.push(iapLockupShelf);
                    context.hasFocusableElements = true;
                }
                break;
            }
            case "AppMarker": {
                const appMarkerShelf = createAppMarker(objectGraph, storyModule, articleData, context);
                if (isSome(appMarkerShelf)) {
                    shelves.push(appMarkerShelf);
                    context.hasFocusableElements = true;
                }
                break;
            }
            case "StoryList": {
                const storyListShelf = createStoryCards(objectGraph, storyModule, context, shelfIndex);
                if (isSome(storyListShelf)) {
                    shelves.push(storyListShelf);
                    context.hasFocusableElements = true;
                }
                break;
            }
            case "AppEventLockup": {
                const appEventShelf = createAppEventLockup(objectGraph, storyModule, context);
                if (isSome(appEventShelf)) {
                    shelves.push(appEventShelf);
                    context.hasFocusableElements = true;
                }
                break;
            }
            case "OfferItemLockup": {
                const offerItemShelves = createOfferItemLockup(objectGraph, storyModule, context);
                if (isSome(offerItemShelves)) {
                    shelves.push(...offerItemShelves);
                    context.hasFocusableElements = true;
                }
                break;
            }
            default: {
                objectGraph.console.log(`Unknown module: ${context.module}`);
            }
        }
        for (const shelf of shelves) {
            const existingShelfPresentationHints = (_a = shelf.presentationHints) !== null && _a !== void 0 ? _a : {};
            shelf.presentationHints = {
                ...existingShelfPresentationHints,
                isArticleContext: true,
            };
        }
        return shelves;
    });
}
const FULL_WIDTH_MODULES = ["AppLockup", "InlineImage", "InlineVideo", "AppMarker"];
/**
 * Determines whether the provided parameters signifies a full-width article
 * module.
 * @param shelf The shelf in question.
 * @param type The type of article module.
 * @returns Whether or not the given shelf for the article type is full width.
 */
function isArticleShelfFullWidth(objectGraph, shelf, type) {
    if (shelf && type) {
        const itemCount = shelf.items.length;
        if (itemCount > 0 && FULL_WIDTH_MODULES.indexOf(type) !== -1) {
            const lastItem = shelf.items[itemCount - 1];
            switch (shelf.contentType) {
                case "framedArtwork": {
                    const framedArt = lastItem;
                    return framedArt && framedArt.isFullWidth;
                }
                case "framedVideo": {
                    const framedVideo = lastItem;
                    return framedVideo && framedVideo.isFullWidth;
                }
                default: {
                    return true;
                }
            }
        }
    }
    return false;
}
// region Footer Lockup
/**
 * Adds either a `footerLockup` or `arcadeFooterLockup` property on `ArticlePage` model, based on type of article.
 * @param page Page to add footer to if needed.
 * @param articleData Original data of article being rendered.
 * @param context Parse context for article builder.
 */
function addFooterLockupForPageIfNeeded(objectGraph, page, articleData, context) {
    // App Lockup for Articles about single specific app.
    const footerProductData = productDataFromArticle(objectGraph, articleData);
    if (footerProductData) {
        const externalDeepLinkUrl = externalDeepLink.deepLinkUrlFromData(objectGraph, articleData);
        page.footerLockup = productFooterLockupFromData(objectGraph, footerProductData, context, externalDeepLinkUrl);
        return;
    }
    // Arcade Lockup for Acquisition Story for supported platforms
    const isArcadeAcquisitionEI = mediaAttributes.attributeAsBooleanOrFalse(articleData, "isAcquisition");
    const platformSupportsArcadeFooterLockup = objectGraph.host.isiOS || objectGraph.host.isMac;
    const additionalDataIsAvailable = serverData.isDefinedNonNull(context.additionalData);
    if (additionalDataIsAvailable && isArcadeAcquisitionEI && platformSupportsArcadeFooterLockup) {
        const upsellData = arcadeCommon.upsellFromRelationshipOf(objectGraph, articleData);
        page.arcadeFooterLockup = arcadeFooterLockupFromData(objectGraph, upsellData, context);
    }
}
/**
 * Find platform data from editorial item to enhance sharing and display in footer lockup
 * At the moment, only single app editorials get footer lockups and have enhanced sharing.
 *
 * @param editorialItem Item to find footer content for
 * @returns content to display in footer lockup, or null if no content should be displayed
 */
export function productDataFromArticle(objectGraph, editorialItem) {
    const relatedContent = mediaRelationships.relationshipCollection(editorialItem, "card-contents");
    if (relatedContent.length !== 1) {
        return null;
    }
    const contentData = relatedContent[0];
    if (!contentData) {
        return null;
    }
    switch (contentData.type) {
        case "apps":
        case "app-bundles":
            return contentData;
        default:
            return null;
    }
}
/**
 * Creates a footer lockup with a data for a specific app.
 * Cover method over `lockupFromData` to override `offerStyle`.
 *
 * @param data MAPI data to build footer with.
 * @param context Parse context
 * @param externalDeepLinkUrl promotional deep link url to use on the lockup's offer.
 * @returns A new `Lockup` object for footer lockups.
 */
function productFooterLockupFromData(objectGraph, data, context, externalDeepLinkUrl) {
    const lockupOptions = {
        offerStyle: footerLockupOfferStyle(objectGraph),
        metricsOptions: {
            pageInformation: context.metricsPageInformation,
            locationTracker: context.metricsLocationTracker,
        },
        clientIdentifierOverride: context.clientIdentifierOverride,
        externalDeepLinkUrl: externalDeepLinkUrl,
        crossLinkSubtitle: context.crossLinkSubtitle,
        artworkUseCase: 0 /* content.ArtworkUseCase.Default */,
        canDisplayArcadeOfferButton: content.shelfContentTypeCanDisplayArcadeOfferButtons(objectGraph, "smallLockup"),
    };
    return lockups.lockupFromData(objectGraph, data, lockupOptions);
}
/**
 * Creates a footer lockup representing the Arcade subscription service.
 * @param upsellData Contains both editorial and iAP data for Arcade
 * @param context Parse context.
 */
function arcadeFooterLockupFromData(objectGraph, upsellData, context) {
    const metricsOptions = {
        pageInformation: context.metricsPageInformation,
        locationTracker: context.metricsLocationTracker,
    };
    return lockups.arcadeLockupFromData(objectGraph, upsellData, metricsOptions, models.marketingItemContextFromString("editorialItem"), "infer", null);
}
/**
 * Determines the offer style to use for the footer lockup.
 */
function footerLockupOfferStyle(objectGraph) {
    switch (objectGraph.client.deviceType) {
        case "mac":
            return "white";
        default:
            return "infer";
    }
}
// endregion
function createFallbackListShelf(objectGraph, cardMedia) {
    if (cardMedia instanceof models.TodayCardMediaList || cardMedia instanceof models.TodayCardMediaRiver) {
        const fallbackShelf = new models.Shelf("smallLockup");
        fallbackShelf.items = cardMedia.lockups;
        if (objectGraph.client.isWeb) {
            fallbackShelf.presentationHints = {
                ...fallbackShelf.presentationHints,
                isArticleContext: true,
            };
        }
        return fallbackShelf;
    }
    return null;
}
function shareSheetActionFromData(objectGraph, editorialItem, todayCardConfig) {
    const productData = productDataFromArticle(objectGraph, editorialItem);
    /*
     * Determine title
     */
    let title = null;
    const name = content.notesFromData(objectGraph, editorialItem, "name");
    const short = content.notesFromData(objectGraph, editorialItem, "short");
    // Prefer "name: short"
    if (name && short) {
        title = objectGraph.loc
            .string("ShareSheet.TitleSubtitle.Format", "{title}: {subtitle}")
            .replace("{title}", name)
            .replace("{subtitle}", short);
    }
    // Followed by name
    if (!title && name) {
        title = name;
    }
    // Followed by short
    if (!title && short) {
        title = short;
    }
    // Followed by product name
    if (!title && productData) {
        const productTitle = mediaAttributes.attributeAsString(productData, "name");
        const cardDisplayStyle = mediaAttributes.attributeAsString(editorialItem, "cardDisplayStyle");
        switch (cardDisplayStyle) {
            case TodayCardDisplayStyle.GameOfTheDay: {
                title = objectGraph.loc.string("SHARE_SHEET_GAME_OF_DAY_TITLE_FORMAT").replace("{title}", productTitle);
                break;
            }
            case TodayCardDisplayStyle.AppOfTheDay: {
                title = objectGraph.loc.string("SHARE_SHEET_APP_OF_DAY_TITLE_FORMAT").replace("{title}", productTitle);
                break;
            }
            default: {
                objectGraph.console.log(`No title for article with unknown style: ${cardDisplayStyle}`);
                break;
            }
        }
    }
    const url = mediaAttributes.attributeAsString(editorialItem, "url");
    let articleArtwork;
    const cardDisplayStyle = mediaAttributes.attributeAsString(editorialItem, "cardDisplayStyle");
    switch (cardDisplayStyle) {
        case TodayCardDisplayStyle.Grid:
        case TodayCardDisplayStyle.List:
        case TodayCardDisplayStyle.River:
            articleArtwork = artworkBuilder.createArtworkForResource(objectGraph, "resource://ShareCollectionThumbnail", 40, 40);
            break;
        default:
            articleArtwork = null;
            break;
    }
    // Create share sheet model (bail out if unable to do so)
    const shareData = sharing.shareSheetDataForArticle(objectGraph, title, url, null, articleArtwork, editorialItem);
    if (!serverData.isDefinedNonNull(shareData)) {
        return null;
    }
    const activities = sharing.shareSheetActivitiesForArticle(objectGraph, url, todayCardPreviewUrlForTodayCard(objectGraph, editorialItem.id, todayCardConfig), editorialItem.id);
    return new models.ShareSheetAction(shareData, activities);
}
function createShareShelf(objectGraph, shareAction, context, isLastModuleFullWidth) {
    if (!serverData.isDefinedNonNull(shareAction) ||
        objectGraph.client.isVision ||
        preprocessor.GAMES_TARGET ||
        objectGraph.client.isCompanionVisionApp) {
        return null;
    }
    // Create share button
    const shareButton = new models.RoundedButton("share", objectGraph.loc.string("SHARE_STORY"), !isLastModuleFullWidth, shareAction);
    // Add share shelf
    const shareButtonShelf = new models.Shelf("roundedButton");
    shareButtonShelf.items = [shareButton];
    return shareButtonShelf;
}
function createParagraph(objectGraph, module, context) {
    const text = mediaAttributes.attributeAsString(module, "editorialCopy");
    if (!text) {
        return null;
    }
    const paragraph = new models.Paragraph(text, "text/x-apple-as3-nqml", "article");
    // Setup impressions
    addImpressionsFieldsToModel(objectGraph, paragraph, context);
    const shelf = new models.Shelf("paragraph");
    shelf.items = [paragraph];
    return shelf;
}
function createImage(objectGraph, module, context) {
    const displayStyle = mediaAttributes.attributeAsString(module, "inlineImageDisplayType");
    const artworkData = mediaAttributes.attributeAsDictionary(module, "artwork");
    // If the displayStyle is FullWidth want to 'allowTransparency' so that images blend into the page in both
    // light and dark mode. Previously editorial would bake white backgrounds into images they wanted to 'blend'
    // with the page
    const artwork = content.artworkFromApiArtwork(objectGraph, artworkData, {
        useCase: 13 /* content.ArtworkUseCase.ArticleImage */,
        allowingTransparency: displayStyle === "FullWidth" && !objectGraph.client.isVision,
        withJoeColorPlaceholder: objectGraph.client.isVision,
    });
    if (!artwork) {
        return null;
    }
    const frame = new models.FramedArtwork(artwork, false, "text/x-apple-as3-nqml");
    // Get the optional caption
    frame.caption = mediaAttributes.attributeAsString(module, "editorialCopy");
    context.subStyle = displayStyle;
    if (displayStyle) {
        switch (displayStyle) {
            case "BoundingBox": {
                frame.isFullWidth = false;
                frame.hasRoundedCorners = true;
                break;
            }
            case "FullWidth":
            default: {
                frame.isFullWidth = true;
                frame.hasRoundedCorners = false;
                break;
            }
        }
    }
    // Setup impressions
    addImpressionsFieldsToModel(objectGraph, frame, context);
    const shelf = new models.Shelf("framedArtwork");
    shelf.items = [frame];
    return shelf;
}
function createTip(objectGraph, module, context) {
    const artworkData = mediaAttributes.attributeAsDictionary(module, "artwork");
    const artwork = content.artworkFromApiArtwork(objectGraph, artworkData, {
        useCase: 13 /* content.ArtworkUseCase.ArticleImage */,
    });
    if (!artwork) {
        return null;
    }
    const caption = mediaAttributes.attributeAsString(module, "editorialCopy");
    const ordinal = mediaAttributes.attributeAsString(module, "tipNumber");
    // Create the tip image
    const frame = new models.FramedArtwork(artwork, false, "text/x-apple-as3-nqml");
    frame.isFullWidth = false;
    frame.hasRoundedCorners = true;
    frame.caption = caption;
    frame.ordinal = ordinal;
    // Setup impressions
    addImpressionsFieldsToModel(objectGraph, frame, context);
    // Create the shelf
    const shelf = new models.Shelf("framedArtwork");
    shelf.items = [frame];
    return shelf;
}
function createPullQuote(objectGraph, module, context) {
    const text = mediaAttributes.attributeAsString(module, "quote");
    const attribution = mediaAttributes.attributeAsString(module, "quoteAttribution");
    // Get the optional artwork
    const artworkData = mediaAttributes.attributeAsDictionary(module, "artwork");
    const artwork = content.artworkFromApiArtwork(objectGraph, artworkData, {
        useCase: 13 /* content.ArtworkUseCase.ArticleImage */,
    });
    const fullWidth = mediaAttributes.attributeAsString(module, "pullQuoteDisplayType") === "FullWidth";
    // Create the quote
    const quote = new models.Quote(text, attribution, artwork, fullWidth);
    // Setup impressions
    addImpressionsFieldsToModel(objectGraph, quote, context);
    // Create the shelf
    const shelf = new models.Shelf("quote");
    shelf.items = [quote];
    return shelf;
}
function createHorizontalRule(objectGraph, module, context) {
    const lineStyle = mediaAttributes.attributeAsString(module, "lineStyle");
    const fullWidth = mediaAttributes.attributeAsString(module, "displayStyle") === "FullWidth";
    let ruleColor = color.named("defaultLine");
    if (objectGraph.client.isVision && (lineStyle === "Dotted" || lineStyle === "Dashed")) {
        ruleColor = color.white;
    }
    // Parse the customColor from Media API. This can only be a dynamic color.
    const apiColor = mediaAttributes.attributeAsDictionary(module, "customColor");
    const lightColor = color.fromHex(serverData.asString(apiColor, "lightMode"));
    const darkColor = color.fromHex(serverData.asString(apiColor, "darkMode"));
    if (!serverData.isNullOrEmpty(lightColor) && !serverData.isNullOrEmpty(darkColor)) {
        ruleColor = color.dynamicWith(lightColor, darkColor);
    }
    const horizontalRule = new models.HorizontalRule(lineStyle, ruleColor, fullWidth);
    // Create the Shelf
    const shelf = new models.Shelf("horizontalRule");
    shelf.items = [horizontalRule];
    return shelf;
}
function createVideo(objectGraph, module, context) {
    // Get the preview artwork
    const artworkData = mediaAttributes.attributeAsDictionary(module, "video.previewFrame");
    const artwork = content.artworkFromApiArtwork(objectGraph, artworkData, {
        useCase: 13 /* content.ArtworkUseCase.ArticleImage */,
    });
    if (!artwork) {
        return null;
    }
    // Get the video URL
    const videoUrl = mediaAttributes.attributeAsString(module, "video.video");
    if (!videoUrl) {
        return null;
    }
    const videoDisplayType = mediaAttributes.attributeAsString(module, "inlineVideoDisplayType");
    const isFullWidth = videoDisplayType === "FullWidth";
    // Create the video
    const video = new models.Video(videoUrl, artwork, videoDefaults.defaultVideoConfiguration(objectGraph));
    metricsHelpersMedia.addMetricsEventsToVideo(objectGraph, video, {
        pageInformation: context.metricsPageInformation,
        locationTracker: context.metricsLocationTracker,
        id: context.pageId,
    });
    const videoModule = new models.FramedVideo(video, isFullWidth, "text/x-apple-as3-nqml");
    // Get the optional caption
    videoModule.caption = mediaAttributes.attributeAsString(module, "editorialCopy");
    // Setup impressions
    addImpressionsFieldsToModel(objectGraph, videoModule, context);
    // Create the shelf
    const shelf = new models.Shelf("framedVideo");
    shelf.items = [videoModule];
    return shelf;
}
function createAppLockup(objectGraph, module, context) {
    const contentData = contentFromModule(objectGraph, module, context);
    if (!contentData) {
        return null;
    }
    // Shelf to generate. Either lockup, app showcase, or app event shelf
    let shelf = null;
    // If we have an app-events relationship, we want to use this as the priority. This sometimes exists on the
    // AppLockup type, rather than as the AppEventLockup type, so that older clients can still render this
    // item by falling back to the AppLockup type.
    const appEventsDataItems = mediaRelationships.relationshipCollection(module, "app-events");
    if (serverData.isDefinedNonNullNonEmpty(appEventsDataItems)) {
        shelf = appPromotionsShelf.appEventsShelfForArticle(objectGraph, appEventsDataItems, context.metricsPageInformation, context.metricsLocationTracker, context);
        if (serverData.isDefinedNonNull(shelf)) {
            return shelf;
        }
    }
    // Set the display style
    const displayStyle = mediaAttributes.attributeAsString(module, "appLockupSize");
    context.subStyle = displayStyle;
    let shelfStyle;
    let isLockup = false;
    if (displayStyle) {
        switch (displayStyle) {
            case "Small": {
                shelfStyle = "smallLockup";
                isLockup = true;
                break;
            }
            case "Medium": {
                shelfStyle = "mediumLockup";
                isLockup = true;
                break;
            }
            case "Large":
            default: {
                if (objectGraph.client.isWatch ||
                    objectGraph.client.isTV ||
                    objectGraph.client.isVision ||
                    preprocessor.GAMES_TARGET) {
                    // Per design, on watchOS we always show a lockup for app showcases.
                    // Watch App Store treats all lockup sizes the same -- let's pick small.
                    shelfStyle = "smallLockup";
                    isLockup = true;
                }
                else {
                    shelfStyle = "appShowcase";
                }
                break;
            }
        }
    }
    // Determine the deep link URL, if there is one.
    const externalDeepLinkUrl = externalDeepLink.deepLinkUrlFromData(objectGraph, module);
    // Create the appropriate shelf item
    if (isLockup) {
        const lockupShelf = new models.Shelf(shelfStyle);
        const metricsOptions = {
            metricsOptions: {
                pageInformation: context.metricsPageInformation,
                locationTracker: context.metricsLocationTracker,
            },
            clientIdentifierOverride: context.clientIdentifierOverride,
            externalDeepLinkUrl: externalDeepLinkUrl,
            artworkUseCase: content.artworkUseCaseFromShelfStyle(objectGraph, shelfStyle),
        };
        let lockup;
        if (preprocessor.GAMES_TARGET) {
            const shelfID = new PageID(context.pageId).shelfID(module.id);
            lockup = gamesComponentBuilder.makeArticleGameLockup(objectGraph, contentData, shelfID);
        }
        else {
            lockup = lockups.lockupFromData(objectGraph, contentData, metricsOptions);
        }
        if (isNothing(lockup)) {
            return null;
        }
        lockupShelf.items = [lockup];
        shelf = lockupShelf;
    }
    else {
        // On all platforms, the AppLockup platform generates a AppShowcase when display style is large.
        shelf = createAppShowcase(objectGraph, module, context);
    }
    return shelf;
}
function createAppShowcase(objectGraph, module, context) {
    // Create the shelf
    const shelf = new models.Shelf("appShowcase");
    // Parameterize by platform:
    // tvOS populates the `screenshots` field to display alongside video.
    const showcaseHasScreenshots = objectGraph.client.isTV;
    // Only non-tvOS has shelf background color
    const shelfHasBackgroundColor = objectGraph.client.deviceType !== "tv";
    const contentData = contentFromModule(objectGraph, module, context);
    const externalDeepLinkUrl = externalDeepLink.deepLinkUrlFromData(objectGraph, module);
    const lockup = lockups.lockupFromData(objectGraph, contentData, {
        offerStyle: "colored",
        metricsOptions: {
            pageInformation: context.metricsPageInformation,
            locationTracker: context.metricsLocationTracker,
        },
        clientIdentifierOverride: context.clientIdentifierOverride,
        externalDeepLinkUrl: externalDeepLinkUrl,
        crossLinkSubtitle: context.crossLinkSubtitle,
        artworkUseCase: 1 /* content.ArtworkUseCase.LockupIconSmall */,
    });
    const showcase = new models.AppShowcase("large", lockup);
    showcase.description = lockups.subtitleFromData(objectGraph, contentData);
    // Add Video
    // Configure the video for the showcase, if the module demands it.
    let showcaseVideo = null;
    const videoType = mediaAttributes.attributeAsString(module, "appLockupVideo");
    switch (videoType) {
        case "AppTrailer": {
            const allAppVideos = content.videoPreviewsFromData(objectGraph, contentData);
            if (allAppVideos && allAppVideos.length > 0) {
                showcaseVideo = allAppVideos[0];
            }
            break;
        }
        default:
            break;
    }
    if (showcaseVideo) {
        metricsHelpersMedia.addMetricsEventsToVideo(objectGraph, showcaseVideo, {
            pageInformation: context.metricsPageInformation,
            locationTracker: context.metricsLocationTracker,
            id: context.pageId,
        });
        showcase.video = showcaseVideo;
    }
    // Add Screenshots for AppShowcase if necessary
    if (showcaseHasScreenshots) {
        showcase.screenshots = content.screenshotsFromData(objectGraph, contentData, 14 /* content.ArtworkUseCase.ArticleScreenshots */, [content.currentAppPlatform(objectGraph)]);
    }
    // Configure background if necessary.
    if (shelfHasBackgroundColor) {
        shelf.background = {
            type: "color",
            color: appShowcaseBackgroundColor,
        };
    }
    shelf.items = [showcase];
    return shelf;
}
function createIAPLockup(objectGraph, module, context) {
    const contentData = contentFromModule(objectGraph, module, context);
    if (!contentData) {
        return null;
    }
    // Create the lockup
    const lockup = lockups.inAppPurchaseLockupFromData(objectGraph, contentData, {
        metricsOptions: {
            pageInformation: context.metricsPageInformation,
            locationTracker: context.metricsLocationTracker,
        },
        clientIdentifierOverride: context.clientIdentifierOverride,
        artworkUseCase: 1 /* content.ArtworkUseCase.LockupIconSmall */,
    });
    if (!lockup) {
        return null;
    }
    const showcase = new models.InAppPurchaseShowcase(lockup);
    // Create the shelf
    const shelf = new models.Shelf("inAppPurchaseShowcase");
    shelf.background = {
        type: "color",
        color: iAPBackgroundColor,
    };
    shelf.items = [showcase];
    return shelf;
}
function createAppList(objectGraph, module, context) {
    const showOrdinals = mediaAttributes.attributeAsBooleanOrFalse(module, "showOrdinals");
    const ordinalDirection = mediaAttributes.attributeAsString(module, "collectionLockupDisplayType") === "OrdinalDesc"
        ? "descending"
        : "ascending";
    // Set the display style
    const displayStyle = mediaAttributes.attributeAsString(module, "collectionLockupSize");
    context.subStyle = displayStyle;
    let style;
    if (displayStyle) {
        switch (displayStyle) {
            case "Large": {
                style = "largeLockup";
                break;
            }
            case "Medium": {
                style = "mediumLockup";
                break;
            }
            case "Small":
            default: {
                style = "smallLockup";
                break;
            }
        }
    }
    // Construct the lockup options
    const lockupOptions = {
        metricsOptions: {
            pageInformation: context.metricsPageInformation,
            locationTracker: context.metricsLocationTracker,
        },
        clientIdentifierOverride: context.clientIdentifierOverride,
        artworkUseCase: content.artworkUseCaseFromShelfStyle(objectGraph, style),
        canDisplayArcadeOfferButton: content.shelfContentTypeCanDisplayArcadeOfferButtons(objectGraph, style),
    };
    // Check if we have content
    const contents = mediaRelationships.relationshipCollection(module, "contents");
    if (isNothing(contents)) {
        return null;
    }
    let childLockups = [];
    if (preprocessor.GAMES_TARGET) {
        const shelfID = new PageID(context.pageId).shelfID(module.id);
        childLockups = gamesComponentBuilder.makeArticleGameLockups(objectGraph, contents, shelfID);
    }
    else {
        childLockups = lockups.lockupsFromData(objectGraph, contents, {
            includeOrdinals: showOrdinals,
            ordinalDirection: ordinalDirection,
            lockupOptions: lockupOptions,
        });
    }
    if (!childLockups || childLockups.length === 0) {
        return null;
    }
    // Create the shelf
    const shelf = new models.Shelf(style);
    shelf.items = childLockups;
    return shelf;
}
function createAppMedia(objectGraph, module, context) {
    const contentData = contentFromModule(objectGraph, module, context);
    if (!contentData) {
        return null;
    }
    // Set the display style
    const mediaOption = mediaAttributes.attributeAsString(module, "appMediaOption");
    const appMediaPlatform = mediaAttributes.attributeAsString(module, "appMediaPlatform");
    context.subStyle = mediaOption;
    switch (mediaOption) {
        case "Screenshots": {
            let shelf = null;
            // I'm so sorry, but making this split makes the macOS client code infinitely better because we are able
            // to reuse the same product media view and component contract that is used on product page screenshots/trailers.
            // Really, iOS should be reworked such that its module & product page implementation has a single source,
            // but this has serious design obstacles that need to be worked through.
            if (objectGraph.client.isMac) {
                shelf = new models.Shelf("productMedia");
                const productMedia = content.productMediaFromData(objectGraph, contentData, 14 /* content.ArtworkUseCase.ArticleScreenshots */);
                if (serverData.isDefinedNonNull(productMedia) && productMedia.length) {
                    shelf.items = productMedia;
                }
            }
            else {
                shelf = new models.Shelf("screenshots");
                if (serverData.isNull(appMediaPlatform)) {
                    /**
                     * The server did not tell us which app platform to use, so we need to infer based on various keys in
                     * the response. These parameters are only fully baked into product-dv responses, so we we need to do
                     * the more expensive product-dv lookup in order to correctly infer the default screenshots to use for
                     * the shelf.
                     */
                    const screenshots = content.screenshotsFromData(objectGraph, contentData, 14 /* content.ArtworkUseCase.ArticleScreenshots */);
                    if (screenshots && screenshots.length > 0) {
                        shelf.items = [screenshots[0]];
                    }
                }
                else {
                    /**
                     * Server tells us which platform to use -- dictated by `appMediaPlatform`. Selectively do a lookup for
                     * just those screenshots.
                     */
                    const desiredAppPlatform = appPlatformFromAppMediaPlatform(objectGraph, appMediaPlatform);
                    if (desiredAppPlatform) {
                        const screenshots = content.screenshotsFromData(objectGraph, contentData, 14 /* content.ArtworkUseCase.ArticleScreenshots */, [desiredAppPlatform]);
                        if (screenshots && screenshots.length) {
                            shelf.items = [screenshots[0]];
                        }
                    }
                }
            }
            if (serverData.isDefinedNonNull(shelf) && shelf.items.length === 0) {
                return null;
            }
            return shelf;
        }
        case "AppTrailers":
            const trailersShelf = new models.Shelf("framedVideo");
            const videoPreviews = content.videoPreviewsFromData(objectGraph, contentData);
            if (videoPreviews && videoPreviews.length > 0) {
                const video = videoPreviews[0];
                metricsHelpersMedia.addMetricsEventsToVideo(objectGraph, video, {
                    pageInformation: context.metricsPageInformation,
                    locationTracker: context.metricsLocationTracker,
                    id: context.pageId,
                });
                const firstTrailer = new models.FramedVideo(video, false, "text/plain", null, null, true);
                trailersShelf.items = [firstTrailer];
                return trailersShelf;
            }
            else {
                return null;
            }
        default: {
            return null;
        }
    }
}
function createLink(objectGraph, module, context) {
    if (objectGraph.client.isTV || objectGraph.client.isWatch) {
        return null;
    }
    const urlString = mediaAttributes.attributeAsString(module, "url");
    if (!urlString) {
        return null;
    }
    const url = new urls.URL(urlString);
    const linkTitle = mediaAttributes.attributeAsString(module, "urlTitle");
    let text = mediaAttributes.attributeAsString(module, "editorialCopy");
    if (!text) {
        text = url.host;
    }
    const mediaHosts = [
        "itunes.apple.com",
        "apps.apple.com",
        "music.apple.com",
        "books.apple.com",
        "podcasts.apple.com",
        "watch-app.cdn-apple.com",
        "tv.apple.com",
    ];
    let linkPresentationEnabled = false;
    for (const mediaHost of mediaHosts) {
        if (url.host.endsWith(mediaHost)) {
            linkPresentationEnabled = true;
        }
    }
    const action = new models.ExternalUrlAction(urlString);
    metricsHelpersClicks.addClickEventToAction(objectGraph, action, {
        targetType: "link",
        pageInformation: context.metricsPageInformation,
        id: `${context.index}`,
        locationTracker: context.metricsLocationTracker,
    });
    const link = new models.EditorialLink(linkTitle, text, action, linkPresentationEnabled);
    // Setup impressions
    addImpressionsFieldsToModel(objectGraph, link, context);
    const shelf = new models.Shelf("editorialLink");
    shelf.items = [link];
    return shelf;
}
function createTextList(objectGraph, module, context) {
    const listEntries = mediaAttributes.attributeAsArrayOrEmpty(module, "editorialCopy");
    if (!listEntries.length) {
        return null;
    }
    const type = mediaAttributes.attributeAsString(module, "textListDisplayType");
    context.subStyle = type;
    let isBulleted = false;
    switch (type) {
        case "Bulleted": {
            isBulleted = true;
            break;
        }
        default: {
            isBulleted = false;
            break;
        }
    }
    let text;
    if (isBulleted) {
        text = "<ul>";
    }
    else {
        text = "<ol>";
    }
    for (const textEntry of listEntries) {
        const listItemJSONString = JSON.stringify(textEntry);
        // rdar://104446319 - We must use `parse` on our JSON string to convert back to
        // a raw string object as this ensures leading/trailing quotation marks are *not* escaped
        const listItem = JSON.parse(listItemJSONString);
        text = `${text}<li>${listItem}</li>`;
    }
    if (isBulleted) {
        text = `${text}</ul>`;
    }
    else {
        text = `${text}</ol>`;
    }
    const paragraph = new models.Paragraph(text, "text/x-apple-as3-nqml", "article");
    // Setup impressions
    addImpressionsFieldsToModel(objectGraph, paragraph, context);
    const shelf = new models.Shelf("paragraph");
    shelf.items = [paragraph];
    return shelf;
}
function createStoryCards(objectGraph, module, context, shelfIndex) {
    if (objectGraph.client.isVision) {
        const shelfToken = createBaseShelfToken(objectGraph, undefined, module, false, shelfIndex, context.metricsPageInformation, context.metricsLocationTracker);
        const shelf = buildSmallStoryCardShelf(objectGraph, shelfToken);
        shelf.isHorizontal = true;
        return shelf;
    }
    const cards = mediaRelationships.relationshipCollection(module, "contents");
    if (!cards) {
        return null;
    }
    const title = mediaAttributes.attributeAsString(module, "name");
    const subtitle = mediaAttributes.attributeAsString(module, "tagline");
    let shelf = null;
    if (objectGraph.client.isiOS && objectGraph.featureFlags.isEnabled("mini_today_cards_article")) {
        const todayParseContext = new TodayParseContext(context.metricsPageInformation, context.metricsLocationTracker);
        shelf = todayHorizontalCardUtil.shelfForMiniTodayCards(objectGraph, cards, title, subtitle, todayParseContext);
    }
    else {
        const isSmallStoryCardsSupported = objectGraph.host.isiOS || objectGraph.host.isMac || objectGraph.host.isWeb;
        const resolvedContentType = isSmallStoryCardsSupported ? "smallStoryCard" : "todayBrick";
        shelf = todayHorizontalCardUtil.shelfForHorizontalCardItems(objectGraph, cards, resolvedContentType, title, subtitle, context, null);
        if (isSmallStoryCardsSupported) {
            // Only specific small story cards are supported and will crash otherwise, filter those here preemptively.
            // rdar://91965501 (MAS Crashing - Earth Day Landing Page - 4/19)
            if (Array.isArray(shelf.items)) {
                shelf.items = shelf.items.filter((item) => {
                    if (!(item instanceof models.TodayCard)) {
                        return true;
                    }
                    return todayHorizontalCardUtil.isHorizontalCardSupportedForKind(objectGraph, item.media.kind, resolvedContentType);
                });
            }
        }
    }
    return shelf;
}
function createAppEventLockup(objectGraph, module, context) {
    const contentData = contentFromModule(objectGraph, module, context);
    if (!contentData) {
        return null;
    }
    return appPromotionsShelf.appEventsShelfForArticle(objectGraph, [contentData], context.metricsPageInformation, context.metricsLocationTracker, context);
}
function createOfferItemLockup(objectGraph, module, context) {
    if (!objectGraph.client.isiOS) {
        return [];
    }
    const offerItem = mediaRelationships.relationshipData(objectGraph, module, "contents");
    if (serverData.isNullOrEmpty(offerItem)) {
        return null;
    }
    // Offer detail Paragraph
    const offerParagraph = mediaAttributes.attributeAsString(module, "editorialCopy");
    const paragraph = new models.Paragraph(offerParagraph, "text/x-apple-as3-nqml", "article");
    const paragraphShelf = new models.Shelf("paragraph");
    paragraphShelf.items = [paragraph];
    // Winback Offer Card
    const offerItemShelf = appPromotionsShelf.appEventsShelfForArticle(objectGraph, [offerItem], context.metricsPageInformation, context.metricsLocationTracker, context);
    return [paragraphShelf, offerItemShelf];
}
/**
 * Ingests EI canvas modules of form:
 * {
 *      id: <editorial-id>,
 *      type: "editorial-item-shelves",
 *      attributes: {
 *          displayType: "AppMarker",
 *          appMarkerType: <AppMarkerType>
 *      }
 * }
 * to generate an shelf for AppMarker model.
 */
function createAppMarker(objectGraph, appMarkerModule, articleData, context) {
    const markerType = mediaAttributes.attributeAsString(appMarkerModule, "appMarkerType");
    context.subStyle = markerType;
    let shelf = null;
    switch (markerType) {
        case "OSUpgrade":
            shelf = createOSUpgradeClientControlButton(objectGraph, appMarkerModule, context);
            break;
        case "Acquisition":
            shelf = createArcadeShowcase(objectGraph, appMarkerModule, articleData, context);
            break;
        default:
            break;
    }
    return shelf;
}
/**
 * Ingests EI canvas modules of form:
 * {
 *      id: <editorial-id>,
 *      type: "editorial-item-shelves",
 *      attributes: {
 *          displayType: "AppMarker",
 *          appMarkerType: "OSUpgrade"
 *      }
 * }
 * to generate an shelf with an button that links to preferences updates.
 */
function createOSUpgradeClientControlButton(objectGraph, osUpgradeModule, context) {
    const deviceType = objectGraph.client.deviceType;
    if (deviceType !== "mac") {
        return null; // Early exit - Only MAS utilizes OS Upgrade Client Control Button currently.
    }
    const installUpdateUrl = links.osUpdateUrl(deviceType);
    if (installUpdateUrl === null) {
        return null;
    }
    // Action to Preferences
    const openUpdatesAction = new models.ExternalUrlAction(installUpdateUrl);
    // Action to open preferences is configured as `link`
    metricsHelpersClicks.addClickEventToAction(objectGraph, openUpdatesAction, {
        targetType: "link",
        pageInformation: context.metricsPageInformation,
        id: `${context.index}`,
        locationTracker: context.metricsLocationTracker,
    });
    // Shelf model
    const upgradeControlText = objectGraph.loc.string("CLIENT_CONTROL_OS_UPGRADE_TITLE", "CHECK FOR UPDATE");
    const upgradeControl = new models.ClientControlButton(upgradeControlText, openUpdatesAction);
    // Add impressions
    addImpressionsFieldsToModel(objectGraph, upgradeControl, context);
    const shelf = new models.Shelf("clientControlButton");
    shelf.items = [upgradeControl];
    return shelf;
}
/**
 * Ingests EI canvas modules of form:
 * {
 *      id: <editorial-id>,
 *      type: "editorial-item-shelves",
 * }
 * with additional data:
 * - Upsell data on `context.additionalData`
 * - Icon Artwork data (iOS only) on `context.additionalData`
 *
 * to generate an shelf that promotes Arcade service.
 *
 * @param arcadeShowcaseModule Arcade showcase module
 * @param articleData The data backing the article containing the module. Used for top-level relationship.
 * @param context Parse context for this page parsing. This context contains the additional requirements data.
 */
function createArcadeShowcase(objectGraph, arcadeShowcaseModule, articleData, context) {
    const supportedOnPlatform = objectGraph.host.isiOS || objectGraph.host.isMac || objectGraph.client.isVision;
    if (!supportedOnPlatform) {
        return null;
    }
    // Default to upsell on relation, falling back to upsell that may have been fetched separately for orphaned acquisition modules.
    let upsellData = arcadeCommon.upsellFromRelationshipOf(objectGraph, articleData);
    if (!upsellData && context.additionalData) {
        const upsellResponse = context.additionalData.get("upsellForNonacquisitionCanvas");
        upsellData = arcadeCommon.upsellFromContentsOfUpsellResponse(objectGraph, upsellResponse);
    }
    if (!serverData.isDefinedNonNull(upsellData)) {
        return null;
    }
    const baseMetricsOptions = {
        pageInformation: context.metricsPageInformation,
        locationTracker: context.metricsLocationTracker,
    };
    // Flow to See All games if subscribed
    const subscribedAction = arcadeCommon.openArcadeMainAction(objectGraph, context.metricsPageInformation, context.metricsLocationTracker, objectGraph.client.isVision);
    if (preprocessor.GAMES_TARGET) {
        subscribedAction.title = objectGraph.loc.string("OfferButton.Arcade.Title.Explore");
    }
    else {
        subscribedAction.title = objectGraph.loc.string("ARCADE_ACTION_TITLE_EXPLORE", "EXPLORE");
    }
    // Flow to Arcade Subscribe page if unsubscribed.
    let unsubscribedAction;
    const unsubscribedActionTitle = breakoutsCommon.callToActionLabelFromData(objectGraph, upsellData.marketingItemData);
    if (serverData.isDefinedNonNullNonEmpty(unsubscribedActionTitle)) {
        // We support an inline offer here instead, when the pricing token is there.
        unsubscribedAction = arcadeUpsell.arcadeOfferButtonActionFromData(objectGraph, upsellData.marketingItemData, models.marketingItemContextFromString("editorialItemCanvas"), baseMetricsOptions);
        if (serverData.isDefinedNonNull(unsubscribedAction)) {
            unsubscribedAction.title = unsubscribedActionTitle;
        }
    }
    else {
        // If Upsell EI is misconfigured and missing `breakoutCallToActionLabel`, default to opening Arcade app for unsubscribed state.
        unsubscribedAction = arcadeCommon.openArcadeMainAction(objectGraph, context.metricsPageInformation, context.metricsLocationTracker, objectGraph.client.isVision);
        if (preprocessor.GAMES_TARGET) {
            unsubscribedAction.title = objectGraph.loc.string("OfferButton.Arcade.Title.Explore");
        }
        else {
            unsubscribedAction.title = objectGraph.loc.string("ARCADE_ACTION_TITLE_EXPLORE", "EXPLORE");
        }
    }
    const arcadeShowcase = new models.ArcadeShowcase(unsubscribedAction, subscribedAction);
    const unsubscribedDescription = arcadeUpsell.descriptionFromData(objectGraph, upsellData.marketingItemData);
    arcadeShowcase.unsubscribedDescription = unsubscribedDescription;
    const offerDisplayProperties = new models.OfferDisplayProperties("arcade", objectGraph.bag.arcadeAppAdamId, null, "colored", null, "dark", null, null, null, null, null, null, null, null, null, null, null, null, objectGraph.bag.arcadeProductFamilyId);
    if (preprocessor.GAMES_TARGET) {
        offerDisplayProperties.titles["subscribed"] = objectGraph.loc.string("OfferButton.Arcade.Title.Explore");
    }
    else {
        offerDisplayProperties.titles["subscribed"] = objectGraph.loc.string("ARCADE_ACTION_TITLE_EXPLORE", "EXPLORE");
    }
    arcadeShowcase.offerDisplayProperties = offerDisplayProperties;
    const showcaseMetricsOptions = {
        ...baseMetricsOptions,
        targetType: "arcadeShowcase",
        title: unsubscribedActionTitle,
        id: arcadeShowcaseModule.id,
        kind: "arcadeShowcase",
        softwareType: null,
        displaysArcadeUpsell: true,
    };
    metricsHelpersImpressions.addImpressionFields(objectGraph, arcadeShowcase, showcaseMetricsOptions);
    // Build Artwork for iOS only
    if (objectGraph.host.isiOS || objectGraph.client.isVision) {
        // Context should have additional data to source icons.
        if (serverData.isNull(context.additionalData)) {
            return null;
        }
        const iconResponse = context.additionalData.get("arcadeIcons");
        if (serverData.isDefinedNonNullNonEmpty(iconResponse)) {
            const iconMetricsOptions = {
                pageInformation: context.metricsPageInformation,
                locationTracker: context.metricsLocationTracker,
            };
            const iconsDataCollection = mediaDataStructure.dataCollectionFromResultsListContainer(iconResponse);
            arcadeShowcase.iconArtworks = content.impressionableAppIconsFromDataCollection(objectGraph, iconsDataCollection, iconMetricsOptions, {
                useCase: 2 /* content.ArtworkUseCase.LockupIconMedium */,
            });
        }
    }
    const shelf = new models.Shelf("arcadeShowcase");
    shelf.items = [arcadeShowcase];
    const shelfHasBackgroundColor = objectGraph.host.isiOS || objectGraph.client.isVision;
    if (shelfHasBackgroundColor) {
        shelf.background = {
            type: "color",
            color: arcadeShowcaseShelfBackgroundColor,
        };
    }
    return shelf;
}
// endregion
function contentFromModule(objectGraph, module, context) {
    const contents = mediaRelationships.relationshipData(objectGraph, module, "contents");
    if (!contents) {
        return null;
    }
    return contents;
}
function addImpressionsFieldsToModel(objectGraph, model, context, impressionData) {
    if (!model) {
        return;
    }
    let impressionType = context.module;
    if (context.subStyle) {
        impressionType = impressionType + "_" + context.subStyle;
    }
    if (serverData.isNull(impressionData)) {
        impressionData = {
            id: `${context.index}`,
            impressionIndex: context.index,
            idType: "sequential",
            impressionType: impressionType,
            kind: "iosModule",
        };
    }
    model.impressionMetrics = new models.ImpressionMetrics(metricsHelpersUtil.sanitizedMetricsDictionary(impressionData));
}
//# sourceMappingURL=article.js.map