import { isNothing, isSome } from "@jet/environment";
import * as validation from "@jet/environment/json/validation";
import * as models from "../../api/models";
import { asArrayOrEmpty, asBoolean, asDictionary, asInterface, asString, isDefinedNonNull, isDefinedNonNullNonEmpty, isNull, isNullOrEmpty, } from "../../foundation/json-parsing/server-data";
import { editorialCardFromData } from "../../foundation/media/associations";
import * as mediaAttributes from "../../foundation/media/attributes";
import { attributeAsString, hasAttributes } from "../../foundation/media/attributes";
import * as mediaDataStructure from "../../foundation/media/data-structure";
import { relationshipData } from "../../foundation/media/relationships";
import { Parameters, Path, Protocol } from "../../foundation/network/url-constants";
import { URL } from "../../foundation/network/urls";
import { isAdPlacementEnabled } from "../ads/ad-common";
import { categoryArtworkData } from "../categories";
import { artworkFromApiArtwork, iconFromData } from "../content/content";
import { extractEditorialClientParams } from "../editorial-pages/editorial-data-util";
import { addImpressionFields } from "../metrics/helpers/impressions";
import { nextPosition, popLocation, pushContentLocation } from "../metrics/helpers/location";
import { combinedRecoMetricsDataFromMetricsData } from "../metrics/helpers/util";
import * as onDevicePersonalization from "../personalization/on-device-personalization";
import * as color from "./../../foundation/util/color-util";
import { asNumber } from "@apple-media-services/media-api";
import { createTodayAdCard } from "./cards/today-ad-card-builder";
import { defaultTodayCardConfiguration, todayCardFromData } from "./today-card-util";
import * as impressionDemotion from "../../common/personalization/on-device-impression-demotion";
import { EditorialBackgroundType, TodayCardMetricsDisplayStyle, TodayHeaderArtworkBehavior, TodaySectionHeaderArtworkPlacement, } from "./today-types";
/**
 * We try to flatten the today response so we can treat it as a single list
 * of items. These are the different item types in the flattened list.
 */
export var FlattenedTodayItemType;
(function (FlattenedTodayItemType) {
    FlattenedTodayItemType["EditorialItem"] = "editorialItem";
    FlattenedTodayItemType["EditorialItemGroup"] = "editorialItemGroup";
})(FlattenedTodayItemType || (FlattenedTodayItemType = {}));
/**
 * Flatten the entire modules array from the TodayPage response into a single list of items.
 * @param objectGraph The dependency graph for the App Store
 * @param todayModules The today modules to flatten from the page response
 * @param onboardingCardsData The media api data for the onboarding cards, inserted into the flattened list
 * @param todayRecommendations The today recommendations result, inserted into the flattened list
 * @param adsResponse The response from ad platforms
 * @returns The flattened list of today items
 */
export function flattenTodayModules(objectGraph, todayModules, todayRecommendations, onboardingCardsData, adsResponse) {
    const flattenedItems = [];
    let insertedOnboardingCards = isNullOrEmpty(onboardingCardsData);
    // Retrieve our personalization data
    const personalizationDataContainer = personalizationDataContainerForTodayModules(objectGraph, todayModules);
    let absoluteFeedIndex = 0;
    for (const todayModule of todayModules) {
        if (isNullOrEmpty(todayModule.contents)) {
            continue;
        }
        const personalizedModuleDataResult = onDevicePersonalization.personalizeDataItems(objectGraph, "today", todayModule.contents, true, personalizationDataContainer);
        todayModule.contents = personalizedModuleDataResult.personalizedData;
        todayModule.onDevicePersonalizationProcessingType = personalizedModuleDataResult.processingType;
        let isFirstItemInModule = true;
        const moduleMetadata = {
            label: todayModule.label,
            title: todayModule.title,
            meta: todayModule.meta,
            date: todayModule.date,
            onDevicePersonalizationProcessingType: todayModule.onDevicePersonalizationProcessingType,
            isTodayForAppsModule: mediaDataStructure.isModuleTodayForApps(todayModule),
        };
        if (!insertedOnboardingCards) {
            for (const onboardingCardData of onboardingCardsData) {
                flattenedItems.push({
                    type: FlattenedTodayItemType.EditorialItem,
                    data: onboardingCardData,
                    isDataHydrated: hasAttributes(onboardingCardData),
                    isFirstItemInModule,
                    moduleMetadata: { ...moduleMetadata },
                    containedAdSlots: [absoluteFeedIndex],
                });
            }
            isFirstItemInModule = false;
            insertedOnboardingCards = true;
            absoluteFeedIndex += 1;
        }
        for (let moduleItem of todayModule.contents) {
            const onDeviceUseCase = asString(moduleItem, "meta.personalizationData.onDeviceUseCase");
            switch (moduleItem.type) {
                case "editorial-items":
                    if (isSome(onDeviceUseCase)) {
                        // ODP personalization for Today Arcade stories.
                        const storyData = todayRecommendations === null || todayRecommendations === void 0 ? void 0 : todayRecommendations.storyData(onDeviceUseCase);
                        if (isSome(storyData)) {
                            moduleItem = storyData;
                        }
                    }
                    flattenedItems.push({
                        type: FlattenedTodayItemType.EditorialItem,
                        data: moduleItem,
                        isDataHydrated: hasAttributes(moduleItem),
                        isFirstItemInModule,
                        moduleMetadata: { ...moduleMetadata },
                        containedAdSlots: [absoluteFeedIndex],
                    });
                    isFirstItemInModule = false;
                    absoluteFeedIndex += 1;
                    break;
                case "editorial-item-groups":
                    const groupContents = asArrayOrEmpty(moduleItem, "meta.associations.recommendations.data");
                    if (isNullOrEmpty(groupContents)) {
                        continue;
                    }
                    let storyGroupData;
                    if (isSome(onDeviceUseCase)) {
                        // ODP personalization for Today Arcade story groups.
                        storyGroupData = todayRecommendations === null || todayRecommendations === void 0 ? void 0 : todayRecommendations.storyGroupData(onDeviceUseCase);
                    }
                    if (isSome(storyGroupData)) {
                        moduleItem = storyGroupData;
                    }
                    else {
                        // ODP personalization for in-app event story groups.
                        const personalizedStoryGroupDataResult = onDevicePersonalization.personalizeDataItems(objectGraph, "today", groupContents, true, personalizationDataContainer);
                        moduleItem["meta"]["associations"]["recommendations"]["data"] =
                            personalizedStoryGroupDataResult.personalizedData;
                        todayModule.onDevicePersonalizationProcessingType =
                            personalizedStoryGroupDataResult.processingType;
                    }
                    flattenedItems.push({
                        type: FlattenedTodayItemType.EditorialItemGroup,
                        data: moduleItem,
                        isDataHydrated: hasAttributes(moduleItem),
                        isFirstItemInModule,
                        moduleMetadata: { ...moduleMetadata },
                        containedAdSlots: Array.from({ length: groupContents.length }, (key, value) => value + absoluteFeedIndex),
                    });
                    isFirstItemInModule = false;
                    absoluteFeedIndex += groupContents.length;
                    break;
                default:
                    break;
            }
        }
    }
    return flattenedItems;
}
/**
 * Whether the next content item in the flattened list is hydrated.
 * @param flattenedItems The flattened list of today items
 * @returns True if the next content item is hydrated, false otherwise
 */
