/**
 * Common utilities for dealing with Ads.
 */
import { isNothing, isSome } from "@jet/environment";
import { Lockup, TodayCard, TodayCardMediaMediumLockupWithAlignedRegion, TodayCardMediaMediumLockupWithScreenshots, TodayCardMediaSingleLockup, } from "../../api/models";
import { isDefinedNonNull, isDefinedNonNullNonEmpty, isNull, isNullOrEmpty, } from "../../foundation/json-parsing/server-data";
import { attributeAsString } from "../../foundation/media/attributes";
import { IAdSearchInformation } from "../metrics/helpers/models";
import { platformSupportsAdverts } from "../search/search-ads";
import { shouldTodayAdBeCondensed } from "../../foundation/experimentation/today-ad-experiments";
import { getTemplateTypeForMediumAdFromLockupWithCustomCreative, getTemplateTypeForMediumAdFromLockupWithScreenshots, searchAdMissedOpportunityFromId, } from "../lockups/ad-lockups";
import { contentAttributeAsString } from "../content/attributes";
import { currentLocation, currentPosition } from "../metrics/helpers/location";
// region placement logic
/**
 * A helper function to check if the given AdPlacement is enabled.
 * @param objectGraph The object graph.
 * @param adPlacementKey The ad placement to check. Matches values sent in the bag.
 * @returns A boolean indicating if the provided AdPlacement is enabled.
 */
export function isAdPlacementEnabled(objectGraph, placementType) {
    if (!platformSupportsAdverts(objectGraph)) {
        return false;
    }
    switch (placementType) {
        case "searchLanding":
            // In transitioning from the legacy bag key `isSearchLandingAdsEnabled` to the new key `enabledAdPlacements`,
            // we will enable SLP ads if either of the keys indicates ads are enabled for SLP.
            const slpPlacementBagValue = adPlacementBagValueForAdPlacementType(placementType);
            if (isSome(slpPlacementBagValue)) {
                return (objectGraph.bag.isSearchLandingAdsEnabled ||
                    objectGraph.bag.enabledAdPlacements.includes(slpPlacementBagValue));
            }
            else {
                return objectGraph.bag.isSearchLandingAdsEnabled;
            }
        case "searchResults":
            return true; // Legacy
        case "today":
            const todayBagValue = adPlacementBagValueForAdPlacementType(placementType);
            if (isNull(todayBagValue)) {
                return false;
            }
            return objectGraph.bag.enabledAdPlacements.includes(todayBagValue) && isSome(todayAdStyle(objectGraph));
        case "productPageYMAL":
        case "productPageYMALDuringDownload":
            const placementBagValue = adPlacementBagValueForAdPlacementType(placementType);
            if (isNull(placementBagValue)) {
                return false;
            }
            return objectGraph.bag.enabledAdPlacements.includes(placementBagValue);
        default:
            return false;
    }
}
/**
 * Get the style to use for the Today ad.
 * A return type of `undefined` indicates the ad style is unsupported on the client.
 * @param objectGraph The Object Graph.
 * @returns A style, or undefined if it's unsupported.
 */
export function todayAdStyle(objectGraph) {
    if (objectGraph.bag.todayAdMediumLockupScreenshotEnabled) {
        return "mediumLockup";
    }
    if (objectGraph.bag.todayAdCondensedEnabled) {
        // Only supported on iPhone.
        if (!objectGraph.client.isPhone) {
            return undefined;
        }
        return "singleLockup";
    }
    if (shouldTodayAdBeCondensed(objectGraph)) {
        return "singleLockup";
    }
    else {
        return undefined;
    }
}
/**
 * Get the timeout of the ad fetch for a given placement from the bag values.
 * @param objectGraph The object graph.
 * @param placementType The ad placement.
 * @param isDeltaTimeout Whether the timeout we're looking for is a delta timeout between parallel organic and ad requests completing.
 * See `Ads.setTimeoutForCurrentOnDeviceAdFetch` for more details on how this is used.
 * @returns The timeout.
 */
