import { isNothing, isSome } from "@jet/environment";
import * as models from "../../../api/models";
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 mediaDataStructure from "../../../foundation/media/data-structure";
import * as mediaRelationship from "../../../foundation/media/relationships";
import { Parameters, Path, Protocol } from "../../../foundation/network/url-constants";
import * as urls from "../../../foundation/network/urls";
import * as appEvents from "../../app-promotions/app-event";
import * as appPromotionsCommon from "../../app-promotions/app-promotions-common";
import { pageRouter } from "../../builders/routing";
import * as legacyArtwork from "../../content/artwork/legacy-artwork";
import * as contentAttributes from "../../content/attributes";
import * as content from "../../content/content";
import * as externalDeepLink from "../../linking/external-deep-link";
import * as lockups from "../../lockups/lockups";
import * as metricsHelpersClicks from "../../metrics/helpers/clicks";
import * as metricsHelpersLocation from "../../metrics/helpers/location";
import * as onDevicePersonalization from "../../personalization/on-device-personalization";
import * as productPageVariants from "../../product-page/product-page-variants";
import * as refresh from "../../refresh/page-refresh-controller";
import * as mediaRequestUtils from "../../builders/url-mapping-utils";
import { defaultTodayCardConfiguration, lockupsForRelatedContent } from "../../today/today-card-util";
import { isLockupShelf, } from "../grouping-types";
import { categoryArtworkData } from "../../categories";
import { hrefToRoutableUrl } from "../../builders/url-mapping";
import { clientIdentifierForEditorialContextInData } from "../../lockups/editorial-context";
import { areAppTagsEnabled } from "../../util/app-tags-util";
import { AppEventsAttributes } from "../../../gameservicesui/src/foundation/media-api/requests/recommendation-request-types";
/**
 * Create the base 2 requirements to start parsing a grouping page shelf. These include the base shelf token and the
 * base metrics options
 *
 * @param objectGraph The App Store dependency graph for making native calls and viewing properties
 * @param mediaApiData The media api data for this specific shelf
 * @param groupingParseContext The grouping page parsing context.
 */
export function createBaseShelfRequirements(objectGraph, mediaApiData, groupingParseContext) {
    const featuredContentId = mediaAttributes.attributeAsNumber(mediaApiData, "editorialElementKind");
    // Populate the gamesFilter, if it is provided.
    let gamesFilter = null;
    const rawGamesFilter = mediaAttributes.attributeAsString(mediaApiData, "gamesFilter");
    switch (rawGamesFilter) {
        case "arcade":
        case "nonArcade":
        case "all":
            gamesFilter = rawGamesFilter;
            break;
        default:
            if (featuredContentId === 495 /* FeaturedContentID.AppStore_PopularWithYourFriendsMarker */ ||
                featuredContentId === 500 /* FeaturedContentID.AppStore_ContinuePlayingMarker */) {
                gamesFilter = "arcade"; // Defaulting to Arcade because this param won't be passed until 20I.
            }
            break;
    }
    // Build Shelf eyebrow, title, and subtitle
    let eyebrow = null;
    let title = mediaAttributes.attributeAsString(mediaApiData, shelfTitleAttributePathForFeaturedContentId(objectGraph, featuredContentId));
    let titleArtwork = null;
    let badges = null;
    let subtitle = mediaAttributes.attributeAsString(mediaApiData, "tagline");
    const shelfHeaderConfiguration = {};
    // 'Similar To' Personalised Shelf
    // Check if personalised shelf has a badge-content, if it's got valid content, and the clients support eyebrows and title artwork:
    // - Reco shelf title becomes the eyebrow
    // - Featured App in badge-content becomes the shelf title, and we add the icon as title artwork
    // - No subtitle should be shown
    // - Flag to use the eyebrown name for metrics
    let wantsEyebrowNameForMetrics = false;
    const badgeContent = mediaRelationship.relationshipCollection(mediaApiData, "badge-content")[0];
    if (featuredContentId === 476 /* FeaturedContentID.AppStore_PersonalizedShelfMarker */ &&
        serverData.isDefinedNonNullNonEmpty(badgeContent)) {
        eyebrow = objectGraph.loc.uppercased(mediaAttributes.attributeAsString(mediaApiData, shelfTitleAttributePathForFeaturedContentId(objectGraph, featuredContentId)));
        subtitle = null;
        const dataType = badgeContent.type;
        if (dataType === "collections") {
            title = content.notesFromData(objectGraph, badgeContent, "name");
            const artworkData = categoryArtworkData(objectGraph, badgeContent, false);
            // Reconfigure eyebrow text color only if there is eyebrow artwork.
            if (isSome(artworkData)) {
                titleArtwork = content.artworkFromApiArtwork(objectGraph, artworkData, {
                    useCase: 1 /* content.ArtworkUseCase.LockupIconSmall */,
                    style: "unadorned",
                });
                const eyebrowColor = { type: "named", name: "secondaryText" };
                shelfHeaderConfiguration.eyebrowColor = eyebrowColor;
            }
            badges = { forYou: true };
        }
        else {
            wantsEyebrowNameForMetrics = true;
            title = mediaAttributes.attributeAsString(badgeContent, shelfTitleAttributePathForFeaturedContentId(objectGraph, featuredContentId));
            titleArtwork = content.iconFromData(objectGraph, badgeContent, {
                useCase: 1 /* content.ArtworkUseCase.LockupIconSmall */,
            });
        }
    }
    const shelfToken = {
        featuredContentId,
        id: serverData.asString(mediaApiData, "id"),
        presentationHints: {},
        metricsPageInformation: groupingParseContext.metricsPageInformation,
        metricsLocationTracker: groupingParseContext.metricsLocationTracker,
        pageGenreId: groupingParseContext.pageGenreId,
        featuredContentData: mediaApiData,
        title: title,
        subtitle: subtitle,
        eyebrow: eyebrow,
        titleArtwork: titleArtwork,
        shelfHeaderConfiguration: shelfHeaderConfiguration,
        shouldFilter: false,
        gamesFilter: gamesFilter,
        remainingItems: [],
        isFirstRender: true,
        isDeferring: false,
        showOrdinals: false,
        hasExistingContent: false,
        showingPlaceholders: false,
        ordinalIndex: 1,
        isSearchLandingPage: groupingParseContext.isSearchLandingPage,
        isArcadePage: groupingParseContext === null || groupingParseContext === void 0 ? void 0 : groupingParseContext.isArcadePage,
    };
    const shelfMetricsOptions = {
        id: shelfToken.id,
        kind: null,
        softwareType: serverData.asBooleanOrFalse(groupingParseContext === null || groupingParseContext === void 0 ? void 0 : groupingParseContext.isArcadePage) ? "Arcade" : null,
        targetType: "swoosh",
        title: wantsEyebrowNameForMetrics ? shelfToken.eyebrow : shelfToken.title,
        badges: badges,
        pageInformation: groupingParseContext.metricsPageInformation,
        locationTracker: groupingParseContext.metricsLocationTracker,
        idType: "its_contentId",
        fcKind: featuredContentId,
        recoMetricsData: recoMetricsDataForFCData(objectGraph, mediaApiData),
    };
    return {
        shelfToken: shelfToken,
        metricsOptions: shelfMetricsOptions,
    };
}
// endregion
// region Shelf Tokens
/**
 * Whether we should defer building the rest of a shelf given a shelf token
 * @param token
 */