export function nextFlattenedItemIsHydrated(flattenedItems) {
    for (const flattenedItem of flattenedItems) {
        switch (flattenedItem.type) {
            case FlattenedTodayItemType.EditorialItem:
            case FlattenedTodayItemType.EditorialItemGroup:
                return hasAttributes(flattenedItem.data);
            default:
                break;
        }
    }
    return false;
}
/**
 * Iterates through all the today modules, and creates a set of personalization
 * data that is targetted only to the contents of these modules.
 *
 * @param dataArray The input array of today modules.
 * @returns Any relevant OnDevicePersonalizaionData
 */
export function personalizationDataContainerForTodayModules(objectGraph, todayModules) {
    if (!onDevicePersonalization.isPersonalizationAvailable(objectGraph)) {
        return null;
    }
    // Here we iterate through all the modules, and look inside each content items meta for personalizationData.
    const appIds = new Set();
    for (const todayModule of todayModules) {
        if (isNull(todayModule.contents)) {
            continue;
        }
        const appIdFromContent = (contentData) => {
            return asString(contentData, "meta.personalizationData.appId");
        };
        for (const contentData of todayModule.contents) {
            switch (contentData.type) {
                case "editorial-item-groups":
                    const groupItems = asArrayOrEmpty(contentData.meta, "associations.recommendations.data");
                    for (const groupItem of groupItems) {
                        const appId = appIdFromContent(groupItem);
                        if (isDefinedNonNullNonEmpty(appId)) {
                            appIds.add(appId.toString());
                        }
                    }
                    break;
                default:
                    const appId = appIdFromContent(contentData);
                    if (isDefinedNonNullNonEmpty(appId)) {
                        appIds.add(appId.toString());
                    }
                    break;
            }
        }
    }
    return onDevicePersonalization.personalizationDataContainerForAppIds(objectGraph, appIds);
}
// MARK: - Shelf Creation
/**
 * @param objectGraph The dependency graph for the App Store
 * @param todayItem The today item that this card is contained in, this could be a single item or a story group item
 * @param todayCardData The MAPI data that will be used to create this today card
 * @param isAdEligible Whether the current card is being placed in a slot that was eligible for an ad
 * @param currentRowIndex The current row index this card is going to be in
 * @param metricsDisplayStyle: The display style for impressions and location
 * @param isHeroCard: Whether this is the first card in a hero story group
 * @returns The configuration to use when parsing a today card
 */
function createTodayCardConfiguration(objectGraph, todayCardData, isAdEligible, currentRowIndex, metricsDisplayStyle, isHeroCard) {
    var _a;
    const cardConfig = defaultTodayCardConfiguration(objectGraph);
    cardConfig.useOTDTextStyle = (_a = asBoolean(todayCardData, "meta.personalizationData.isOfTheDay")) !== null && _a !== void 0 ? _a : false;
    cardConfig.replaceIfAdPresent = asBoolean(todayCardData, "meta.personalizationData.replaceIfAdPresent");
    cardConfig.isAdEligible = isAdEligible;
    cardConfig.currentRowIndex = currentRowIndex;
    cardConfig.metricsDisplayStyle = metricsDisplayStyle;
    cardConfig.isHeroCard = isHeroCard;
    if (objectGraph.client.isWeb) {
        cardConfig.prevailingCropCodes = {
            "defaultCrop": "sr",
            "editorialArtwork.dayCard": "grav.west",
        };
    }
    return cardConfig;
}
/**
 * @param objectGraph The dependency graph for the App Store
 * @param pageContext The page context for the today page
 * @returns The currentRowIndex from the pageContext, this is only used on iPhone, otherwise return
 * null since we cant reliably know the row index
 */
function rowCountFromContext(objectGraph, pageContext) {
    if (!objectGraph.client.isPhone) {
        return undefined;
    }
    return pageContext.currentRowIndex;
}
/**
 * Update the page context with the current row count, this uses the groupDisplayStyle to determine
 * the display style for the cards in the current row
 * @param pageContext The page context for the today page we're parsing
 * @param currentGroupDisplayStyle The current groupDisplayItem from the TodayItem we're parsing
 * @param currentItemIndex The index of the current item in the shelf, that we just created a card
 * for, this method is called after creating a card
 */
function incrementRowIndexIfNecessary(pageContext, currentGroupDisplayStyle, currentItemIndex) {
    switch (currentGroupDisplayStyle) {
        case models.GroupDisplayStyle.Grid:
            if (currentItemIndex % 2 === 1) {
                pageContext.currentRowIndex++;
            }
            break;
        case models.GroupDisplayStyle.Hero:
            if (currentItemIndex === 0 || (currentItemIndex - 1) % 2 === 1) {
                pageContext.currentRowIndex++;
            }
            break;
        case models.GroupDisplayStyle.Standard:
            pageContext.currentRowIndex++;
            break;
        default:
            break;
    }
}
/**
 * Update the page context with the current metrics display style, this is based off the groupDisplayStyle
 * @param objectGraph The dependency graph for the App Store
 * @param pageContext The page context for the today page we're parsing
 * @param currentGroupDisplayStyle The current groupDisplayItem from the TodayItem we're parsing
 * @param currentItemIndex The index of the current item in the shelf, that we just created a card
 * for, this method is called after creating a card
 */