export function adFetchTimeoutForPlacement(objectGraph, placementType, isDeltaTimeout) {
    var _a, _b, _c, _d, _e;
    const timeouts = objectGraph.bag.adPlacementTimeouts;
    const defaultTimeout = 0.3;
    switch (placementType) {
        case "searchResults":
            return isDeltaTimeout ? null : (_a = timeouts === null || timeouts === void 0 ? void 0 : timeouts["search-results-in-seconds"]) !== null && _a !== void 0 ? _a : defaultTimeout;
        case "searchLanding":
            return isDeltaTimeout ? null : (_b = objectGraph.bag.searchLandingAdFetchTimeout) !== null && _b !== void 0 ? _b : defaultTimeout; // Legacy value.
        case "today":
            // Today uses its timeout value as a delta after the organic request finishes.
            return isDeltaTimeout ? (_c = timeouts === null || timeouts === void 0 ? void 0 : timeouts["today-in-seconds"]) !== null && _c !== void 0 ? _c : defaultTimeout : null;
        case "productPageYMAL":
            return isDeltaTimeout ? null : (_d = timeouts === null || timeouts === void 0 ? void 0 : timeouts["product-page-ymal-in-seconds"]) !== null && _d !== void 0 ? _d : defaultTimeout;
        case "productPageYMALDuringDownload":
            return isDeltaTimeout ? null : (_e = timeouts === null || timeouts === void 0 ? void 0 : timeouts["product-page-ymal-during-download-in-seconds"]) !== null && _e !== void 0 ? _e : defaultTimeout;
        default:
            return defaultTimeout;
    }
}
/**
 * Convert the value describing an ad placement from the App Store representation to the bag representation.
 * @param placementType The bag value for different ad placements
 * @returns The equivalent bag placement value as a string.
 */
function adPlacementBagValueForAdPlacementType(placementType) {
    switch (placementType) {
        case "searchResults":
            return "search-results";
        case "searchLanding":
            return "search-landing";
        case "today":
            return "today";
        case "productPageYMAL":
            return "product-page-ymal";
        case "productPageYMALDuringDownload":
            return "product-page-ymal-during-download";
        default:
            return undefined;
    }
}
/**
 * The minimum number of landscape media items for a Today ad placement.
 * The app must satisfy this or `todayAdPlacementMinimumPortraitMedia` to be shown.
 */
export const todayAdPlacementMinimumLandscapeMedia = 5;
/**
 * The minimum portrait media for a Today ad placement.
 * The app must satisfy this or `todayAdPlacementMinimumLandscapeMedia` to be shown.
 */
export const todayAdPlacementMinimumPortraitMedia = 4;
/**
 * Get a count of the media in each orientation specifically for a Today ad placement.
 * This is specific to Today because the placement only supports one video.
 * @param trailers A set of trailers for an app.
 * @param screenshots A set of screenshots for an app.
 * @returns A count of media in each orientation, respecting the placement's rules.
 */
export function getMediaOrientationCountsForTodayPlacement(trailers, screenshots) {
    var _a, _b, _c, _d;
    // Grab the first array of platform screenshots and trailers - the ones the ad will display.
    const platformScreenshotsArtwork = (_b = (_a = screenshots[0]) === null || _a === void 0 ? void 0 : _a.artwork) !== null && _b !== void 0 ? _b : [];
    const platformTrailersVideos = (_d = (_c = trailers[0]) === null || _c === void 0 ? void 0 : _c.videos) !== null && _d !== void 0 ? _d : [];
    // Split media into orientations.
    const portraitScreenshots = platformScreenshotsArtwork.filter((artwork) => artwork.isPortrait());
    const landscapeScreenshots = platformScreenshotsArtwork.filter((artwork) => artwork.isLandscape());
    // Take a maximum of one video of each orientation.
    const portraitVideos = platformTrailersVideos.filter((video) => video.preview.isPortrait()).slice(0, 1);
    const landscapeVideos = platformTrailersVideos.filter((video) => video.preview.isLandscape()).slice(0, 1);
    const portraitCount = portraitScreenshots.length + portraitVideos.length;
    const landscapeCount = landscapeScreenshots.length + landscapeVideos.length;
    return {
        landscape: landscapeCount,
        portrait: portraitCount,
    };
}
/**
 * A convenience method to determine if a position is ad eligible.
 * @param placementType The type of placement for an ad.
 * @param locationTracker The location tracker being used to build the items. Used to determine the current shelf and position within the shelf.
 * @returns A boolean indicating if the current position is considered ad eligible.
 */
