import { isSome } from "@jet/environment";
import * as models from "../../../api/models";
import * as serverData from "../../../foundation/json-parsing/server-data";
import * as mediaDataFetching from "../../../foundation/media/data-fetching";
import * as mediaNetwork from "../../../foundation/media/network";
import { ResponseMetadata } from "../../../foundation/network/network";
import { Parameters, Path, Protocol } from "../../../foundation/network/url-constants";
import * as urls from "../../../foundation/network/urls";
import * as mediaUrlMapping from "../../builders/url-mapping";
import { shouldUsePrerenderedIconArtwork } from "../../content/content";
import * as metricsHelpersImpressions from "../../metrics/helpers/impressions";
import * as metricsHelpersLocation from "../../metrics/helpers/location";
import * as impressionDemotion from "../../personalization/on-device-impression-demotion";
import * as productVariants from "../../product-page/product-page-variants";
import * as refresh from "../../refresh/page-refresh-controller";
import * as groupingShelfControllerCommon from "./grouping-shelf-controller-common";
/**
 * A GroupingShelfController is responsible for all the logic around parsing and rending
 * a single grouping page shelf.
 *
 * The `ShelfMetadata` is a type the is specific for a given shelf and has some additional data needed to render
 * that shelf.
 */
export class GroupingShelfController {
    // endregion
    // region AnyGroupingShelfController
    /**
     * Indicates whether this grouping shelf controller can create a shelf for the given mediaApiData.
     * @param objectGraph The App Store dependency graph
     * @param mediaApiData The outer data object containing the FC properties and data
     * @param featuredContentId The featured content id for this shelf data
     * @param nativeGroupingShelfId The id of the custom shelf type, one not defined on the server
     */
    supports(objectGraph, mediaApiData, featuredContentId, nativeGroupingShelfId) {
        return this._supports(objectGraph, mediaApiData, featuredContentId, nativeGroupingShelfId);
    }
    /**
     * Indicates whether this grouping shelf controller can create a shelf for the given mediaApiData.
     * @param objectGraph The App Store dependency graph
     * @param mediaApiData The outer data object containing the FC properties and data
     * @param featuredContentId The featured content id for this shelf data
     * @param nativeGroupingShelfId The id of the custom shelf type, one not defined on the server
     */
    _supports(objectGraph, mediaApiData, featuredContentId, nativeGroupingShelfId) {
        const isFeaturedContentIdSupported = this.supportedFeaturedContentIds.has(featuredContentId);
        let isNativeGroupingShelfIdSupported;
        if (serverData.isDefinedNonNull(nativeGroupingShelfId)) {
            isNativeGroupingShelfIdSupported = this.supportedNativeGroupingShelfIds.has(nativeGroupingShelfId);
        }
        else {
            isNativeGroupingShelfIdSupported = true;
        }
        return isFeaturedContentIdSupported && isNativeGroupingShelfIdSupported;
    }
    /**
     * This method will return a grouping page shelf regardless of the type of controller
     * @param objectGraph The App Store dependency graph
     * @param groupingParseContext The parse context for the grouping page so far
     * @param mediaApiData The outer data object containing the FC properties and data
     * @param baseShelfToken The base grouping shelf token created by the grouping-controller
     * @param baseMetricsOptions The minimum set of metrics options for this shelf, created by the
     * grouping page controller
     */
    createShelf(objectGraph, mediaApiData, groupingParseContext, baseShelfToken, baseMetricsOptions) {
        var _a, _b, _c;
        const typedMediaApiData = mediaApiData;
        const shelfData = this.initialShelfDataFromGroupingMediaApiData(objectGraph, typedMediaApiData);
        const shelfToken = this.shelfTokenFromBaseTokenAndMediaApiData(objectGraph, typedMediaApiData, baseShelfToken, groupingParseContext);
        const shelfMetricsOptions = this.shelfMetricsOptionsFromBaseMetricsOptions(objectGraph, shelfToken, baseMetricsOptions);
        const hasShelfMetricsOptions = serverData.isDefinedNonNullNonEmpty(shelfMetricsOptions);
        if (hasShelfMetricsOptions && this.shouldImpressShelf()) {
            metricsHelpersLocation.pushContentLocation(objectGraph, shelfMetricsOptions, shelfToken.title);
        }
        /// Reorder the shelf contents based on the impression data if available
        if (serverData.isDefinedNonNullNonEmpty(shelfData.shelfContents)) {
            shelfData.shelfContents = impressionDemotion.personalizeDataItems(shelfData.shelfContents, (_a = groupingParseContext.recoImpressionData) !== null && _a !== void 0 ? _a : {}, (_b = baseMetricsOptions === null || baseMetricsOptions === void 0 ? void 0 : baseMetricsOptions.recoMetricsData) !== null && _b !== void 0 ? _b : {});
        }
        const shelf = this._createShelf(objectGraph, shelfToken, shelfData, groupingParseContext);
        if (hasShelfMetricsOptions && this.shouldImpressShelf()) {
            metricsHelpersLocation.popLocation(shelfMetricsOptions.locationTracker);
            if (serverData.isDefinedNonNull(shelf)) {
                metricsHelpersImpressions.addImpressionFields(objectGraph, shelf, shelfMetricsOptions);
                // rdar://84952935 (Placeholder shelves are not being impressed
                // For placeholder shelves we end up replacing the entire shelf, so we need to make sure the original
                // impression metrics are included in the token, so they can be added when the real content is fetched
                // We're doing this here because this is where we decide whether the original shelf should be impressed
                if (((_c = shelf.url) === null || _c === void 0 ? void 0 : _c.length) > 0 &&
                    serverData.isDefinedNonNullNonEmpty(shelf.impressionMetrics) &&
                    shelfToken.showingPlaceholders) {
                    const originalShelfUrlString = shelf.url;
                    try {
                        // Extract the token from the URL.
                        // Note: Although we have access to the shelfToken here, we do not know that
                        //       the url was constructed from token in its current state. To be safe,
                        //       if not efficient, we reverse engineer the URL to get the token.
                        const originalShelfUrl = urls.URL.from(originalShelfUrlString);
                        const encodedToken = originalShelfUrl.pathComponents().pop();
                        const shelfTokenFromUrl = JSON.parse(decodeURIComponent(encodedToken));
                        // Modify the token to include the impressions metrics.
                        shelfTokenFromUrl.originalPlaceholderShelfImpressionMetrics = shelf.impressionMetrics;
                        groupingShelfControllerCommon.updateShelfUrlWithNewToken(objectGraph, shelf, shelfTokenFromUrl);
                    }
                    catch {
                        shelf.url = originalShelfUrlString;
                    }
                }
            }
        }
        this.finalizeInitialShelfForDisplay(objectGraph, shelf, shelfToken, shelfData, groupingParseContext);
        if (hasShelfMetricsOptions && this.shouldPrepareLocationTrackerForNextPosition()) {
            metricsHelpersLocation.nextPosition(groupingParseContext.metricsLocationTracker);
        }
        return shelf;
    }
    /**
     * Initialize a builder with globally unique name.
     *
     * @param {string} builderClass Globally unique name.
     */
    constructor(builderClass) {
        // region Supported Types
        this.supportedFeaturedContentIds = new Set([]);
        this.supportedNativeGroupingShelfIds = new Set([]);
        this.builderClass = builderClass;
    }
    /**
     * Determines the strategy for fetching incomplete shelves based on feature flags and shelf type
     *
     * @param objectGraph - The application store object graph.
     * @returns The strategy for fetching incomplete shelves, either on shelf appearance or on page load.
     */
    incompleteShelfFetchStrategy(objectGraph) {
        if (objectGraph.client.isiOS) {
            return models.IncompleteShelfFetchStrategy.OnShelfWillAppear;
        }
        else {
            return models.IncompleteShelfFetchStrategy.OnPageLoad;
        }
    }
    // endregion
    // region Metrics
    /**
     * Return the shelf metrics options to use for this specific shelf. Using the base options from the grouping
     * page controller
     *
     * @param objectGraph The App Store dependency graph
     * @param shelfToken The shelf shelfToken for this current shelf creation request
     * @param baseMetricsOptions The minimum set of metrics options for this shelf, created by the
     * grouping page controller
     */
    shelfMetricsOptionsFromBaseMetricsOptions(objectGraph, shelfToken, baseMetricsOptions) {
        return baseMetricsOptions;
    }
    /**
     * Whether the shelf itself should be impressed, there are some cases where the shelf itself
     * does not get impressed, just the contents.
     */
    shouldImpressShelf() {
        return true;
    }
    /**
     * Whether we should move the location tracker to the next position after creating our shelf
     */
    shouldPrepareLocationTrackerForNextPosition() {
        return true;
    }
    // endregion
    // Shelf Finalization
    /**
     * This method will set any required fields on our shelf once it has created as part of the initial page rendering.
     * This includes things like timing metrics, hiding empty shelves etc.
     *
     * @param objectGraph The App Store dependency graph
     * @param shelf The created shelf
     * @param shelfToken The shelf shelfToken for this current shelf creation request
     * @param shelfData The media api shelfContents array for this shelf
     * @param groupingParseContext The parse context for the grouping page so far
     * @private
     */
    finalizeInitialShelfForDisplay(objectGraph, shelf, shelfToken, shelfData, groupingParseContext) {
        var _a, _b;
        if (serverData.isNullOrEmpty(shelf)) {
            return;
        }
        // Should not show see all links on search groupings
        if (shelfToken.isSearchLandingPage) {
            groupingShelfControllerCommon.modifyShelfForSearchLandingGrouping(objectGraph, shelf, shelfToken);
        }
        if (((_a = shelf.url) === null || _a === void 0 ? void 0 : _a.length) > 0 &&
            serverData.isDefinedNonNullNonEmpty(groupingParseContext.additionalShelfParameters)) {
            shelf.url = urls.URL.from(shelf.url)
                .append("query", groupingParseContext.additionalShelfParameters)
                .build();
        }
        // If we're on iOS and no prior fetch strategy has been defined, set the fetchStrategy to OnShelfWillAppear.
        shelf.fetchStrategy = this.incompleteShelfFetchStrategy(objectGraph);
        // Shelf will fetch content after sending follow-up fetch request.
        const willFetchShelfContent = isSome(shelf) && ((_b = shelf.url) === null || _b === void 0 ? void 0 : _b.length) > 0;
        if (serverData.isNullOrEmpty(shelf.items) && !willFetchShelfContent) {
            shelf.isHidden = true;
        }
        shelf.accessibilityMetadata = createShelfAccessibilityMetadata(objectGraph, shelf);
    }
    /**
     * This method will set any required fields on our shelf once it has been fetched as a result of a secondary fetch.
     * This includes things like timing metrics, hiding empty shelves etc.
     *
     * @param objectGraph The AppStore dependency graph
     * @param shelf The created shelf
     * @param shelfToken The shelf shelfToken for this current shelf creation request
     * @param shelfData The media api shelfContents array for this shelf
     * @private
     */
    finalizeSecondaryShelfForDisplay(objectGraph, shelf, shelfToken, shelfData) {
        if (serverData.isNullOrEmpty(shelf)) {
            return;
        }
        if (shelfToken.remainingItems.length) {
            const remainingIds = shelfToken.remainingItems.map((data) => {
                return data.id;
            });
            objectGraph.console.warn("Could not load items for: " + remainingIds.join(","));
        }
        if (shelf) {
            shelf.mergeWhenFetched = groupingShelfControllerCommon.shelfFetchShouldMergeWhenFetched(objectGraph, shelfToken);
            shelf.networkTimingMetrics = shelfData.responseTimingValues;
            shelf.nextPreferredContentRefreshDate = refresh.nextPreferredContentRefreshDateForController(refresh.newPageRefreshController());
        }
        // Merge the original impression metrics with the newly created impression metrics.
        if (serverData.isDefinedNonNullNonEmpty(shelfToken.originalPlaceholderShelfImpressionMetrics)) {
            // If the `shelf.impressionMetrics` is null, we just defer to the original metrics.
            if (serverData.isNull(shelf.impressionMetrics)) {
                shelf.impressionMetrics = shelfToken.originalPlaceholderShelfImpressionMetrics;
            }
            else {
                for (const key in shelfToken.originalPlaceholderShelfImpressionMetrics.fields) {
                    if (Object.prototype.hasOwnProperty.call(shelfToken.originalPlaceholderShelfImpressionMetrics.fields, key)) {
                        shelf.impressionMetrics.fields[key] =
                            shelfToken.originalPlaceholderShelfImpressionMetrics.fields[key];
                    }
                }
            }
        }
        if (!shelfToken.hasExistingContent && serverData.isNullOrEmpty(shelf.items)) {
            shelf.isHidden = true;
        }
        // Should not show see all links on search groupings
        if (shelfToken.isSearchLandingPage) {
            groupingShelfControllerCommon.modifyShelfForSearchLandingGrouping(objectGraph, shelf, shelfToken);
        }
        shelf.accessibilityMetadata = createShelfAccessibilityMetadata(objectGraph, shelf);
    }
    // endregion
    // region ShelfBuilder
    async handleShelf(objectGraph, url, parameters, matchedRuleIdentifier) {
        const tokenJson = parameters["token"];
        const shelfToken = JSON.parse(tokenJson);
        shelfToken.isFirstRender = false;
        try {
            const shelfData = await this.secondaryShelfDataForShelfUrl(objectGraph, url, shelfToken, parameters);
            const shelf = this._createShelf(objectGraph, shelfToken, shelfData, null);
            this.finalizeSecondaryShelfForDisplay(objectGraph, shelf, shelfToken, shelfData);
            return shelf;
        }
        catch (error) {
            if (shelfToken && !shelfToken.hasExistingContent) {
                const hiddenShelf = new models.Shelf(shelfToken.shelfStyle);
                hiddenShelf.isHidden = true;
                return hiddenShelf;
            }
            else {
                throw error;
            }
        }
    }
    shelfRoute(objectGraph) {
        if (serverData.isDefinedNonNullNonEmpty(this.supportedNativeGroupingShelfIds)) {
            return routesForNativeGroupingShelfIds(this.supportedNativeGroupingShelfIds);
        }
        else {
            return routesForFeaturedContentIds(this.supportedFeaturedContentIds);
        }
    }
    // endregion
    // region Static Base Helpers
    /**
     * This is a standard default implementation for the secondary shelf data fetch. This can be used for all the
     * grouping shelf controls that dont implement a custom ShelfDataType
     * @param objectGraph
     * @param shelfUrl
     * @param parameters
     */
    static async secondaryGroupingShelfDataForShelfUrl(objectGraph, shelfUrl, shelfToken, parameters) {
        return await GroupingShelfController.secondaryGroupingShelfMediaApiData(objectGraph, shelfUrl, shelfToken, parameters).then((mediaApiData) => {
            const hydratedItems = groupingShelfControllerCommon.hydratedRemainingItemsForShelfTokenFromMediaApiData(objectGraph, shelfToken, mediaApiData);
            return {
                shelfContents: hydratedItems,
                responseTimingValues: mediaApiData[ResponseMetadata.timingValues],
            };
        });
    }
    /**
     * This is a standard default implementation for the media api request, for an incomplete grouping shelf.
     *
     * @param objectGraph
     * @param shelfUrl
     * @param parameters
     */
    static async secondaryGroupingShelfMediaApiData(objectGraph, shelfUrl, shelfToken, parameters) {
        const urlString = shelfUrl.build();
        let request;
        if (mediaUrlMapping.isMediaUrl(objectGraph, shelfUrl)) {
            request = new mediaDataFetching.Request(objectGraph, urlString);
        }
        else {
            request = groupingShelfControllerCommon.generateShelfRequest(objectGraph, shelfToken, parameters);
        }
        if (!request) {
            return await Promise.reject(new Error(`Could not construct media API request for: ${shelfUrl}`));
        }
        request.includingAdditionalPlatforms(defaultMediaApiPlatforms(objectGraph));
        request.includingAttributes(defaultMediaApiAttributes(objectGraph));
        request.usingCustomAttributes(productVariants.shouldFetchCustomAttributes(objectGraph));
        request.attributingTo(shelfUrl.build());
        return await mediaNetwork.fetchData(objectGraph, request).then((mediaApiData) => {
            groupingShelfControllerCommon.flushRequestedItemsFromShelfToken(shelfToken, request.ids);
            return mediaApiData;
        });
    }
}
export function createShelfAccessibilityMetadata(objectGraph, shelf) {
    var _a;
    let accessibilityLabel = objectGraph.loc.string("Shelves.Accessibility.Label");
    if (isSome(shelf.title)) {
        accessibilityLabel = `${shelf.title}, ${accessibilityLabel}`;
    }
    else if (isSome((_a = shelf.header) === null || _a === void 0 ? void 0 : _a.title)) {
        accessibilityLabel = `${shelf.header.title}, ${accessibilityLabel}`;
    }
    const accessibilityRoleDescription = objectGraph.loc.string("Shelves.Accessibility.RoleDescription");
    return {
        label: accessibilityLabel,
        roleDescription: accessibilityRoleDescription,
    };
}
function routeForFeaturedContentId(featuredContentId, nativeGroupingShelfId, additionalQueryParams) {
    const query = serverData.isDefinedNonNullNonEmpty(additionalQueryParams)
        ? [...additionalQueryParams]
        : [];
    query.push(`${Parameters.groupingFeaturedContentId}=${featuredContentId}`);
    if (serverData.isDefinedNonNullNonEmpty(nativeGroupingShelfId)) {
        query.push(`${Parameters.nativeGroupingShelfId}=${nativeGroupingShelfId}`);
    }
    return {
        protocol: Protocol.internal,
        path: `/${Path.grouping}/${Path.shelf}/{token}`,
        query: query,
    };
}
export function routesForFeaturedContentIds(featuredContentIds, additonalQueryParams) {
    const routes = [];
    for (const featuredContentId of featuredContentIds) {
        routes.push(routeForFeaturedContentId(featuredContentId, null, additonalQueryParams));
    }
    return routes;
}
export function routesForNativeGroupingShelfIds(nativeGroupingShelfIds, additonalQueryParams) {
    const routes = [];
    for (const nativeGroupingShelfId of nativeGroupingShelfIds) {
        routes.push(routeForFeaturedContentId(-1 /* FeaturedContentID.Native_GroupingShelf */, nativeGroupingShelfId, additonalQueryParams));
    }
    return routes;
}
// region Media Api Attributes
function defaultMediaApiPlatforms(objectGraph) {
    return mediaDataFetching.defaultAdditionalPlatformsForClient(objectGraph);
}
function defaultMediaApiAttributes(objectGraph) {
    const attributes = ["editorialArtwork", "isAppleWatchSupported", "requiredCapabilities", "badge-content"];
    if (objectGraph.appleSilicon.isSupportEnabled) {
        attributes.push("macRequiredCapabilities");
    }
    if (objectGraph.client.isMac) {
        attributes.push("hasMacIPAPackage");
    }
    if (objectGraph.bag.enableUpdatedAgeRatings) {
        attributes.push("ageRating");
    }
    if (shouldUsePrerenderedIconArtwork(objectGraph)) {
        attributes.push("iconArtwork");
    }
    return attributes;
}
// endregion
//# sourceMappingURL=grouping-shelf-controller.js.map