function updateCurrentRowMetricsDisplayStyle(objectGraph, pageContext, currentGroupDisplayStyle, currentItemIndex) {
    if (objectGraph.client.isPad) {
        pageContext.currentRowMetricsDisplayStyle = TodayCardMetricsDisplayStyle.MediumCard;
        return;
    }
    switch (currentGroupDisplayStyle) {
        case models.GroupDisplayStyle.Grid:
            pageContext.currentRowMetricsDisplayStyle = TodayCardMetricsDisplayStyle.SmallCard;
            break;
        case models.GroupDisplayStyle.Hero:
            if (currentItemIndex === 0) {
                pageContext.currentRowMetricsDisplayStyle = TodayCardMetricsDisplayStyle.MediumCard;
            }
            else {
                pageContext.currentRowMetricsDisplayStyle = TodayCardMetricsDisplayStyle.SmallCard;
            }
            break;
        case models.GroupDisplayStyle.Standard:
            pageContext.currentRowMetricsDisplayStyle = TodayCardMetricsDisplayStyle.MediumCard;
            break;
        default:
            break;
    }
}
/**
 * @param objectGraph The app store object graph
 * @param data The data from MAPI for a single item in the today feed, group or single EI
 * @returns The group display style for the given data
 */
function groupDisplayStyleFromData(objectGraph, data) {
    var _a;
    if (data.type === "editorial-items") {
        return models.GroupDisplayStyle.Standard;
    }
    let groupDisplayStyle;
    const editorialCard = editorialCardFromData(data);
    if (hasAttributes(editorialCard)) {
        groupDisplayStyle = mediaAttributes.attributeAsString(editorialCard, "editorialItemGroupDisplayStyle");
    }
    if (isNothing(groupDisplayStyle)) {
        groupDisplayStyle =
            (_a = mediaAttributes.attributeAsString(data, "displayStyle")) !== null && _a !== void 0 ? _a : models.GroupDisplayStyle.Standard;
    }
    return isGroupDisplayStyleSupported(objectGraph, groupDisplayStyle)
        ? groupDisplayStyle
        : models.GroupDisplayStyle.Standard;
}
/**
 * @param objectGraph The app dependency graph
 * @param displayStyle The display style to check support for
 * @returns Whether group display style is supported for this platform
 */
function isGroupDisplayStyleSupported(objectGraph, displayStyle) {
    if (isNothing(displayStyle)) {
        return false;
    }
    switch (displayStyle) {
        case models.GroupDisplayStyle.Grid:
            return objectGraph.client.isPhone;
        default:
            return true;
    }
}
/**
 * Determine if the current card is eligible for an ad, based on the parsed card count, and the
 * bag defined values for slots that can have ads
 * @param pageContext The page context for the today page
 * @returns Whether the current card is eligible for an ad
 */
function isTodayCardConfigurationAdEligible(pageContext) {
    if (pageContext.adLocation === pageContext.parsedCardCount) {
        return true;
    }
    if (isNothing(pageContext.eligibleAdLocations)) {
        return false;
    }
    return pageContext.eligibleAdLocations.includes(pageContext.parsedCardCount);
}
/**
 * @param objectGraph The dependency graph for the App Store
 * @param todayItem The flattened today item to create a shelf for
 * @param pageContext The page context for the today page
 * @returns The shelf for the given today item, or null if we cant create one
 */
export function todayShelfForEditorialItem(objectGraph, todayItem, pageContext) {
    var _a;
    const shelf = createTodayShelfWithItems(objectGraph, todayItem, pageContext, () => {
        var _a, _b, _c;
        const shelfItems = [];
        (_a = pageContext.pageInformation.iAdInfo) === null || _a === void 0 ? void 0 : _a.updateContainerId((_b = pageContext.pageInformation.iAdInfo) === null || _b === void 0 ? void 0 : _b.containerIdForSlotIndex((_c = pageContext.parsedCardCount) !== null && _c !== void 0 ? _c : 0));
        const groupDisplayStyle = groupDisplayStyleFromData(objectGraph, todayItem.data);
        updateCurrentRowMetricsDisplayStyle(objectGraph, pageContext, groupDisplayStyle, 0);
        const cardConfig = createTodayCardConfiguration(objectGraph, todayItem.data, isTodayCardConfigurationAdEligible(pageContext), rowCountFromContext(objectGraph, pageContext), pageContext.currentRowMetricsDisplayStyle, false);
        cardConfig.baseMetricsOptions = {
            recoMetricsData: recoMetricsFromTodayItem(todayItem),
        };
        const editorialItemTodayCard = createTodayCardForPageContext(objectGraph, pageContext, cardConfig, todayItem.data);
        if (isNothing(editorialItemTodayCard)) {
            return shelfItems;
        }
        shelfItems.push(editorialItemTodayCard);
        nextPosition(pageContext.locationTracker);
        pageContext.parsedCardCount++;
        incrementRowIndexIfNecessary(pageContext, groupDisplayStyle, 0);
        return shelfItems;
    });
    shelf.contentsMetadata = {
        type: "todaySection",
        debugSectionTypeIndicatorColor: todayItem.type === FlattenedTodayItemType.EditorialItemGroup
            ? color.named("systemGreen")
            : color.named("systemBlue"),
        groupDisplayStyle: models.GroupDisplayStyle.Standard,
    };
    // If this is not the first item in the module, attempt to set the background
    if (!todayItem.isFirstItemInModule) {
        const editorialShelfBackgroundInfo = todaySectionEditorialBackground(objectGraph, todayItem);
        if (isSome(editorialShelfBackgroundInfo)) {
            shelf.background = editorialShelfBackgroundInfo.shelfBackground;
            if (isSome((_a = shelf.header) === null || _a === void 0 ? void 0 : _a.configuration)) {
                shelf.header.configuration.eyebrowColor = editorialShelfBackgroundInfo.eyebrowColor;
                shelf.header.configuration.titleColor = editorialShelfBackgroundInfo.titleColor;
                shelf.header.configuration.subtitleColor = editorialShelfBackgroundInfo.subtitleColor;
            }
        }
    }
    return shelf;
}
/**

 * @param objectGraph The dependency graph for the App Store
 * @param todayItem The flattened today item, of type EditorialItemGroup to create a shelf for
 * @param pageContext The page context for the today page
 * @returns The shelf for the given today item, or null if we cant create one
 */
