/**
 * Handles fetching Ads for all on-device ad placements
 * This is currently SLP, Today, Product Page.
 *
 * # Ad fetching for on-device placements
 * On-Device placements are unpersonalized and rely on CDN caching. To show ads, we:
 * 1. Fetch from an on-device cache of ads (managed by PromotedContent framework) for the specific placement
 * 2. Fetching ad data from MAPI
 * 2. Stitch ad onto page data...
 */
import * as serverData from "../../foundation/json-parsing/server-data";
import { Request } from "../../foundation/media/data-fetching";
import { dataFromDataContainer } from "../../foundation/media/data-structure";
import { fetchData } from "../../foundation/media/network";
import { buildURLFromRequest } from "../../foundation/media/url-builder";
import { Parameters } from "../../foundation/network/url-constants";
import { offerDataFromData } from "../offers/offers";
import { productVariantDataForData, shouldFetchCustomAttributes } from "../product-page/product-page-variants";
import { todayTabODPTimeoutUseCase } from "../personalization/on-device-recommendations-today";
import { setTimeoutForRequestKey } from "../util/timeout-manager-util";
import { adLogger } from "../search/search-ads";
import * as adCommon from "./ad-common";
import { getSelectedCustomCreativeId } from "../search/custom-creative";
import { isSome } from "@jet/environment";
// region exports
/**
 * Fetch ads for the given placement type leveraging on-device ads cache.
 * @param objectGraph the object graph.
 * @param placementType the placement type to fetch the ad for.
 * @param adamId the adamId of the app for which the product page is being viewed, to provide a relevant ad. Only required for product page placements.
 * @returns a promise containing an ad.
 */
export async function fetchAds(objectGraph, placementType, adamId) {
    const timeout = adCommon.adFetchTimeoutForPlacement(objectGraph, placementType, false);
    const request = new Request(objectGraph);
    switch (placementType) {
        case "today":
            request.usingCustomAttributes(shouldFetchCustomAttributes(objectGraph));
            switch (adCommon.todayAdStyle(objectGraph)) {
                case "mediumLockup":
                    if (preprocessor.CARRY_BUILD || preprocessor.DEBUG_BUILD) {
                        if (objectGraph.featureFlags.isEnabled("aligned_region_artwork_2025A")) {
                            request.includingAttributes(["customScreenshotsByTypeForAd", "adCreativeArtwork"]);
                        }
                        else {
                            request.includingAttributes(["customScreenshotsByTypeForAd"]);
                        }
                    }
                    else {
                        request.includingAttributes(["customScreenshotsByTypeForAd"]);
                    }
                    break;
                default:
                    break;
            }
            break;
        case "productPageYMAL":
        case "productPageYMALDuringDownload":
            request.usingCustomAttributes(shouldFetchCustomAttributes(objectGraph));
            break;
        default:
            break;
    }
    /**
     * Ad not available content filtering
     */
    const adsOverrideLanguage = objectGraph.bag.adsOverrideLanguage;
    if (serverData.isDefinedNonNullNonEmpty(adsOverrideLanguage)) {
        request.enablingFeature("adsLocaleMetadata").addingQuery("l", adsOverrideLanguage);
    }
    const requestMetaFields = buildURLFromRequest(objectGraph, request).query;
    try {
        const onDeviceResponse = await objectGraph.ads.fetchOnDeviceAdPlacement(placementType, timeout, requestMetaFields, adamId);
        return await handleAdResponse(objectGraph, onDeviceResponse, placementType);
    }
    catch {
        return null;
    }
}
/**
 * Handle the response from an on device ad request.
 * @param objectGraph The App Store Object Graph.
 * @param onDeviceResponse The response from the on device ad fetcher.
 * @param placementType The placement this request was for.
 * @returns A promise containing an ad.
 */