export function isAdEligible(placementType, locationTracker) {
    var _a;
    if (isNothing(locationTracker) || isNothing(placementType)) {
        return false;
    }
    const shelfId = (_a = currentLocation(locationTracker)) === null || _a === void 0 ? void 0 : _a.id;
    if (isNothing(shelfId)) {
        return false;
    }
    const eligibleIndex = adEligibleIndexForType(placementType, shelfId);
    if (isNothing(eligibleIndex)) {
        return false;
    }
    const index = currentPosition(locationTracker);
    return index === eligibleIndex;
}
/**
 * Inserts a missed opportunity on the correct slot in the page if an ad isn't already present. This looks for
 * types conforming to SearchAdOpportunityProviding and handles all of the construction for the metadata associated
 * with a SearchAdOpportunity.
 * @param objectGraph The object graph.
 * @param shelves Completed shelves that are constructed for the page.
 * @param placementType The location where the ad will be shown.
 * @param shelfIdentifier The specific shelf identifier we should be evaluating against. This is not inferred to support things like Today Page that can use a shelf-per-item.
 * @param pageInformation Metrics information for the page.
 */
export function applySearchAdMissedOpportunityToShelvesIfNeeded(objectGraph, shelves, placementType, shelfIdentifier, pageInformation) {
    var _a, _b, _c;
    if (!platformSupportsAdverts(objectGraph) || isNull(pageInformation.iAdInfo)) {
        return;
    }
    const adEligibleIndex = adEligibleIndexForType(placementType, shelfIdentifier);
    if (isNothing(adEligibleIndex)) {
        return;
    }
    // Opportunities aren't present when we've recorded certain missed opportunities
    let missedOpportunityReason = null;
    if (typeof pageInformation.iAdInfo.pageFields.iAdMissedOpportunityReason === "string") {
        missedOpportunityReason = pageInformation.iAdInfo.pageFields.iAdMissedOpportunityReason;
    }
    if (isNothing(missedOpportunityReason) ||
        missedOpportunityReason.length === 0 ||
        missedOpportunityReason === "EDITORIALTAKEOVER" ||
        missedOpportunityReason === "SLPLOAD") {
        return;
    }
    const allShelfItems = [];
    for (const shelf of shelves) {
        // searchResult is handled in a separate non-shelf workflow
        const isValidContentType = shelf.contentType === "smallLockup" || shelf.contentType === "todayCard";
        if (!isValidContentType) {
            continue;
        }
        const nextShelfItems = shelf.items;
        if (isDefinedNonNull(nextShelfItems) && nextShelfItems.length > 0) {
            allShelfItems.push(...nextShelfItems);
        }
    }
    if (allShelfItems.length <= adEligibleIndex) {
        return;
    }
    const adEligibleShelfItem = allShelfItems[adEligibleIndex];
    const isTodayCard = adEligibleShelfItem instanceof TodayCard;
    const isSmallLockup = adEligibleShelfItem instanceof Lockup;
    const shelfItemMedia = isTodayCard ? adEligibleShelfItem.media : null;
    const lockupTypeIsMediumScreenshotFormat = isDefinedNonNull(shelfItemMedia) && shelfItemMedia instanceof TodayCardMediaMediumLockupWithScreenshots;
    const lockupTypeIsMediumCreativeFormat = isDefinedNonNull(shelfItemMedia) && shelfItemMedia instanceof TodayCardMediaMediumLockupWithAlignedRegion;
    const lockupHasPlacedCondensedTodayAd = isDefinedNonNull(shelfItemMedia) &&
        shelfItemMedia instanceof TodayCardMediaSingleLockup &&
        isDefinedNonNull(shelfItemMedia.condensedAdLockupWithIconBackground.lockup.searchAdOpportunity);
    const lockupHasPlacedMediumAdScreenshots = lockupTypeIsMediumScreenshotFormat &&
        isDefinedNonNull(shelfItemMedia.mediumAdLockupWithScreenshotsBackground.lockup.searchAdOpportunity);
    const lockupHasPlacedMediumAdCreative = lockupTypeIsMediumCreativeFormat &&
        isDefinedNonNull(shelfItemMedia.mediumAdLockupWithAlignedRegionBackground.lockup.searchAdOpportunity);
    const lockupHasPlacedSmallLockup = isSmallLockup && isDefinedNonNull(adEligibleShelfItem.searchAdOpportunity);
    // If we've already processed the ad data and placed it, we don't want to indicate that there's a missed opportunity
    if (lockupHasPlacedCondensedTodayAd ||
        lockupHasPlacedMediumAdScreenshots ||
        lockupHasPlacedMediumAdCreative ||
        lockupHasPlacedSmallLockup) {
        return;
    }
    adEligibleShelfItem.searchAdOpportunity = searchAdMissedOpportunityFromId(objectGraph, pageInformation);
    (_a = adEligibleShelfItem.searchAdOpportunity) === null || _a === void 0 ? void 0 : _a.setMissedOpportunityReason(missedOpportunityReason !== null && missedOpportunityReason !== void 0 ? missedOpportunityReason : "NOAD");
    if (lockupTypeIsMediumScreenshotFormat) {
        (_b = adEligibleShelfItem.searchAdOpportunity) === null || _b === void 0 ? void 0 : _b.setTemplateType(getTemplateTypeForMediumAdFromLockupWithScreenshots(shelfItemMedia.mediumAdLockupWithScreenshotsBackground.screenshots[0]));
    }
    else if (lockupTypeIsMediumCreativeFormat) {
        adEligibleShelfItem.searchAdOpportunity.setTemplateType(getTemplateTypeForMediumAdFromLockupWithCustomCreative());
    }
    else {
        (_c = adEligibleShelfItem.searchAdOpportunity) === null || _c === void 0 ? void 0 : _c.setTemplateType("APPLOCKUP");
    }
}
/**
 * This is a bit of a workaround for `isAdEligible` function in this file. Ideally this is bag-driven, but I need to figure out
 * how to make it work. `shelfIdentifier` doesn't seem to work correctly, except for the YMAL ad on PP
 * @param placementType The ad placement type to evaluate for ad eligibility
 * @param shelfIdentifier The identifier for the shelf to evaluate for ad eligibility
 * @returns The equivalent bag placement value as a string.
 */