export function todayShelfForEditorialItemGroup(objectGraph, todayItem, pageContext) {
    var _a;
    let isValidHeroStoryGroup = true;
    const shelf = createTodayShelfWithItems(objectGraph, todayItem, pageContext, () => {
        var _a, _b, _c, _d, _e, _f, _g;
        const shelfItems = [];
        const displayLimit = impressionDemotion.isImpressionDemotionAvailable(objectGraph)
            ? (_a = asNumber(todayItem.data, "meta.personalizationData.displayEICount")) !== null && _a !== void 0 ? _a : 100
            : 100;
        let groupItems = asArrayOrEmpty(todayItem.data.meta, "associations.recommendations.data");
        if (isSome(pageContext.recoImpressionData)) {
            groupItems = impressionDemotion.personalizeDataItems(groupItems, pageContext.recoImpressionData, (_b = pageContext.pageInformation.recoMetricsData) !== null && _b !== void 0 ? _b : {});
        }
        const groupDisplayStyle = groupDisplayStyleFromData(objectGraph, todayItem.data);
        let parsedGroupItemCount = 0;
        for (const [index, groupItem] of groupItems.entries()) {
            (_c = pageContext.pageInformation.iAdInfo) === null || _c === void 0 ? void 0 : _c.updateContainerId((_d = pageContext.pageInformation.iAdInfo) === null || _d === void 0 ? void 0 : _d.containerIdForSlotIndex((_e = pageContext.parsedCardCount) !== null && _e !== void 0 ? _e : 0));
            updateCurrentRowMetricsDisplayStyle(objectGraph, pageContext, groupDisplayStyle, parsedGroupItemCount);
            const cardConfig = createTodayCardConfiguration(objectGraph, groupItem, isTodayCardConfigurationAdEligible(pageContext), rowCountFromContext(objectGraph, pageContext), pageContext.currentRowMetricsDisplayStyle, groupDisplayStyle === models.GroupDisplayStyle.Hero && index === 0);
            const shelfItem = createTodayCardForPageContext(objectGraph, pageContext, cardConfig, groupItem);
            if (isSome(shelfItem)) {
                shelfItems.push(shelfItem);
                nextPosition(pageContext.locationTracker);
                pageContext.parsedCardCount++;
                incrementRowIndexIfNecessary(pageContext, groupDisplayStyle, parsedGroupItemCount);
                parsedGroupItemCount++;
            }
            if (cardConfig.isHeroCard && isNothing(shelfItem)) {
                if (["debug", "internal"].includes(objectGraph.client.buildType)) {
                    validation.unexpectedType("defaultValue", `Hero story group ${(_f = todayItem.data) === null || _f === void 0 ? void 0 : _f.id} must contain a valid hero card at index ${index}. Unable to parse card ${groupItem.id}.`, null);
                }
                isValidHeroStoryGroup = false;
            }
            if (index < groupItems.length - 1 &&
                pageContext.adPlacementBehavior === models.AdPlacementBehavior.insertIntoShelf) {
                // Attempt to insert the ad card after creating the next item in the group, but only
                // if we're still **within** the story group, ads that fall at the beginning or end of
                // the group are handled at the top level of the page parsing.
                const adCard = createAdCardForTodayPageContextIfNecessary(objectGraph, pageContext, createTodayCardConfiguration(objectGraph, pageContext.adData, isTodayCardConfigurationAdEligible(pageContext), rowCountFromContext(objectGraph, pageContext), undefined, false));
                if (isSome(adCard)) {
                    pageContext.parsedCardCount++;
                    incrementRowIndexIfNecessary(pageContext, models.GroupDisplayStyle.Standard, 0);
                    nextPosition(pageContext.locationTracker);
                    shelfItems.push(adCard);
                }
            }
            // if the count is equal to the story limit. stop here and break.
            if (shelfItems.length === displayLimit) {
                break;
            }
        }
        if (isValidHeroStoryGroup) {
            // The number of items required to be a valid hero story group, this includes the hero
            // card, and two additional cards to form a row
            const heroStoryGroupRequiredItemCount = 3;
            if (shelfItems.length !== heroStoryGroupRequiredItemCount) {
                if (["debug", "internal"].includes(objectGraph.client.buildType)) {
                    validation.unexpectedType("defaultValue", `Hero story group ${(_g = todayItem.data) === null || _g === void 0 ? void 0 : _g.id} must contain exactly ${heroStoryGroupRequiredItemCount} items but only found ${shelfItems.length} items.`, null);
                }
                isValidHeroStoryGroup = false;
            }
        }
        return shelfItems;
    });
    // Fallback to standard group display style if we had an issue creating a valid hero group
    let groupDisplayStyle = groupDisplayStyleFromData(objectGraph, todayItem.data);
    if (groupDisplayStyle === models.GroupDisplayStyle.Hero && !isValidHeroStoryGroup) {
        groupDisplayStyle = models.GroupDisplayStyle.Standard;
    }
    shelf.contentsMetadata = {
        type: "todaySection",
        debugSectionTypeIndicatorColor: color.named("systemGreen"),
        groupDisplayStyle: groupDisplayStyle,
    };
    // If this is not the first item in the module, attempt to set the background
    if (!todayItem.isFirstItemInModule) {
        const editorialShelfBackgroundInfo = todaySectionEditorialBackground(objectGraph, todayItem);
        if (isSome(editorialShelfBackgroundInfo)) {
            shelf.background = editorialShelfBackgroundInfo.shelfBackground;
            if (isSome((_a = shelf.header) === null || _a === void 0 ? void 0 : _a.configuration)) {
                shelf.header.configuration.eyebrowColor = editorialShelfBackgroundInfo.eyebrowColor;
                shelf.header.configuration.titleColor = editorialShelfBackgroundInfo.titleColor;
                shelf.header.configuration.subtitleColor = editorialShelfBackgroundInfo.subtitleColor;
            }
        }
        else if (groupDisplayStyle === models.GroupDisplayStyle.Hero && Array.isArray(shelf.items)) {
            shelf.background = todaySectionBackgroundForHeroDisplayStyle(shelf.items);
        }
    }
    return shelf;
}
/**
 * @param objectGraph The dependency graph for the App Store
 * @param todayItem The flattened today item, used to create the items in this shelf
 * @param pageContext The page context for the today page
 * @param itemProvider The function that will provide the items for this shelf
 * @returns The shelf for the given today item
 */