async function handleAdResponse(objectGraph, onDeviceResponse, placementType) {
    var _a, _b, _c, _d, _e, _f;
    if (serverData.isNullOrEmpty(onDeviceResponse.clientRequestId)) {
        onDeviceResponse.clientRequestId = objectGraph.random.nextUUID();
        adLogger(objectGraph, `clientRequestId was nil. Assigned ${onDeviceResponse.clientRequestId}`);
    }
    const aggregateResponse = {
        clientRequestId: onDeviceResponse.clientRequestId,
        iAdId: onDeviceResponse.iAdId,
        placementType: (_b = (_a = onDeviceResponse === null || onDeviceResponse === void 0 ? void 0 : onDeviceResponse.ad) === null || _a === void 0 ? void 0 : _a.placementType) !== null && _b !== void 0 ? _b : placementType,
    };
    // Failed w/o Ad from device.
    if (onDeviceResponse.failureReason) {
        aggregateResponse.failureReason = onDeviceResponse.failureReason;
        return aggregateResponse;
    }
    // Set the basic ad info received on the response.
    aggregateResponse.onDeviceAd = onDeviceResponse.ad;
    // Ad requests should return with at least some basic app metadata.
    // Note: On pre-SydneyC builds, this is expected to be null for the SLP placement.
    let mediaResponse = (_c = onDeviceResponse.ad) === null || _c === void 0 ? void 0 : _c.appMetadata;
    // Get the currently available app data from the data container.
    const appData = dataFromDataContainer(objectGraph, mediaResponse);
    // We should only attempt to fetch the full app data if the app data provided to us via
    // Promoted Content is incomplete. This should only be the case for SLP ads - Chainlink
    // placements should arrive with complete app data.
    // We check a couple of attributes here as a way to be sure it's hydrated. Sometimes
    // a single attribute can be misleading.
    if (serverData.isNullOrEmpty((_d = appData === null || appData === void 0 ? void 0 : appData.attributes) === null || _d === void 0 ? void 0 : _d.name) ||
        serverData.isNullOrEmpty((_e = appData === null || appData === void 0 ? void 0 : appData.attributes) === null || _e === void 0 ? void 0 : _e.platformAttributes) ||
        serverData.isNullOrEmpty(offerDataFromData(objectGraph, appData))) {
        try {
            const adRequest = createRequestForOnDeviceAd(objectGraph, onDeviceResponse.ad);
            mediaResponse = await fetchData(objectGraph, adRequest);
        }
        catch (e) {
            adLogger(objectGraph, `fetchAds request failed - ${e}`);
            aggregateResponse.failureReason = "mapiFetchFail";
        }
    }
    // The app data should now be complete, set it on the response.
    if (serverData.isDefinedNonNullNonEmpty((_f = dataFromDataContainer(objectGraph, mediaResponse)) === null || _f === void 0 ? void 0 : _f.attributes)) {
        aggregateResponse.mediaResponse = decorateiAdAttributeFromOnDeviceAdResponse(objectGraph, mediaResponse, onDeviceResponse);
        // Check the localization is valid for the ad.
        if (!adCommon.isAdLocalizationValid(objectGraph, dataFromDataContainer(objectGraph, mediaResponse), aggregateResponse.onDeviceAd)) {
            adLogger(objectGraph, `fetchAds request failed - localization not available`);
            aggregateResponse.failureReason = "localizationNotAvailable";
        }
        const metadataFailReason = checkAppMetadataIsValidForPlacement(objectGraph, aggregateResponse, placementType);
        if (serverData.isDefinedNonNull(metadataFailReason)) {
            adLogger(objectGraph, `fetchAds request failed - ${metadataFailReason}`);
            aggregateResponse.failureReason = metadataFailReason;
        }
    }
    return aggregateResponse;
}
/**
 * Indicates that an organic request kicked off parallel to an ad fetch has completed.
 * This gives us an opportunity to enforce a timeout on the ad request for the time beyond the organic request.
 * @param objectGraph The object graph.
 * @param placementType The placement for which the parallel organic request finished.
 */
export function parallelOrganicRequestDidFinish(objectGraph, placementType) {
    const timeout = adCommon.adFetchTimeoutForPlacement(objectGraph, placementType, true);
    if (serverData.isNull(timeout)) {
        return;
    }
    objectGraph.ads.setTimeoutForCurrentOnDeviceAdFetch(placementType, timeout);
    setTimeoutForRequestKey(objectGraph, timeout, todayTabODPTimeoutUseCase);
}
// endregion
// region internals
/**
 * Create an request for on-device adverts
 * @param ad The on device ad to fetch MAPI data for.
 */
function createRequestForOnDeviceAd(objectGraph, ad) {
    const request = new Request(objectGraph)
        .withIdOfType(ad.adamId, "apps")
        .usingCustomAttributes(shouldFetchCustomAttributes(objectGraph))
        .includingAttributes(["customScreenshotsByTypeForAd"]);
    if (serverData.isDefinedNonNullNonEmpty(ad.cppIds)) {
        request.addingQuery(Parameters.productVariantID, ad.cppIds[0]);
    }
    // If there is an `adsOverrideLanguage`, attach it to this request too.
    const adsOverrideLanguage = objectGraph.bag.adsOverrideLanguage;
    if (serverData.isDefinedNonNullNonEmpty(adsOverrideLanguage)) {
        request.addingQuery("l", adsOverrideLanguage);
    }
    return request;
}
/**
 * Decorate `iad` attribute with contents of `OnDeviceAdvert`.
 *
 * @param mediaResponse The data from of MAPI request
 * @param ad Ad data that was fetched independent of response.
 */