function adEligibleIndexForType(placementType, shelfIdentifier) {
    var _a;
    const adEligibleBagRepresentation = {
        today: [
            {
                shelfIdentifier: "today",
                adEligibleIndex: 1,
            },
        ],
        productPageYMAL: [
            {
                shelfIdentifier: "customers-also-bought-apps",
                adEligibleIndex: 0,
            },
        ],
        searchLanding: [
            {
                shelfIdentifier: "R8802",
                adEligibleIndex: 0,
            },
        ],
        searchResults: [
            {
                shelfIdentifier: "search-results",
                adEligibleIndex: 0,
            },
        ],
    };
    const matchingSlot = ((_a = adEligibleBagRepresentation[placementType]) !== null && _a !== void 0 ? _a : []).find((element) => {
        return element.shelfIdentifier === shelfIdentifier;
    });
    if (isDefinedNonNullNonEmpty(matchingSlot) && isDefinedNonNull(matchingSlot.adEligibleIndex)) {
        return matchingSlot.adEligibleIndex;
    }
    else {
        return undefined;
    }
}
// endregion
// region iad data
export function iadInfoFromOnDeviceAdResponse(objectGraph, placementType, adResponse, flattenedTodayFeed = null) {
    var _a, _b;
    if (!platformSupportsAdverts(objectGraph) || isNull(adResponse)) {
        return null;
    }
    return new IAdSearchInformation(objectGraph, placementType, IAdSearchInformation.createInitialSlotInfos(objectGraph, placementType, (_a = adResponse === null || adResponse === void 0 ? void 0 : adResponse.onDeviceAd) === null || _a === void 0 ? void 0 : _a.positionInfo, flattenedTodayFeed), adResponse.iAdId, adResponse.clientRequestId, undefined, (_b = adResponse.onDeviceAd) === null || _b === void 0 ? void 0 : _b.positionInfo);
}
/**
 * Key added to `Data`'s `attributes` field so downstream builders can use this field.
 * This is currently generated by JS when we create `SponsoredSearchAdverts`
 */
export const instanceIdAttributeKey = "jet_native_advert_instanceid";
/**
 * Returns the native advert id if one was annotated during `applyNativeAdvertData`
 */
export function advertInstanceIdForData(objectGraph, data) {
    return attributeAsString(data, instanceIdAttributeKey);
}
/**
 * Decorates `instanceId` that identifies an ad to given `Data` for consumption in builders.
 */
export function decorateAdInstanceIdOnData(objectGraph, data, instanceId) {
    if (isDefinedNonNullNonEmpty(data === null || data === void 0 ? void 0 : data.attributes)) {
        data.attributes[instanceIdAttributeKey] = instanceId;
    }
}
// endregion
// region ad localization
/**
 * Whether the available localization data in an ad is valid for the defined `adsOverrideLanguage`, if any.
 * If no `adsOverrideLanguage` is set, `true` is returned.
 * The logic for a valid set of data differs based on the passed in placement and if an ad display style
 * is defined (for SRP only).
 * @param objectGraph The Object Graph.
 * @param data The app data.
 * @param onDeviceAd Optionally, an `OnDeviceAdvert` - this is only used as a fallback for SLP ads,
 * where the meta resource object is attached to the original ad request, but not to the subsequent
 * hydration request. For this case only, we must use a combination of these two requests to identify
 * whether the localization is valid.
 * @param adDisplayStyle: Optionally, an ad display style - this is used for SRP ads, where the data used
 * in the ad UI depends on which ad display style is being used.
 * @returns A boolean indicating if the available localization data is valid.
 */