function createTodayShelfWithItems(objectGraph, todayItem, pageContext, itemProvider) {
    const shouldRecordShelfMetrics = todayItem.type === FlattenedTodayItemType.EditorialItemGroup;
    const shelf = new models.Shelf("todayCard");
    shelf.id = todayItem.data.id;
    shelf.isHorizontal = false;
    shelf.header = createTodayShelfHeaderForTodayItem(objectGraph, todayItem, pageContext);
    if (shouldRecordShelfMetrics) {
        const shelfMetricsOptions = {
            id: shelf.id,
            kind: "editorialItemGroup",
            softwareType: null,
            targetType: "swoosh",
            title: sectionTitleFromTodayItem(objectGraph, todayItem, true),
            pageInformation: pageContext.pageInformation,
            locationTracker: pageContext.locationTracker,
            idType: "its_id",
            recoMetricsData: recoMetricsFromTodayItem(todayItem),
        };
        if (todayItem.type === FlattenedTodayItemType.EditorialItemGroup) {
            shelfMetricsOptions["optimizationId"] = asString(todayItem.data, "meta.personalizationData.optimizationId");
            shelfMetricsOptions["optimizationEntityId"] = asString(todayItem.data, "meta.personalizationData.optimizationEntityId");
        }
        addImpressionFields(objectGraph, shelf, shelfMetricsOptions);
        pushContentLocation(objectGraph, shelfMetricsOptions, shelfMetricsOptions.title);
    }
    shelf.items = itemProvider();
    shelf.isHidden = isNullOrEmpty(shelf.items);
    if (shouldRecordShelfMetrics) {
        popLocation(pageContext.locationTracker);
        nextPosition(pageContext.locationTracker);
    }
    return shelf;
}
function createTodayShelfHeaderForTodayItem(objectGraph, todayItem, pageContext) {
    var _a;
    const shouldSuppressHeader = (_a = asBoolean(todayItem.data, "meta.personalizationData.suppressHeader")) !== null && _a !== void 0 ? _a : false;
    if (shouldSuppressHeader) {
        return null;
    }
    const shelfHeaderConfiguration = {
        eyebrowImageColor: null,
        titleImageColor: null,
        includeSeparator: false,
    };
    const shelfHeader = {
        eyebrow: sectionEyebrowFromTodayItem(objectGraph, todayItem),
        eyebrowArtwork: sectionHeaderArtworkFromTodayItemForPlacement(objectGraph, todayItem, TodaySectionHeaderArtworkPlacement.Eyebrow),
        eyebrowArtworkType: sectionHeaderArtworkTypeFromTodayItemForPlacement(objectGraph, todayItem, TodaySectionHeaderArtworkPlacement.Eyebrow),
        title: sectionTitleFromTodayItem(objectGraph, todayItem),
        titleArtwork: sectionHeaderArtworkFromTodayItemForPlacement(objectGraph, todayItem, TodaySectionHeaderArtworkPlacement.Title),
        titleArtworkType: sectionHeaderArtworkTypeFromTodayItemForPlacement(objectGraph, todayItem, TodaySectionHeaderArtworkPlacement.Title),
        subtitle: sectionSubtitleFromTodayItem(objectGraph, todayItem),
        configuration: shelfHeaderConfiguration,
    };
    if (isSome(shelfHeader.eyebrow) || isSome(shelfHeader.title) || isSome(shelfHeader.subtitle)) {
        return shelfHeader;
    }
    else {
        return null;
    }
}
/**
 * Create a today card for a given today item in a page context. This handles requirements for ad replacement if needed
 * @param objectGraph The dependency graph of the app store
 * @param pageContext The context of the page so we can tell whether its time to insert an ad
 * @param cardConfig The configuration for the today item
 * @param todayItem The flattened today item that should be placed in the shelf or replaced with an ad card
 * @returns The today card to insert in a shelf
 */
function createTodayCardForPageContext(objectGraph, pageContext, cardConfig, todayItem) {
    var _a, _b;
    let editorialItemTodayCard;
    if (pageContext.adPlacementBehavior === models.AdPlacementBehavior.replaceOrganic &&
        isDefinedNonNull(cardConfig.replaceIfAdPresent) &&
        asBoolean(cardConfig.replaceIfAdPresent)) {
        // the ad card should replace the organic
        const adCard = createAdCardForTodayPageContextIfNecessary(objectGraph, pageContext, cardConfig);
        if (isDefinedNonNullNonEmpty(adCard)) {
            editorialItemTodayCard = adCard;
        }
        else {
            editorialItemTodayCard = todayCardFromData(objectGraph, todayItem, cardConfig, pageContext);
        }
    }
    else if (pageContext.adPlacementBehavior === models.AdPlacementBehavior.dropAd &&
        isDefinedNonNull(cardConfig.replaceIfAdPresent) &&
        !asBoolean(cardConfig.replaceIfAdPresent)) {
        // the organic is not replaceable
        editorialItemTodayCard = todayCardFromData(objectGraph, todayItem, cardConfig, pageContext);
        if (isDefinedNonNullNonEmpty(pageContext.adData)) {
            const recorder = pageContext.adIncidentRecorder;
            (_a = recorder === null || recorder === void 0 ? void 0 : recorder.iAdInfo) === null || _a === void 0 ? void 0 : _a.setMissedOpportunity(objectGraph, "EDITORIALTAKEOVER", (_b = recorder === null || recorder === void 0 ? void 0 : recorder.iAdInfo) === null || _b === void 0 ? void 0 : _b.placementType);
        }
    }
    else {
        // we're using existing insertion logic that will insert an ad elsewhere. create a regular card for the flattened item
        editorialItemTodayCard = todayCardFromData(objectGraph, todayItem, cardConfig, pageContext);
    }
    return editorialItemTodayCard;
}
// MARK: - Ads
/**
 * Try create a shelf to display an ad if we're currently at the correct spot in the feed. This is only used
 * for single editorialItem shelves, and the end of an editorial item group, since these locations it does not
 * make sense to include the ad within the shelf for that TodayItem.
 * @param objectGraph The dependency graph of the app store
 * @param pageContext The context of the page so we can tell whether its time to insert an ad
 * @returns The card to insert at the ad slot if necessary
 */