export function shouldDefer(token) {
    return token && token.isDeferring && token.isFirstRender;
}
/**
 * Due to upstream oddities, we get the shelf title in the form of `title` for these markers. These are
 * unique markers in that they are not filled by reco.
 *
 * For personalization markers, they are programmed in DJ with the name field hidden, and it is filled in by
 * Reco. Without wanting to expose loc strings to that name field, we unfortunately have to use this other
 * attribute key.
 *
 * This will all go away once these markers are replaced with proper Reco equivalents (i.e. when popular-
 * with-your-friends and suggested-friends move to Reco).
 */
export function shelfTitleAttributePathForFeaturedContentId(objectGraph, id) {
    switch (id) {
        case 548 /* FeaturedContentID.AppStore_GameCenterActivityFeedMarker */:
        case 495 /* FeaturedContentID.AppStore_PopularWithYourFriendsMarker */:
        case 496 /* FeaturedContentID.AppStore_SuggestedFriendsMarker */:
            return "title";
        default:
            return "name";
    }
}
/**
 * Returns the URL schema for grouping shelves that may need to fetch additional content.
 * Grouping-Related builders can extend on this scheme if needed, e.g. query param on Continue Playing shelves.
 * @param token Token to encode in URL for subsequent fetch.
 */
export function groupingShelfUrl(token) {
    let shelfUrl = new urls.URL()
        .set("protocol", Protocol.internal)
        .append("pathname", Path.grouping)
        .append("pathname", Path.shelf)
        .append("pathname", encodeURIComponent(JSON.stringify(token)))
        .param(Parameters.groupingFeaturedContentId, `${token.featuredContentId}`);
    if (isSome(token.nativeGroupingShelfId)) {
        shelfUrl = shelfUrl.param(Parameters.nativeGroupingShelfId, `${token.nativeGroupingShelfId}`);
    }
    return shelfUrl.build();
}
/**
 * Configure `url` on a standard grouping shelf if it needs to fetch more content.
 * @param shelf Shelf to add url to.
 * @param token Token tode encode in URL for subsequent fetch.
 */
export function createShelfTokenUrlIfNecessaryForShelf(objectGraph, shelf, token) {
    if (serverData.isNullOrEmpty(token)) {
        return null;
    }
    // Web does not curently support pagination via "fetch more", so shelf token URLs are not needed.
    // Note: removing these URLs for web reduces view model size by ~45%.
    if (objectGraph.client.isWeb) {
        return null;
    }
    // Ensure the token has a `shelfStyle` so we can construct an empty shelf in the case of a hydration error,
    // or the shelf being empty upon hydration.
    if (serverData.isNull(token.shelfStyle)) {
        token.shelfStyle = shelf.contentType;
    }
    const hasNonPlaceholderItems = shelf.contentType !== "placeholder" && serverData.isDefinedNonNullNonEmpty(shelf.items);
    token.hasExistingContent = token.hasExistingContent || (hasNonPlaceholderItems && token.isFirstRender);
    const shouldAddFirstRenderUrl = token.remainingItems.length || token.recommendationsHref || token.onDeviceRecommendationsUseCase;
    if (shouldAddFirstRenderUrl && token.isFirstRender) {
        return groupingShelfUrl(token);
    }
    else {
        return null;
    }
}
/**
 * Updates a shelf URL based on the provided token.
 * @param shelf Shelf to add url to.
 * @param token Token to encode in the url.
 */
export function updateShelfUrlWithNewToken(objectGraph, shelf, token) {
    const originalShelfUrl = urls.URL.from(shelf.url);
    const updatedUrl = urls.URL.from(createShelfTokenUrlIfNecessaryForShelf(objectGraph, shelf, token));
    // Add missing query params to the updated URL from the original.
    for (const key of Object.keys(originalShelfUrl.query)) {
        if (serverData.isNull(updatedUrl.query[key])) {
            updatedUrl.query[key] = originalShelfUrl.query[key];
        }
    }
    // Finally, update the shelf's URL.
    shelf.url = updatedUrl.build();
}
/**
 * From a grouping shelf token determine the list of unhydrated MAPI data to fetch
 * this will also handle the case were whe need to fetch additional relationship data
 *
 * This method will also hoist up ids of content needed to be fetched from an unhydrated relationship if needed. For <rdar://problem/42797176>.
 *
 * This is necessary to accommodate some MAPI objects which have a nested relationship that isn't hydrated and cannot
 * be hydrated by re-fetching the content at parent ID.
 *
 * @param token The shelfToken for the shelf we're fetching data for
 */