export function isAdLocalizationValid(objectGraph, data, onDeviceAd, adDisplayStyle) {
    var _a, _b, _c, _d, _e;
    const adsOverrideLanguage = objectGraph.bag.adsOverrideLanguage;
    // No localization requirements if there is no adsOverrideLanguage.
    if (isNullOrEmpty(adsOverrideLanguage) || isNullOrEmpty(data)) {
        return true;
    }
    let metaResource = (_a = data.meta) === null || _a === void 0 ? void 0 : _a.resource;
    // If the `metaResource in `data` is missing, attempt to fall back to the `onDeviceAd` values
    // which are provided for SLP.
    if (isNullOrEmpty(metaResource) && isDefinedNonNullNonEmpty(onDeviceAd)) {
        metaResource = (_e = (_d = (_c = (_b = onDeviceAd === null || onDeviceAd === void 0 ? void 0 : onDeviceAd.appMetadata) === null || _b === void 0 ? void 0 : _b.data) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.meta) === null || _e === void 0 ? void 0 : _e.resource;
    }
    // If `metaResource` still don't exist, we can't validate localization.
    if (isNullOrEmpty(metaResource)) {
        // The meta resource attributes object is missing, assume we don't have the correct localizations.
        return false;
    }
    // Title
    // Title is always used. Check it's localized.
    const titleLocale = attributeAsString(metaResource, "name.locale");
    if (titleLocale !== adsOverrideLanguage) {
        return false;
    }
    // Subtitle
    // Subtitle is constructed using either the subtitle field, or falling back to the category.
    // If the subtitle text is present, we need to validate the locale. If not, we don't.
    const subtitle = contentAttributeAsString(objectGraph, data, "subtitle");
    const subtitleLocale = contentAttributeAsString(objectGraph, metaResource, "subtitle.locale");
    if (isDefinedNonNullNonEmpty(subtitle) && subtitleLocale !== adsOverrideLanguage) {
        return false;
    }
    // SRP advertising text
    // SRP ads can optionally provide a 3rd set of text, `advertisingText`.
    // It's only used when the `adDisplayStyle` is `TEXT`.
    if (adDisplayStyle === "TEXT" /* SearchAdDisplayStyle.TEXT */) {
        // The text used can be defined by the ad payload inside the `data`. We need to use that path
        // to get both the text and the locale for that text.
        const iAdTextKey = attributeAsString(data, "iad.format.text");
        if (isSome(iAdTextKey) && iAdTextKey !== "none") {
            let attributePath;
            const attributePathLocale = iAdTextKey;
            // In the case where the path is "description", we need to use different paths for the text and the locale.
            // This is due to a Media API limitation where the locale for "description" is just under that key, rather
            // than "description.standard", which is the actual path for the text content.
            if (iAdTextKey === "description") {
                attributePath = "description.standard";
            }
            else {
                attributePath = iAdTextKey;
            }
            const advertisingText = contentAttributeAsString(objectGraph, data, attributePath);
            const advertisingTextLocale = contentAttributeAsString(objectGraph, metaResource, attributePathLocale.concat(".locale"));
            if (isDefinedNonNullNonEmpty(advertisingText) && advertisingTextLocale !== adsOverrideLanguage) {
                return false;
            }
        }
    }
    return true;
}
// endregion
// region metrics
/**
 * Retrieve the eligible slot positions for a given ad placement type.
 * @param objectGraph The Object Graph.
 * @param placementType The placement the ad is for.
 * @returns An array of eligible slot positions for the provided placement.
 */
export function eligibleSlotPositionsForAdPlacement(objectGraph, placementType) {
    if (isNothing(placementType)) {
        return undefined;
    }
    const placementTypeBagValue = adPlacementBagValueForAdPlacementType(placementType);
    if (isNothing(placementTypeBagValue)) {
        return undefined;
    }
    return objectGraph.bag.adPlacementEligibleSlotPositions[placementTypeBagValue];
}
// endregion
//# sourceMappingURL=ad-common.js.map