export function createAdShelfForTodayPageContextIfNecessary(objectGraph, pageContext) {
    let adShelf = null;
    if (!isAdPlacementEnabled(objectGraph, "today") ||
        isNothing(pageContext.adData) ||
        pageContext.adPlacementBehavior !== models.AdPlacementBehavior.insertIntoShelf) {
        return adShelf;
    }
    if (pageContext.adLocation !== pageContext.parsedCardCount) {
        return adShelf;
    }
    adShelf = new models.Shelf("todayCard");
    adShelf.id = pageContext.adData.id;
    adShelf.isHorizontal = false;
    adShelf.contentsMetadata = {
        type: "todaySection",
        debugSectionTypeIndicatorColor: color.named("systemBlue"),
        groupDisplayStyle: models.GroupDisplayStyle.Standard,
    };
    const shelfItems = [];
    /// Attempt to create an ad card before creating the next item in the group
    const adCard = createAdCardForTodayPageContextIfNecessary(objectGraph, pageContext, createTodayCardConfiguration(objectGraph, pageContext.adData, isTodayCardConfigurationAdEligible(pageContext), rowCountFromContext(objectGraph, pageContext), undefined, false));
    if (isSome(adCard)) {
        pageContext.parsedCardCount++;
        incrementRowIndexIfNecessary(pageContext, models.GroupDisplayStyle.Standard, 0);
        nextPosition(pageContext.locationTracker);
        shelfItems.push(adCard);
    }
    adShelf.items = shelfItems;
    return isDefinedNonNullNonEmpty(adShelf.items) ? adShelf : null;
}
/**
 * Try to create an ad card for the page context if the requirements are met
 * @param objectGraph The dependency graph of the app store
 * @param pageContext The context of the page so we can tell whether its time to insert an ad
 * @param cardConfig The configuration of the card
 * @returns The card to insert at the ad slot if necessary
 */
export function createAdCardForTodayPageContextIfNecessary(objectGraph, pageContext, cardConfig) {
    var _a, _b, _c;
    if (!isAdPlacementEnabled(objectGraph, "today")) {
        return null;
    }
    // The ad should respect its slot, unless we're using reco logic for organic replacement. If we need to replace
    // the organic, we will arbitrarily respect the reco flag to avoid edge cases with mismatching data
    if (pageContext.adLocation !== pageContext.parsedCardCount &&
        pageContext.adPlacementBehavior !== models.AdPlacementBehavior.replaceOrganic) {
        return null;
    }
    (_a = pageContext.pageInformation.iAdInfo) === null || _a === void 0 ? void 0 : _a.updateContainerId((_b = pageContext.pageInformation.iAdInfo) === null || _b === void 0 ? void 0 : _b.containerIdForSlotIndex((_c = pageContext.parsedCardCount) !== null && _c !== void 0 ? _c : 0));
    const adCard = createTodayAdCard(objectGraph, pageContext.adData, pageContext.adIncidentRecorder, cardConfig, pageContext);
    if (isSome(adCard)) {
        return adCard;
    }
    else {
        return null;
    }
}
// MARK: - Shelf Header Content
/**
 * @param objectGraph The dependency graph of the app store
 * @param item The today item to look for the eyebrow in
 * @returns The text displayed above the title on a section header
 */
export function sectionEyebrowFromTodayItem(objectGraph, item) {
    const editorialClientParams = extractEditorialClientParams(objectGraph, item.data);
    if (item.isFirstItemInModule || editorialClientParams.suppressHeaderBadge) {
        return null;
    }
    let sectionBadge;
    const editorialCard = editorialCardFromData(item.data);
    if (hasAttributes(editorialCard)) {
        sectionBadge = mediaAttributes.attributeAsString(editorialCard, "headerBadge");
    }
    if (isSome(sectionBadge)) {
        return sectionBadge;
    }
    switch (item.data.type) {
        case "editorial-items":
            sectionBadge = attributeAsString(item.data, "headerBadge");
            break;
        case "editorial-item-groups":
            sectionBadge = attributeAsString(item.data, ["editorialNotes", "badge"]);
            break;
        default:
            break;
    }
    return sectionBadge;
}
/**
 * @param objectGraph The dependency graph of the app store
 * @param item The today item to look for the title in
 * @param alwaysReturnTitle If true, the title will be returned even if it is the first item in a module
 * @returns The title displayed above a section
 */
export function sectionTitleFromTodayItem(objectGraph, item, alwaysReturnTitle = false) {
    const editorialClientParams = extractEditorialClientParams(objectGraph, item.data);
    if ((item.isFirstItemInModule || editorialClientParams.suppressHeaderName) && !alwaysReturnTitle) {
        return null;
    }
    let sectionTitle;
    const editorialCard = editorialCardFromData(item.data);
    if (hasAttributes(editorialCard)) {
        sectionTitle = mediaAttributes.attributeAsString(editorialCard, "headerName");
    }
    if (isSome(sectionTitle)) {
        return sectionTitle;
    }
    switch (item.data.type) {
        case "editorial-items":
            sectionTitle = attributeAsString(item.data, "headerName");
            break;
        case "editorial-item-groups":
            sectionTitle = attributeAsString(item.data, ["editorialNotes", "name"]);
            break;
        default:
            break;
    }
    return sectionTitle;
}
/**
 * @param objectGraph The dependency graph of the app store
 * @param item The today item to look for the subtitle in
 * @returns The subtitle displayed below the title on a section header
 */