export function unhydratedRemainingItemsFromShelfToken(objectGraph, token) {
    var _a;
    const shouldFetchRelationshipItems = ((_a = token.relationshipToFetch) === null || _a === void 0 ? void 0 : _a.length) > 0;
    let remainingItems = token.remainingItems;
    if (shouldFetchRelationshipItems) {
        remainingItems = token.remainingItems.map((remainingItem) => {
            return mediaRelationship.relationshipData(objectGraph, remainingItem, token.relationshipToFetch);
        });
    }
    return remainingItems;
}
/**
 * Given a MAPI response for a list of unhydrated shelf items, and the original grouping shelf token determine
 * the list of hydrated MAPI data. This also handles the case where we need to hoist up relationship data
 *
 * If ids to be fetched were hoisted from nested relationships, replace the original unhydrated nested
 * relationship on parent with hydrated data.
 *
 * @param token
 * @param mediaApiData
 */
export function hydratedRemainingItemsForShelfTokenFromMediaApiData(objectGraph, token, mediaApiData) {
    var _a;
    const didFetchRelationshipItems = ((_a = token.relationshipToFetch) === null || _a === void 0 ? void 0 : _a.length) > 0;
    let hyrdatedItems = mediaDataStructure.dataCollectionFromDataContainer(mediaApiData);
    if (didFetchRelationshipItems) {
        const dataMapping = {};
        for (const dataItem of mediaApiData.data) {
            dataMapping[dataItem.id] = dataItem;
        }
        hyrdatedItems = [];
        for (const remainingItem of token.remainingItems) {
            const unhydratedRelationshipData = mediaRelationship.relationshipData(objectGraph, remainingItem, token.relationshipToFetch);
            if (serverData.isDefinedNonNullNonEmpty(unhydratedRelationshipData)) {
                remainingItem.relationships[token.relationshipToFetch].data = [
                    dataMapping[unhydratedRelationshipData.id],
                ];
            }
            hyrdatedItems.push(remainingItem);
        }
    }
    return hyrdatedItems;
}
/**
 * Deletes all remainingItems that have been requested for hydration from the shelfToken.
 *
 * @param shelfToken
 * @param requestedItems
 */
export function flushRequestedItemsFromShelfToken(shelfToken, requestedItemIds) {
    shelfToken.remainingItems = shelfToken.remainingItems.filter((remainingItem) => {
        return !requestedItemIds.has(remainingItem.id);
    });
}
// endregion
// region Shelf Requests
/**
 * Generates a media API request for fetching a shelf's data
 * @param objectGraph
 * @param {URL} url The URL of the page being requested
 * @param {Parameters} parameters The parameters that were extracted from the URL
 * @returns {Request} A media API request
 */
export function generateShelfRequest(objectGraph, token, parameters) {
    var _a;
    // Whether or not token is for fetching additional items that were unhydated.
    // These tokens are used when a server returned explicit IDs for the contents of shelves, and some (or all) of those IDs were fully unhydrated.
    const isFetchingAdditionalContent = serverData.isDefinedNonNullNonEmpty(token.remainingItems);
    // Whether or not token is for fetching personalized recommendations.
    // Recommendation shelves can sometimes return with no IDs provided within its shelf. We are expected to use the `recommendationsHref` to fetch in this case.
    // Note that server can sometimes choose to prepopulate IDs for personalized shelves, even if `recommendationsHref` is present.
    const isPersonalizedRefetch = !isFetchingAdditionalContent && ((_a = token.recommendationsHref) === null || _a === void 0 ? void 0 : _a.length) > 0;
    if (isFetchingAdditionalContent) {
        // Remaining items to fetch, since they were unhydrated in original page response.
        const remainingItemsToFetch = unhydratedRemainingItemsFromShelfToken(objectGraph, token);
        // MAPI Request
        const mediaApiRequest = new mediaDataFetching.Request(objectGraph, remainingItemsToFetch, true);
        productPageVariants.addVariantParametersToRequestForItems(objectGraph, mediaApiRequest, remainingItemsToFetch);
        prepareGroupingShelfRequest(objectGraph, mediaApiRequest);
        return mediaApiRequest;
    }
    else if (isPersonalizedRefetch) {
        const mediaApiRequest = new mediaDataFetching.Request(objectGraph, token.recommendationsHref).includingAgeRestrictions();
        prepareGroupingShelfRequest(objectGraph, mediaApiRequest);
        if (appPromotionsCommon.appEventsAreEnabled(objectGraph)) {
            mediaApiRequest.enablingFeature("appEvents");
            mediaApiRequest.includingMetaKeys("editorial-elements:contents", ["personalizationData", "cppData"]);
            mediaApiRequest.includingScopedAttributes("app-events", AppEventsAttributes);
            mediaApiRequest.includingScopedRelationships("app-events", ["app"]);
        }
        if (appPromotionsCommon.appContingentItemsAreEnabled(objectGraph)) {
            mediaApiRequest.enablingFeature("contingentItems");
            mediaRequestUtils.configureContingentItemsForGroupingRequest(mediaApiRequest);
        }
        if (appPromotionsCommon.appOfferItemsAreEnabled(objectGraph)) {
            mediaApiRequest.enablingFeature("offerItems");
            mediaRequestUtils.configureOfferItemsForMediaRequest(mediaApiRequest);
        }
        if (areAppTagsEnabled(objectGraph, "grouping")) {
            mediaRequestUtils.configureTagsForMediaRequest(mediaApiRequest);
        }
        return mediaApiRequest;
    }
    return null;
}
/**
 * Modify request for a items fetched for grouping shelf items. (i.e. individual IDs).
 * This should be ideally be in grouping builder's `prepareRequest`, but that would change how url-driven requests are configured.
 * @param objectGraph
 * @param request Request to modify.
 */