function decorateiAdAttributeFromOnDeviceAdResponse(objectGraph, mediaResponse, adResponse) {
    const adData = dataFromDataContainer(objectGraph, mediaResponse);
    if (serverData.isNullOrEmpty(adData) || serverData.isNull(adData.attributes)) {
        adLogger(objectGraph, "decorateiAdAttributeFromOnDeviceAd cannot decorate for malformed response");
        return null; // The data is incompatible with `iad` decoration. Return `null` to let builder report error.
    }
    const onDeviceAd = adResponse.ad;
    const lineItem = `${onDeviceAd.adamId}|${onDeviceAd.metadata}`;
    // Create `IAdAttributes` and stitch onto `mediaResponse`
    const iadAttributes = {
        clientRequestId: adResponse.clientRequestId,
        impressionId: onDeviceAd.impressionId,
        metadata: onDeviceAd.metadata,
        privacy: onDeviceAd.privacy,
        lineItem: lineItem,
    };
    const metaContainer = dataFromDataContainer(objectGraph, onDeviceAd.appMetadata);
    if (serverData.isDefinedNonNullNonEmpty(adData.meta) &&
        serverData.isDefinedNonNullNonEmpty(metaContainer) &&
        serverData.isDefinedNonNullNonEmpty(metaContainer.meta)) {
        adData.meta.passthroughAdInfo = metaContainer.meta.passthroughAdInfo;
        if (isSome(onDeviceAd === null || onDeviceAd === void 0 ? void 0 : onDeviceAd.alignedRegionDetails)) {
            adData.meta.alignedRegionDetails = onDeviceAd === null || onDeviceAd === void 0 ? void 0 : onDeviceAd.alignedRegionDetails[0];
        }
    }
    switch (onDeviceAd.placementType) {
        case "today":
            // Enable images for specific placements.
            const imagesEnabled = adCommon.todayAdStyle(objectGraph) === "mediumLockup";
            iadAttributes.format = {
                images: imagesEnabled,
                text: "",
                userRating: false,
            };
            break;
        case "searchLanding":
            iadAttributes.format = {
                images: true,
                text: "",
                userRating: false,
            };
            break;
        default:
            break;
    }
    adData.attributes["iad"] = iadAttributes;
    adCommon.decorateAdInstanceIdOnData(objectGraph, adData, onDeviceAd.instanceId);
    return mediaResponse;
}
/**
 * Checks whether the metadata returned for the app is considered valid for the given placement.
 * Some ad placements have special rules to be shown. These rules are validated here.
 * @param objectGraph The App Store object graph.
 * @param adResponse The ad response.
 * @param placementType The placement the given ad data is to be placed in.
 * @returns A fail reason if there is one. Otherwise null.
 */
function checkAppMetadataIsValidForPlacement(objectGraph, adResponse, placementType) {
    switch (placementType) {
        case "today":
            return checkAppMetadataIsValidForToday(objectGraph, adResponse);
        default:
            return null;
    }
}
/**
 * Checks the metadata returned for the app is considered valid **specifically** for the Today placement.
 *
 * Today ad placements must:
 * - Include a valid cppId in the `meta` field of the data that matches the cppId in the ad result, and
 * - Have enough assets to fulfill the template requirement:
 *     - Portrait: at least 4 assets, one of which can be a video.
 *     - Landscape: at least 5 assets, one of which can be a video.
 *
 * @param objectGraph The App Store object graph.
 * @param adResponse The ad response.
 * @returns A fail reason if there is one. Otherwise null.
 */
function checkAppMetadataIsValidForToday(objectGraph, adResponse) {
    var _a, _b, _c;
    const adData = dataFromDataContainer(objectGraph, adResponse.mediaResponse);
    const productVariantData = productVariantDataForData(objectGraph, adData);
    const hasCPP = (_b = (_a = adResponse.onDeviceAd) === null || _a === void 0 ? void 0 : _a.cppIds) === null || _b === void 0 ? void 0 : _b.includes(productVariantData.productPageId);
    if (preprocessor.CARRY_BUILD || preprocessor.DEBUG_BUILD) {
        const alignedRegionDetails = (_c = adResponse.onDeviceAd) === null || _c === void 0 ? void 0 : _c.alignedRegionDetails;
        const creativeID = serverData.asString(alignedRegionDetails === null || alignedRegionDetails === void 0 ? void 0 : alignedRegionDetails[0], "apAssetId");
        const selectedCustomCreativeId = getSelectedCustomCreativeId(adData);
        const hasCreative = creativeID === selectedCustomCreativeId;
        // First check there is a cppId for the ad that matches the `meta`.
        if (!hasCPP && !hasCreative) {
            // Then check if there is appMetadata for the custom creative ad.
            return "cppAssetsMissing";
        }
    }
    else {
        if (!hasCPP) {
            return "cppAssetsMissing";
        }
    }
    return null;
}
// endregion
//# sourceMappingURL=on-device-ad-fetch.js.map