function sectionSubtitleFromTodayItem(objectGraph, item) {
    const editorialClientParams = extractEditorialClientParams(objectGraph, item.data);
    if (item.isFirstItemInModule || editorialClientParams.suppressHeaderTagline) {
        return null;
    }
    let sectionSubtitle;
    const editorialCard = editorialCardFromData(item.data);
    if (hasAttributes(editorialCard)) {
        sectionSubtitle = mediaAttributes.attributeAsString(editorialCard, "headerTagline");
    }
    if (isSome(sectionSubtitle)) {
        return sectionSubtitle;
    }
    switch (item.data.type) {
        case "editorial-items":
            sectionSubtitle = attributeAsString(item.data, "headerTagline");
            break;
        case "editorial-item-groups":
            sectionSubtitle = attributeAsString(item.data, ["editorialNotes", "tagline"]);
            break;
        default:
            break;
    }
    return sectionSubtitle;
}
/**
 * @param objectGraph The dependency graph of the app store
 * @param item The today item to look for the subtitle in
 * @returns The artwork displayed in the eyebrow of a today section.
 */
function sectionHeaderArtworkFromTodayItemForPlacement(objectGraph, item, placement) {
    var _a;
    const editorialClientParams = extractEditorialClientParams(objectGraph, item.data);
    const headerContents = relationshipData(objectGraph, item.data, "header-contents");
    const artworkBehavior = (_a = editorialClientParams.headerArtworkBehavior) !== null && _a !== void 0 ? _a : TodayHeaderArtworkBehavior.NoArtwork;
    switch (placement) {
        case TodaySectionHeaderArtworkPlacement.Eyebrow:
            switch (artworkBehavior) {
                case TodayHeaderArtworkBehavior.CategoryArtworkWithBadge:
                    return categoryArtworkFromData(objectGraph, headerContents);
                default:
                    return null;
            }
        case TodaySectionHeaderArtworkPlacement.Title:
            switch (artworkBehavior) {
                case TodayHeaderArtworkBehavior.CategoryArtworkWithTitle:
                    return categoryArtworkFromData(objectGraph, headerContents);
                case TodayHeaderArtworkBehavior.ContentArtworkWithTitle:
                    return iconFromData(objectGraph, headerContents, {
                        useCase: 1 /* ArtworkUseCase.LockupIconSmall */,
                    });
                default:
                    return null;
            }
        default:
            return null;
    }
}
/**
 * @param objectGraph The dependency graph of the app store
 * @param item The today item to look for the subtitle in
 * @returns The artwork type displayed in the eyebrow of a today section.
 */
function sectionHeaderArtworkTypeFromTodayItemForPlacement(objectGraph, item, placement) {
    const headerContents = relationshipData(objectGraph, item.data, "header-contents");
    const artworkBehavior = attributeAsString(item.data, [
        "editorialClientParams",
        "headerArtworkBehavior",
    ]);
    switch (placement) {
        case TodaySectionHeaderArtworkPlacement.Eyebrow:
            switch (artworkBehavior) {
                case TodayHeaderArtworkBehavior.CategoryArtworkWithBadge:
                    const hasCategoryArtwork = isSome(categoryArtworkFromData(objectGraph, headerContents));
                    return hasCategoryArtwork ? models.ShelfHeaderArtworkType.Category : null;
                default:
                    return null;
            }
        case TodaySectionHeaderArtworkPlacement.Title:
            switch (artworkBehavior) {
                case TodayHeaderArtworkBehavior.CategoryArtworkWithTitle:
                    const hasCategoryArtwork = isSome(categoryArtworkFromData(objectGraph, headerContents));
                    return hasCategoryArtwork ? models.ShelfHeaderArtworkType.Category : null;
                case TodayHeaderArtworkBehavior.ContentArtworkWithTitle:
                    const hasIconArtwork = isSome(categoryArtworkFromData(objectGraph, headerContents));
                    return hasIconArtwork ? models.ShelfHeaderArtworkType.Icon : null;
                default:
                    return null;
            }
        default:
            return null;
    }
}
/**
 *
 * @param objectGraph The dependency graph of the app store
 * @param data The MAPI data for the related content for a today item
 * @returns The artwork to use for the related content
 */
function categoryArtworkFromData(objectGraph, data) {
    const artworkData = categoryArtworkData(objectGraph, data, false, false, false);
    if (isNothing(artworkData)) {
        return null;
    }
    const artwork = artworkFromApiArtwork(objectGraph, artworkData, {
        useCase: 20 /* ArtworkUseCase.CategoryIcon */,
        allowingTransparency: true,
        cropCode: "sr",
    });
    return artwork;
}
/**
 * @param objectGraph The dependency graph for the App Store
 * @param todayItem The today item that contains the data for a editorial-item or editorial-item-group, this is used to look for
 *  editorialBackground, which can then be used to generate the section gradient background
 * @returns The background to use for the section
 */