export function prepareGroupingShelfRequest(objectGraph, request) {
    request
        .includingAdditionalPlatforms(mediaDataFetching.defaultAdditionalPlatformsForClient(objectGraph))
        .includingRelationshipsForUpsell(true)
        .includingMacOSCompatibleIOSAppsWhenSupported(true)
        .usingCustomAttributes(productPageVariants.shouldFetchCustomAttributes(objectGraph));
    let attributes = ["editorialArtwork", "editorialVideo", "minimumOSVersion"];
    if (request.includesResourceType("app-events") && appPromotionsCommon.appEventsAreEnabled(objectGraph)) {
        request.enablingFeature("appEvents");
        request.includingMetaKeys("editorial-elements:contents", ["personalizationData", "cppData"]);
        request.includingScopedAttributes("app-events", AppEventsAttributes);
        request.includingScopedRelationships("app-events", ["app"]);
    }
    if (request.includesResourceType("contingent-items") &&
        appPromotionsCommon.appContingentItemsAreEnabled(objectGraph)) {
        request.enablingFeature("contingentItems");
        mediaRequestUtils.configureContingentItemsForGroupingRequest(request);
        attributes = [];
    }
    if (request.includesResourceType("offer-items") && appPromotionsCommon.appOfferItemsAreEnabled(objectGraph)) {
        request.enablingFeature("offerItems");
        mediaRequestUtils.configureOfferItemsForMediaRequest(request);
        attributes = [];
    }
    if (request.includesResourceType("apps") || request.includesResourceType("app-events")) {
        attributes = attributes.concat("screenshotsByType", "videoPreviewsByType", "expectedReleaseDateDisplayFormat");
    }
    if (areAppTagsEnabled(objectGraph, "grouping")) {
        mediaRequestUtils.configureTagsForMediaRequest(request);
    }
    request.includingAttributes(attributes);
}
// endregion
// region Metrics
export function recoMetricsDataForFCData(objectGraph, mediaApiData) {
    const featuredContentId = mediaAttributes.attributeAsNumber(mediaApiData, "editorialElementKind");
    switch (featuredContentId) {
        // All of these kinds just require walking their children
        case 425 /* FeaturedContentID.AppStore_GenreStack */:
        case 415 /* FeaturedContentID.AppStore_HeroList */:
        case 416 /* FeaturedContentID.AppStore_Hero */:
        case 417 /* FeaturedContentID.AppStore_CustomHero */:
        case 501 /* FeaturedContentID.AppStore_PersonalizedHeroMarker */:
        case 258 /* FeaturedContentID.Sundance_Flowcase */:
        case 421 /* FeaturedContentID.AppStore_BrickRow */:
        case 422 /* FeaturedContentID.AppStore_Brick */:
        case 423 /* FeaturedContentID.AppStore_CustomBrick */:
        case 261 /* FeaturedContentID.Sundance_BrickRow */:
        case 584 /* FeaturedContentID.AppStore_TagsBrick */:
        case 587 /* FeaturedContentID.AppStore_PersonalizedTagsBrick */: {
            const childrenRelationship = mediaRelationship.relationship(mediaApiData, "children");
            return mediaDataStructure.metricsFromMediaApiObject(childrenRelationship);
        }
        case 437 /* FeaturedContentID.AppStore_LinkList */:
        case 265 /* FeaturedContentID.Sundance_LinkList */: {
            const contentRelationship = mediaRelationship.relationship(mediaApiData, "children");
            const textLinks = mediaAttributes.attributeAsArrayOrEmpty(mediaApiData, "links");
            if (serverData.isDefinedNonNullNonEmpty(contentRelationship)) {
                return mediaDataStructure.metricsFromMediaApiObject(contentRelationship);
            }
            else if (serverData.isDefinedNonNullNonEmpty(textLinks)) {
                return mediaDataStructure.metricsFromMediaApiObject(mediaApiData);
            }
            return null;
        }
        case 414 /* FeaturedContentID.AppStore_TabRoot */:
        case 424 /* FeaturedContentID.AppStore_ChartSet */:
        case 566 /* FeaturedContentID.AppStore_ArcadeDownloadPackMarker */: {
            return null; // unapplicable fckinds
        }
        default: {
            if (isLockupShelf(featuredContentId)) {
                let childrenRelationship = mediaRelationship.relationship(mediaApiData, "contents");
                if (serverData.isNull(childrenRelationship)) {
                    return null;
                }
                const contentItems = childrenRelationship.data;
                if (!contentItems || contentItems.length === 0) {
                    childrenRelationship = mediaRelationship.relationship(mediaApiData, "children");
                }
                return mediaDataStructure.metricsFromMediaApiObject(childrenRelationship);
            }
            else {
                objectGraph.console.warn("Unknown featured content ID:", featuredContentId);
                return null;
            }
        }
    }
}
// endregion
// region Artwork
/**
 * Creating an artwork model with the crop required for a grouping page piece of art
 */