function todaySectionEditorialBackground(objectGraph, todayItem) {
    const editorialBackground = mediaAttributes.attributeAsDictionary(todayItem.data, "editorialBackground", null);
    const editorialBackgroundType = editorialBackground === null || editorialBackground === void 0 ? void 0 : editorialBackground["type"];
    if (isNothing(editorialBackgroundType)) {
        return null;
    }
    let backgroundInfo = null;
    switch (editorialBackgroundType) {
        case EditorialBackgroundType.LinearGradient:
            const linearGradientData = asInterface(editorialBackground);
            const colors = linearGradientData.stops.map((stop) => color.fromHex(stop.color));
            const shelfBackground = {
                type: "gradient",
                colors: colors,
                start: models.ShelfBackgroundGradientLocation.Top,
                end: models.ShelfBackgroundGradientLocation.Bottom,
            };
            const isDark = color.isDarkColor(colors[0]);
            const secondaryLabelLightColor = {
                type: "rgb",
                red: 60 / 255,
                green: 60 / 255,
                blue: 67 / 255,
                alpha: 0.6,
            };
            const secondaryLabelDarkColor = {
                type: "rgb",
                red: 235.0 / 255,
                green: 235.0 / 255,
                blue: 245.0 / 255,
                alpha: 0.6,
            };
            backgroundInfo = {
                shelfBackground: shelfBackground,
                eyebrowColor: isDark ? secondaryLabelDarkColor : secondaryLabelLightColor,
                titleColor: isDark ? color.named("white") : color.named("black"),
                subtitleColor: isDark ? secondaryLabelDarkColor : secondaryLabelLightColor,
            };
            break;
        default:
            backgroundInfo = null;
            break;
    }
    return backgroundInfo;
}
/**
 * @param todayCards The today cards that are contained in the section
 * @returns The background to use for the section
 */
function todaySectionBackgroundForHeroDisplayStyle(todayCards) {
    const backgroundColors = todayCards
        .map((todayCard) => {
        return todayCard.media.bestBackgroundColor();
    })
        .filter((backgroundColor) => isSome(backgroundColor));
    let shelfBackground = null;
    if (backgroundColors.length > 0 && backgroundColors.length <= 4 && backgroundColors.length === todayCards.length) {
        switch (backgroundColors.length) {
            case 1:
                shelfBackground = {
                    type: "materialGradient",
                    colors: {
                        colorCount: "oneColor",
                        color: backgroundColors[0],
                    },
                };
                break;
            case 2:
                shelfBackground = {
                    type: "materialGradient",
                    colors: {
                        colorCount: "twoColor",
                        top: backgroundColors[0],
                        bottom: backgroundColors[1],
                    },
                };
                break;
            case 3:
                shelfBackground = {
                    type: "materialGradient",
                    colors: {
                        colorCount: "threeColor",
                        top: backgroundColors[0],
                        bottomLeading: backgroundColors[1],
                        bottomTrailing: backgroundColors[2],
                    },
                };
                break;
            case 4:
                shelfBackground = {
                    type: "materialGradient",
                    colors: {
                        colorCount: "fourColor",
                        topLeading: backgroundColors[0],
                        topTrailing: backgroundColors[1],
                        bottomLeading: backgroundColors[2],
                        bottomTrailing: backgroundColors[3],
                    },
                };
                break;
            default:
                break;
        }
    }
    else {
        shelfBackground = {
            type: "color",
            color: color.named("secondarySystemBackground"),
        };
    }
    return shelfBackground;
}
// MARK: - Metrics
/**
 * Retrieves the recommendation metrics data from a FlattenedTodayItem object.
 * @param todayItem - The FlattenedTodayItem object containing the module metadata.
 * @returns The recommendation metrics data as a JSONData object.
 */
export function recoMetricsFromTodayItem(todayItem) {
    var _a, _b;
    if (isNothing(todayItem)) {
        return {};
    }
    const recoMetricsData = (_a = asDictionary(todayItem.moduleMetadata, "meta.metrics")) !== null && _a !== void 0 ? _a : {};
    const combinedRecoMetricsData = (_b = combinedRecoMetricsDataFromMetricsData(recoMetricsData, todayItem.moduleMetadata.onDevicePersonalizationProcessingType, null)) !== null && _b !== void 0 ? _b : {};
    return combinedRecoMetricsData;
}
// MARK: - Debug Util
/**
 * Generate a todayCardPreview url that contains the entire original today feed.
 * @param objectGraph The dependency graph of the app store
 * @param todayItems The flattened today feed items
 * @returns The url to add to the today page for debug purposes
 */
export function feedPreviewUrlFromFlattenedTodayItems(objectGraph, todayItems) {
    switch (objectGraph.client.buildType) {
        case "debug":
        case "internal":
            const feedPreviewUrl = new URL();
            feedPreviewUrl.protocol = Protocol.https;
            feedPreviewUrl.host = "apps.apple.com";
            feedPreviewUrl.pathname = `/${Path.todayCardPreview}`;
            const idsParamValues = [];
            for (const item of todayItems) {
                switch (item.type) {
                    case FlattenedTodayItemType.EditorialItem:
                        idsParamValues.push(item.data.id);
                        break;
                    case FlattenedTodayItemType.EditorialItemGroup:
                        const groupItems = asArrayOrEmpty(item.data.meta, "associations.recommendations.data");
                        idsParamValues.push(`${item.data.id}:[${groupItems.map((groupItem) => groupItem.id).join(",")}]`);
                        break;
                    default:
                        break;
                }
            }
            feedPreviewUrl.param(Parameters.ids, idsParamValues.join(","));
            feedPreviewUrl.param(Parameters.isTodayFeedPreview, "true");
            return decodeURIComponent(feedPreviewUrl.build());
        default:
            return null;
    }
}
/**
 * Generate a todayCardPreview url that can display a single today card
 * @param objectGraph The dependency graph of the app store
 * @param todayCardId The id of the created today card
 * @param cardConfig The card config for the created today card
 * @returns The url to add to the today page for debug purposes
 */
export function todayCardPreviewUrlForTodayCard(objectGraph, todayCardId, cardConfig) {
    if (isNothing(todayCardId) || !objectGraph.client.isiOS) {
        return null;
    }
    switch (objectGraph.client.buildType) {
        case "debug":
        case "internal":
            const feedPreviewUrl = new URL();
            feedPreviewUrl.protocol = Protocol.https;
            feedPreviewUrl.host = "apps.apple.com";
            feedPreviewUrl.pathname = `/${Path.todayCardPreview}`;
            feedPreviewUrl.param(Parameters.ids, `${todayCardId}`);
            feedPreviewUrl.param(Parameters.isTodayFeedPreview, "true");
            feedPreviewUrl.param(Parameters.isTodaySection, cardConfig.useOTDTextStyle ? "true" : "false");
            return decodeURIComponent(feedPreviewUrl.build());
        default:
            return null;
    }
}
//# sourceMappingURL=today-parse-util.js.map