export function groupingArtworkFromApiArtwork(objectGraph, artworkData, options) {
    const artwork = content.artworkFromApiArtwork(objectGraph, artworkData, options);
    if (artwork) {
        artwork.crop = "sr";
    }
    return artwork;
}
export function artworkFromFC(objectGraph, node, width, height, options) {
    const artworkData = mediaAttributes.attributeAsDictionary(node, "artwork");
    if (artworkData instanceof Array) {
        const artwork = legacyArtwork.closestArtworkMatchingSize(objectGraph, artworkData, width, height);
        artwork.crop = "bb";
        return artwork;
    }
    else if (artworkData != null) {
        return groupingArtworkFromApiArtwork(objectGraph, artworkData, options);
    }
    return null;
}
export function artworkForTags(objectGraph, node, width, height, options, metricsOptions) {
    const lockupData = serverData.asArrayOrEmpty(node.meta, "associations.apps.data");
    const artworks = [];
    if (isSome(lockupData)) {
        for (const lockup of lockupData) {
            const lockupOptions = {
                artworkUseCase: 1 /* content.ArtworkUseCase.LockupIconSmall */,
                metricsOptions: metricsOptions,
                useJoeColorIconPlaceholder: true,
                joeColorPlaceholderSelectionLogic: content.bestJoeColorPlaceholderSelectionLogic,
            };
            const lockupFromData = lockups.lockupFromData(objectGraph, lockup, lockupOptions);
            const lockupIcon = lockupFromData === null || lockupFromData === void 0 ? void 0 : lockupFromData.icon;
            if (isSome(lockupIcon)) {
                artworks.push(lockupIcon);
            }
        }
    }
    return artworks;
}
// endregion
// region FC Metadata
export function metadataForFCData(objectGraph, fcData, token, shouldPersonalizeContent, personalizationDataContainer, metricsOptions, context, unavailableCallback) {
    var _a, _b, _c;
    const callUnavailable = function (contentData) {
        if (unavailableCallback) {
            unavailableCallback();
        }
        else {
            token === null || token === void 0 ? void 0 : token.remainingItems.push(contentData);
        }
    };
    const isLinkNode = ((_a = serverData.asString(fcData, "url")) === null || _a === void 0 ? void 0 : _a.length) > 0;
    const containsLinkNode = ((_b = mediaAttributes.attributeAsString(fcData, "link.url")) === null || _b === void 0 ? void 0 : _b.length) > 0;
    const isContentNodeUsingPrimaryContent = mediaRelationship.hasRelationship(fcData, "primary-content", false);
    const isContentNode = mediaRelationship.hasRelationship(fcData, "contents", false) || isContentNodeUsingPrimaryContent;
    // Define whether data is a Category Grouping kind.
    let isCategoryGroupingKind = mediaAttributes.attributeAsString(fcData, "kind") === "CategoryGrouping";
    if (containsLinkNode || isLinkNode) {
        return metadataForLink(objectGraph, fcData, token, metricsOptions, unavailableCallback);
    }
    else if (isContentNode) {
        let contentData;
        // Define category grouping content to set when content is a Category Grouping kind.
        let categoryGroupingContent;
        let personalizedDataResult;
        if (shouldPersonalizeContent && !isContentNodeUsingPrimaryContent) {
            const contentDataItems = mediaRelationship.relationshipCollection(fcData, "contents");
            personalizedDataResult = onDevicePersonalization.personalizeDataItems(objectGraph, "groupingCommon", contentDataItems, true, personalizationDataContainer, false, 1);
            const personalizedContentDataItems = personalizedDataResult.personalizedData;
            if (personalizedContentDataItems.length === 0) {
                return null;
            }
            contentData = personalizedContentDataItems[0];
        }
        else {
            contentData = isContentNodeUsingPrimaryContent
                ? mediaRelationship.relationshipData(objectGraph, fcData, "primary-content")
                : mediaRelationship.relationshipData(objectGraph, fcData, "contents");
        }
        // Check whether content is a Category Grouping kind.
        if (mediaAttributes.attributeAsString(contentData, "kind") === "CategoryGrouping") {
            categoryGroupingContent = contentData;
            // Update content to its primary content.
            contentData = mediaRelationship.relationshipData(objectGraph, contentData, "primary-content");
            isCategoryGroupingKind = true;
        }
        // If the content data is null don't even bother with the call unavailable since we have no way of knowing how to fetch it
        if (serverData.isNull(contentData)) {
            return null;
        }
        else if (serverData.isNull(contentData.attributes) || shouldDefer(token)) {
            if (serverData.isDefinedNonNullNonEmpty(token)) {
                token.isDeferring = true;
            }
            callUnavailable(contentData);
            return null;
        }
        // Generate the subtitle
        let subtitle = content.notesFromData(objectGraph, contentData, "tagline") ||
            lockups.subtitleFromData(objectGraph, contentData);
        // Generate the click action
        // Using the fcData here because the ids need to match the id used for the impressions, otherwise the reporting
        // heat maps dont work.
        // rdar://61527868 (Metrics: Arcade Data Mismatch (Location and Impressions have different name fields))
        const metricsClickOptions = metricsHelpersClicks.clickOptionsForLockup(objectGraph, fcData, metricsOptions);
        metricsClickOptions.targetType = metricsOptions.targetType;
        let action = lockups.actionFromData(objectGraph, contentData, metricsClickOptions, token === null || token === void 0 ? void 0 : token.clientIdentifierOverride);
        // Understand if we're dealing with an article here, so when we go to generate app events, we know if we
        // should use the action we've created above or the app event action.
        const isArticle = mediaAttributes.attributeAsBooleanOrFalse(contentData, "isCanvasAvailable");
        // Find the artwork and title depending on the content type
        let artwork = null;
        let caption = null;
        // Find lockup (if any)
        let lockup = null;
        // Find app event (if any)
        let appEvent;
        const shortEditorialDescription = mediaAttributes.attributeAsString(contentData, "itunesNotes.short");
        const hasContentId = ((_c = contentData.id) === null || _c === void 0 ? void 0 : _c.length) > 0;
        const contentMetricsOptions = {
            ...metricsOptions,
            id: hasContentId ? contentData.id : fcData.id,
            idType: hasContentId ? "its_id" : "editorial_id",
        };
        switch (contentData.type) {
            case "groupings": {
                artwork = mediaAttributes.attributeAsDictionary(contentData, "artwork");
                // If data is a Category Grouping kind, reconfigure content.
                if (isCategoryGroupingKind) {
                    contentData = categoryGroupingContent !== null && categoryGroupingContent !== void 0 ? categoryGroupingContent : fcData;
                }
                break;
            }
            case "editorial-items": {
                // Check for an app-event relationship
                const relatedCardContents = mediaRelationship.relationshipData(objectGraph, contentData, "card-contents");
                if (serverData.isDefinedNonNullNonEmpty(relatedCardContents)) {
                    const clickOptions = {
                        ...contentMetricsOptions,
                        inAppEventId: relatedCardContents.id,
                    };
                    const parentAppData = mediaRelationship.relationshipData(objectGraph, relatedCardContents, "app");
                    if (serverData.isDefinedNonNull(parentAppData)) {
                        clickOptions.relatedSubjectIds = [parentAppData.id];
                    }
                    const appEventOrDate = appEvents.appEventOrPromotionStartDateFromData(objectGraph, relatedCardContents, null, false, true, "dark", "white", false, clickOptions, false, true, null, token.isArcadePage, false);
                    const cardDisplayStyle = mediaAttributes.attributeAsString(contentData, "cardDisplayStyle");
                    if (cardDisplayStyle === "AppEventCard") {
                        if (appEventOrDate instanceof Date) {
                            // If we get a date back, we have a valid app event, but it starts in the future.
                            // We don't want the object containing this event to render yet, so return early.
                            refresh.addNextPreferredContentRefreshDate(appEventOrDate, context.refreshController);
                            return null;
                        }
                        else if (isNothing(appEventOrDate)) {
                            return null;
                        }
                        else {
                            appEvent = appEventOrDate;
                            if (!isArticle) {
                                action = appEvent.clickAction;
                            }
                            if (serverData.isNullOrEmpty(subtitle)) {
                                subtitle = content.notesFromData(objectGraph, relatedCardContents, "short");
                            }
                        }
                    }
                }
                caption = mediaAttributes.attributeAsString(contentData, "label");
                if (caption) {
                    // This is a hack for AOTD/GOTD
                    caption = caption.replace(/\n/g, " ");
                }
                const relatedContent = mediaRelationship.relationshipData(objectGraph, contentData, "contents");
                const tagline = serverData.asString(contentData, "editorialNotes.tagline");
                if (serverData.isNullOrEmpty(subtitle)) {
                    if (tagline) {
                        subtitle = tagline;
                    }
                    else if (relatedContent) {
                        subtitle = content.notesFromData(objectGraph, relatedContent, "short");
                    }
                }
                if (serverData.isNullOrEmpty(subtitle) && serverData.isDefinedNonNull(appEvent)) {
                    subtitle = appEvent.subtitle;
                }
                let crossLinkSubtitle = mediaAttributes.attributeAsString(contentData, "editorialNotes.short");
                if (isNothing(crossLinkSubtitle) || crossLinkSubtitle.length === 0) {
                    crossLinkSubtitle = subtitle;
                }
                const cardConfig = defaultTodayCardConfiguration(objectGraph);
                if (serverData.isNull(appEvent) &&
                    externalDeepLink.deepLinkUrlFromData(objectGraph, contentData) &&
                    !objectGraph.client.isiOS) {
                    cardConfig.crossLinkSubtitle = crossLinkSubtitle;
                }
                cardConfig.clientIdentifierOverride = clientIdentifierForEditorialContextInData(objectGraph, contentData);
                if (serverData.isDefinedNonNull(appEvent)) {
                    lockup = appEvent.lockup;
                }
                else {
                    // On iOS and Web, we always attempt to create a lockup. On other platforms, only do this is there is a cross link.
                    // iOS offer style will be determined later based on artwork.
                    const offerEnvironment = objectGraph.client.isiOS ? null : "dark";
                    const offerStyle = objectGraph.client.isiOS ? null : "white";
                    if (objectGraph.client.isiOS ||
                        objectGraph.client.isWeb ||
                        externalDeepLink.deepLinkUrlFromData(objectGraph, contentData)) {
                        metricsHelpersLocation.pushContentLocation(objectGraph, contentMetricsOptions, token === null || token === void 0 ? void 0 : token.title);
                        const relatedLockups = lockupsForRelatedContent(objectGraph, mediaRelationship.relationshipCollection(contentData, "card-contents"), cardConfig, metricsOptions.pageInformation, metricsOptions.locationTracker, offerEnvironment, offerStyle, externalDeepLink.deepLinkUrlFromData(objectGraph, contentData));
                        if (relatedLockups.length === 1) {
                            lockup = relatedLockups[0];
                        }
                        metricsHelpersLocation.popLocation(contentMetricsOptions.locationTracker);
                    }
                }
            }
            // falls through
            default: {
                // Create a lockup if possible on iOS
                const validLockupContentTypes = [
                    "apps",
                    "arcade-apps",
                    "app-bundles",
                    "in-apps",
                ];
                if (serverData.isNull(lockup) &&
                    validLockupContentTypes.indexOf(contentData.type) > -1 &&
                    objectGraph.host.isiOS) {
                    metricsHelpersLocation.pushContentLocation(objectGraph, contentMetricsOptions, token === null || token === void 0 ? void 0 : token.title);
                    const lockupOptions = {
                        metricsOptions: {
                            pageInformation: metricsOptions.pageInformation,
                            locationTracker: metricsOptions.locationTracker,
                            recoMetricsData: mediaDataStructure.metricsFromMediaApiObject(contentData),
                        },
                        clientIdentifierOverride: token === null || token === void 0 ? void 0 : token.clientIdentifierOverride,
                        artworkUseCase: content.artworkUseCaseFromShelfStyle(objectGraph, token === null || token === void 0 ? void 0 : token.shelfStyle),
                        canDisplayArcadeOfferButton: true,
                        shouldHideArcadeHeader: objectGraph.featureFlags.isEnabled("hide_arcade_header_on_arcade_tab") &&
                            token.isArcadePage,
                    };
                    lockup = lockups.lockupFromData(objectGraph, contentData, lockupOptions);
                    metricsHelpersLocation.popLocation(contentMetricsOptions.locationTracker);
                }
                artwork =
                    contentAttributes.contentAttributeAsDictionary(objectGraph, contentData, "editorialArtwork") ||
                        mediaAttributes.attributeAsDictionary(contentData, "editorialArtwork");
                if (serverData.isNullOrEmpty(subtitle) && serverData.isDefinedNonNull(lockup)) {
                    subtitle = lockup.subtitle;
                }
                break;
            }
        }
        if (serverData.isDefinedNonNull(action)) {
            action.presentationStyle = ["textFollowsTintColor"];
            // If data is NOT a Category Grouping kind, reconfigure action title.
            if (!isCategoryGroupingKind) {
                // Prefer design tag or editorial name for title when available
                const designTag = unescapeHtmlString(mediaAttributes.attributeAsString(fcData, "designTag"));
                const editorialTitle = content.notesFromData(objectGraph, contentData, "name");
                action.title = designTag || editorialTitle || action.title || subtitle || caption;
            }
        }
        return {
            action: action,
            caption: caption,
            title: action === null || action === void 0 ? void 0 : action.title,
            subtitle: subtitle,
            artwork: artwork,
            shortEditorialDescription: shortEditorialDescription,
            content: contentData,
            lockup: lockup,
            appEvent: appEvent,
            onDevicePersonalizationDataProcessingType: personalizedDataResult === null || personalizedDataResult === void 0 ? void 0 : personalizedDataResult.processingType,
        };
    }
    return null;
}
/// Gets the action and title for a brick that is backed by tags.
export function metadataForTag(objectGraph, fcData, token, metricsOptions) {
    const shortEditorialDescription = mediaAttributes.attributeAsString(fcData, "name");
    const href = serverData.asString(fcData, "href");
    const url = hrefToRoutableUrl(objectGraph, href);
    const flowPage = objectGraph.required(pageRouter).fetchFlowPage(url);
    const flowAction = new models.FlowAction(flowPage);
    flowAction.pageUrl = url;
    return {
        action: flowAction,
        caption: "null",
        title: shortEditorialDescription,
        subtitle: "null",
        artwork: null,
        shortEditorialDescription: shortEditorialDescription,
    };
}
function metadataForLink(objectGraph, fcData, token, metricsOptions, unavailableCallback) {
    const isFCLinkData = serverData.isDefinedNonNull(serverData.asString(fcData, "url"));
    const linkData = isFCLinkData ? fcData : mediaAttributes.attributeAsDictionary(fcData, "link");
    const callUnavailable = function (contentData) {
        if (unavailableCallback) {
            unavailableCallback();
        }
        else {
            token === null || token === void 0 ? void 0 : token.remainingItems.push(contentData);
        }
    };
    if (serverData.isNull(linkData) || shouldDefer(token)) {
        callUnavailable(fcData);
        return null;
    }
    const target = serverData.asString(linkData, "target");
    const url = serverData.asString(linkData, "url");
    // Prefer design tag for title when available
    const label = serverData.asString(linkData, "label");
    const designTag = unescapeHtmlString(mediaAttributes.attributeAsString(fcData, "designTag"));
    const title = designTag || label;
    let action = null;
    if (target === "external") {
        action = new models.ExternalUrlAction(url);
        action.title = title;
    }
    else {
        const flowPage = objectGraph.required(pageRouter).fetchFlowPage(url);
        const flowAction = new models.FlowAction(flowPage);
        flowAction.pageUrl = url;
        flowAction.title = title;
        action = flowAction;
    }
    action.presentationStyle = ["textFollowsTintColor"];
    // Configure metrics
    const clickOptions = {
        ...metricsOptions,
        id: "",
    };
    metricsHelpersClicks.addClickEventToAction(objectGraph, action, clickOptions);
    return {
        action: action,
        caption: null,
        title: title,
        subtitle: null,
        artwork: null,
        shortEditorialDescription: null,
    };
}
// endregion
// region Editorial Data Merging
export function mergeContentDataIntoEditorialData(contentDataArray, editorialItemsDataArray) {
    const contentDataMap = {};
    for (const contentData of contentDataArray) {
        contentDataMap[contentData.id] = contentData;
    }
    // The relationships we're interested in merging fetched content for
    const relationShipsToMerge = ["contents", "grouping"];
    const mergedEditorialData = [];
    // Steps for merging
    // 1. Loop through the editorial items array
    // 2. For each editorial item loop through the relationships we're interested in
    // 3. If this editorial item has any of these relationships loop through that relationsip looking in our content map
    //    for the hydrated version of each piece of data in the relationship
    // 4. If all the content has been fetched for each relationship we can add it to our resulting array of hydrated editorial items.
    for (const editorialItemData of editorialItemsDataArray) {
        let hasHydratedAllRelationships = true;
        for (const relationshipType of relationShipsToMerge) {
            const unhydratedRelationshipCollection = mediaRelationship.relationshipCollection(editorialItemData, relationshipType);
            if (serverData.isDefinedNonNull(unhydratedRelationshipCollection)) {
                const hydratedRelationship = [];
                for (const unhydratedData of unhydratedRelationshipCollection) {
                    const hydratedData = contentDataMap[unhydratedData.id];
                    if (serverData.isDefinedNonNullNonEmpty(hydratedData)) {
                        hydratedRelationship.push(hydratedData);
                    }
                }
                if (hydratedRelationship.length === unhydratedRelationshipCollection.length) {
                    editorialItemData.relationships[relationshipType] = { data: hydratedRelationship };
                }
                else {
                    hasHydratedAllRelationships = false;
                }
            }
        }
        if (hasHydratedAllRelationships) {
            mergedEditorialData.push(editorialItemData);
        }
    }
    return mergedEditorialData;
}
// endregion
// region Shelf Info
/**
 * For incomplete shelf fetches via a secondary lookup, whether a given shelf w/ token should merge or replace.
 * @param objectGraph
 * @param token The token corresponding to the shelf being built
 */
export function shelfFetchShouldMergeWhenFetched(objectGraph, token) {
    /**
     * <rdar://problem/60069585> [POLISH] Arcade Coming Soon: If too few items, the coming soon swoosh should show larger items.
     * Always reload posterLockup shelves on macOS to adapt if the presentationHint "isLowDensity" changed.
     */
    if (token.shelfStyle === "posterLockup" && objectGraph.client.isMac) {
        return false;
    }
    else if (token.showingPlaceholders) {
        return false;
    }
    // Always reload Arcade download pack shelf
    // as there is always only a single cell (card) that contains lockups or their placeholders.
    if (token.shelfStyle === "arcadeDownloadPackCard") {
        return false;
    }
    if (token.shelfStyle === "ribbonBar" &&
        isSome(token.initialHydratedItems) &&
        token.initialHydratedItems.length > 0) {
        return false;
    }
    // Generally true to support partially hydrated shelves.
    return true;
}
// endregion
// region Search Landing Shelves
/**
 * Modify `shelf` in place for global customization for all SLP shelves.
 * @param objectGraph
 * @param shelf
 * @param token
 */
export function modifyShelfForSearchLandingGrouping(objectGraph, shelf, token) {
    shelf.seeAllAction = null;
    shelf.isHorizontal = false;
    if (shelf.shouldFilterApps) {
        // <rdar://problem/64772261> App Store: SLP: Installed app 'Open' button show up inconsistently (Filtering is sometimes not applied)
        shelf.filteredItemsMinimumCount = 0;
        shelf.filteringExcludedItems = token.includedAdAdamIds;
    }
}
// endregion
// region Card Shelves
/**
 * The shelf content type to use for the inline card display style.
 * @param objectGraph
 * @param {HorizontalCardDisplayStyle} style The display style for the inline card.
 * @returns {ShelfContentType} The shelf content type to use.
 */
export function contentTypeForHorizontalCardDisplayStyle(objectGraph, style) {
    switch (style) {
        case "small":
            return "smallStoryCard";
        case "medium":
            return "mediumStoryCard";
        case "large":
            return "largeStoryCard";
        case "card":
            if (objectGraph.client.isiOS) {
                return "editorialStoryCard";
            }
            else {
                return null;
            }
        default:
            return null;
    }
}
// endregion
/**
 * Determine the best content id to use given a media api data object
 * @param objectGraph
 * @param contentItem The media api data model to find the id from
 */
export function contentIdFromContentItem(objectGraph, contentItem) {
    let contentId = mediaAttributes.attributeAsString(contentItem, "adamId");
    if (!contentId) {
        contentId = mediaAttributes.attributeAsString(contentItem, "contentId");
    }
    if (!contentId) {
        contentId = mediaAttributes.attributeAsString(contentItem, "id");
    }
    return contentId;
}
export function unescapeHtmlString(str) {
    if (serverData.isNull(str)) {
        return null;
    }
    const escapedString = str
        .replace(/&amp;/g, "&")
        .replace(/&gt;/g, ">")
        .replace(/&lt;/g, "<")
        .replace(/&quot;/g, '"')
        .replace(/&#39;/g, "'")
        .replace(/&#96;/g, "`")
        .replace(/\r\n/g, " ")
        .replace(/&nbsp;/g, " ")
        .replace(/<span>/g, "")
        .replace(/<\/span>/g, "")
        .replace(/<br>/g, " ")
        .replace(/\u23ce/g, "")
        .replace(/<i>/g, "")
        .replace(/<\/i>/g, "")
        .replace(/<b>/g, "")
        .replace(/<\/b>/g, "");
    if (escapedString.match(/^\s*$/)) {
        return null;
    }
    return escapedString;
}
/**
 * Update the shelf header to use the provided seeAll action.
 * @param objectGraph The App Store object graph used to check feature flags
 * @param shelfHeader  The shelf header to update
 * @param seeAllAction The "See All" action to apply
 */
export function replaceShelfHeaderSeeAllAction(objectGraph, shelfHeader, seeAllAction) {
    if (objectGraph.featureFlags.isEnabled("shelf_header")) {
        // Modern headers make title tappable with a chevron (>).
        shelfHeader.titleAction = seeAllAction;
    }
    else {
        // Legacy headers show "See All" textual button on trailing edge.
        shelfHeader.accessoryAction = seeAllAction;
    }
}
/**
 * Update the shelf header to use the provided seeAll action.
 * @param objectGraph The App Store object graph used to check feature flags
 * @param shelf  The shelf whose header needs updating
 * @param seeAllAction The see all action to apply
 */
export function replaceShelfSeeAllAction(objectGraph, shelf, seeAllAction) {
    if (objectGraph.featureFlags.isEnabled("shelf_header")) {
        if (isSome(shelf.header)) {
            replaceShelfHeaderSeeAllAction(objectGraph, shelf.header, seeAllAction);
        }
        else {
            shelf.header = {
                titleAction: seeAllAction,
            };
        }
    }
    else {
        shelf.seeAllAction = seeAllAction;
    }
}
//# sourceMappingURL=grouping-shelf-controller-common.js.map