/**
 * Created by joel on 11/4/2018.
 */
import { isNothing, isSome } from "@jet/environment/types/optional";
import * as serverData from "../json-parsing/server-data";
import * as urls from "../network/urls";
import * as attributes from "./attributes";
/// this is exposed for compatibility. If you find yourself needing to use this outside of the media api module you
/// probably have code smell. DO NOT USE.
export function buildURLFromRequest(objectGraph, request) {
    var _a, _b;
    const baseURL = request.href && request.href.length > 0
        ? baseURLForHref(request.href)
        : baseURLForResourceType(objectGraph, request.isMixedMediaRequest, request.resourceType, request.countryCodeOverride);
    const mediaApiURL = new urls.URL(baseURL);
    if (serverData.isDefinedNonNullNonEmpty(request.resourceType)) {
        for (const pathComponent of pathComponentsForRequest(request.resourceType, request.targetResourceType)) {
            mediaApiURL.append("pathname", pathComponent);
        }
    }
    if (request.isMixedMediaRequest) {
        for (const [resourceType, ids] of request.idsByResourceType.entries()) {
            mediaApiURL.param(`ids[${resourceType}]`, Array.from(ids).sort().join(","));
        }
    }
    else if (request.ids.size > 1 || request.useIdsAsQueryParam) {
        mediaApiURL.param("ids", Array.from(request.ids).sort().join(","));
    }
    else if (request.ids.size === 1) {
        const id = request.ids.values().next().value;
        mediaApiURL.append("pathname", id);
    }
    if (request.resourceType !== undefined) {
        const trailingPathComponent = trailingPathComponentForResourceType(request.resourceType);
        if (serverData.isDefinedNonNullNonEmpty(trailingPathComponent)) {
            mediaApiURL.append("pathname", trailingPathComponent);
        }
    }
    mediaApiURL.param("platform", (_a = request.platform) !== null && _a !== void 0 ? _a : undefined);
    if (request.additionalPlatforms.size > 0) {
        mediaApiURL.param("additionalPlatforms", Array.from(request.additionalPlatforms).sort().join(","));
    }
    /**
     * Add `extend` attributes.
     * Note that when `useCustomAttributes` is true, there is `customArtwork` param even when `attributeIncludes` is initially empty.
     * This due MAPI auto-extend for `artwork`, and lack of auto-extend for `customArtwork`
     */
    if (request.attributeIncludes.size > 0 || request.useCustomAttributes) {
        let extendAttributes = Array.from(request.attributeIncludes);
        if (request.useCustomAttributes) {
            extendAttributes = convertRequestAttributesToCustomAttributes(objectGraph, extendAttributes);
        }
        extendAttributes.sort();
        mediaApiURL.param("extend", extendAttributes.join(","));
    }
    // Add age restriction if present.
    if (serverData.isDefinedNonNull(request.ageRestriction) && objectGraph.bag.enableAgeRatingFilter) {
        mediaApiURL.param("restrict[ageRestriction]", request.ageRestriction.toString());
    }
    // Automatically extend iOS catalog requests for apps to include appBinaryTraits.
    if (request.includeAppBinaryTraitsAttribute) {
        request.includingScopedAttributes("apps", ["appBinaryTraits"]);
    }
    if (serverData.isDefinedNonNull(request.scopedAttributeIncludes)) {
        for (const [dataType, scopedIncludes] of request.scopedAttributeIncludes.entries()) {
            mediaApiURL.param(`extend[${dataType}]`, Array.from(scopedIncludes).sort().join(","));
        }
    }
    if (request.relationshipIncludes.size > 0) {
        mediaApiURL.param("include", Array.from(request.relationshipIncludes).sort().join(","));
    }
    if (serverData.isDefinedNonNull(request.scopedRelationshipIncludes)) {
        for (const [dataType, scopedIncludes] of request.scopedRelationshipIncludes.entries()) {
            mediaApiURL.param(`include[${dataType}]`, Array.from(scopedIncludes).sort().join(","));
        }
    }
    if (serverData.isDefinedNonNull(request.metaIncludes)) {
        for (const [dataType, scopedMeta] of request.metaIncludes.entries()) {
            mediaApiURL.param(`meta[${dataType}]`, Array.from(scopedMeta).sort().join(","));
        }
    }
    if (serverData.isSetDefinedNonNullNonEmpty(request.viewsIncludes)) {
        mediaApiURL.param("views", Array.from(request.viewsIncludes).sort().join(","));
    }
    if (serverData.isDefinedNonNull(request.kindIncludes)) {
        for (const [dataType, scopedMeta] of request.kindIncludes.entries()) {
            mediaApiURL.param(`kinds[${dataType}]`, Array.from(scopedMeta).sort().join(","));
        }
    }
    if (serverData.isDefinedNonNull(request.associateIncludes)) {
        for (const [dataType, scopedAssociate] of request.associateIncludes.entries()) {
            mediaApiURL.param(`associate[${dataType}]`, Array.from(scopedAssociate).sort().join(","));
        }
    }
    if (serverData.isDefinedNonNull(request.scopedAvailableInIncludes)) {
        for (const [dataType, scopedAvailableIn] of request.scopedAvailableInIncludes.entries()) {
            mediaApiURL.param(`availableIn[${dataType}]`, Array.from(scopedAvailableIn).sort().join(","));
        }
    }
    if (serverData.isDefinedNonNullNonEmpty(request.fields)) {
        let extendedFields = Array.from(request.fields);
        if (request.useCustomAttributes) {
            extendedFields = convertRequestFieldsToCustomFields(extendedFields);
        }
        request.fields.sort();
        mediaApiURL.param("fields", extendedFields.join(","));
    }
    if (serverData.isDefinedNonNull(request.limit) && request.limit > 0) {
        mediaApiURL.param(`limit`, `${request.limit}`);
    }
    if (serverData.isDefinedNonNull(request.sparseLimit)) {
        mediaApiURL.param(`sparseLimit`, `${request.sparseLimit}`);
    }
    if (serverData.isDefinedNonNull(request.scopedSparseLimit)) {
        for (const [dataType, scopedLimit] of request.scopedSparseLimit.entries()) {
            mediaApiURL.param(`sparseLimit[${dataType}]`, String(scopedLimit));
        }
    }
    if (serverData.isDefinedNonNull(request.sparseCount)) {
        mediaApiURL.param(`sparseCount`, `${request.sparseCount}`);
    }
    for (const relationshipID of Object.keys(request.relationshipLimits).sort()) {
        const limit = request.relationshipLimits[relationshipID];
        mediaApiURL.param(`limit[${relationshipID}]`, `${limit}`);
    }
    if (serverData.isDefinedNonNullNonEmpty(request.additionalQuery)) {
        mediaApiURL.append("query", request.additionalQuery);
    }
    if (serverData.isDefinedNonNullNonEmpty(request.searchTerm)) {
        // Search hints shouldn't add `search` to the end of the path name as the correct final path
        // is `v1/catalog/us/search/suggestions`, which is handled by `trailingPathComponentForResourceType()`
        // Search hints also shouldn't have the bubble param
        if (isNothing(request.resourceType) || request.resourceType !== "search-hints") {
            mediaApiURL.append("pathname", "search");
            mediaApiURL.param("bubble[search]", request.searchTypes.join(","));
        }
        mediaApiURL.param("term", request.searchTerm);
    }
    if (serverData.isDefinedNonNullNonEmpty(request.enabledFeatures)) {
        mediaApiURL.param("with", request.enabledFeatures.join(","));
    }
    if (serverData.isDefinedNonNullNonEmpty(request.context)) {
        mediaApiURL.param("contexts", request.context);
    }
    if (serverData.isDefinedNonNullNonEmpty(request.filterType) &&
        serverData.isDefinedNonNullNonEmpty(request.filterValue)) {
        mediaApiURL.param(`filter[${request.filterType}]`, request.filterValue);
    }
    const language = objectGraph.bag.mediaApiLanguage;
    // Only attach the language query param if:
    // - there is a language available in the bag, and
    // - a language has not been manually attached to the request. This is used for special situations to override the language for particular content,
    // so it should take precedence over the default.
    if (serverData.isDefinedNonNull(language) && serverData.isNull(request.additionalQuery["l"])) {
        mediaApiURL.param("l", language);
    }
    mediaApiURL.host = (_b = hostForUrl(objectGraph, mediaApiURL, request)) !== null && _b !== void 0 ? _b : undefined;
    mediaApiURL.protocol = "https";
    return mediaApiURL;
}
/**
 * Get the media api base url for all requests.
 * @param objectGraph Current object graph
 * @param isMixedCatalogRequest Whether the request intends to use mixed catalog
 * @param type The request resource type
 * @param overrideCountryCode A country code to override the bag value.
 * @returns A built base URL string
 */
function baseURLForResourceType(objectGraph, isMixedCatalogRequest, type, overrideCountryCode) {
    switch (type) {
        case "personalization-data":
        case "reviews":
        case "app-distribution":
            return `/v1/${endpointTypeForResourceType(type)}/`;
        default:
            const countryCode = isSome(overrideCountryCode) && overrideCountryCode.length > 0
                ? overrideCountryCode
                : objectGraph.bag.mediaCountryCode;
            const baseURL = `/v1/${endpointTypeForResourceType(type)}/${countryCode}`;
            return isMixedCatalogRequest ? baseURL : `${baseURL}/`;
    }
}
/**
 * Get the media api base url for all requests that already have an href.
 * @return {string}
 */
function baseURLForHref(href) {
    return href;
}
function endpointTypeForResourceType(type) {
    switch (type) {
        case "apps":
        case "app-events":
        case "arcade-apps":
        case "app-bundles":
        case "charts":
        case "contents":
        case "developers":
        case "eula":
        case "in-apps":
        case "multiple-system-operators":
        case "user-reviews":
        case "customers-also-bought-apps-with-download-intent":
            return "catalog";
        case "categories":
        case "editorial-pages":
        case "editorial-items":
        case "editorial-item-groups":
        case "editorial-elements":
        case "groupings":
        case "multiplex":
        case "multirooms":
        case "rooms":
        case "today":
        case "collections":
            return "editorial";
        case "ratings":
            return "ratings";
        case "personalization-data":
        case "reviews":
            return "me";
        case "upsellMarketingItem":
        case "landing":
            return "engagement";
        case "landing:new-protocol":
            return "recommendations";
        case "personal-recommendations":
            return "recommendations";
        case "engagement-data":
            return "engagement";
        case "app-distribution":
            return "listing";
        default:
            return "catalog";
    }
}
/**
 * The path component to add for the given resource
 */
function pathComponentsForRequest(resourceType, targetResourceType) {
    switch (resourceType) {
        case "eula":
            if (targetResourceType === undefined) {
                return [resourceType]; // Might be modelled better as an error.
            }
            else {
                return [resourceType, targetResourceType];
            }
        case "landing:new-protocol":
            return [];
        case "landing":
            if (targetResourceType === undefined) {
                return ["search", resourceType]; // Might be modelled better as an error.
            }
            else {
                return ["search", resourceType, targetResourceType];
            }
        case "user-reviews":
            return ["apps"];
        case "reviews":
            return ["reviews", "apps"];
        case "multiplex":
            return ["multiplex"];
        case "upsellMarketingItem":
            return ["upsell", "marketing-items"];
        case "trending-contents":
            return ["search", resourceType];
        case "customers-also-bought-apps-with-download-intent":
            return ["apps"];
        case "searchLanding:see-all":
            return [];
        case "search-hints":
            return [];
        case "app-distribution":
            return ["apps"];
        default:
            return [resourceType];
    }
}
/**
 * Add a component to the end of the path for the given resource
 */
function trailingPathComponentForResourceType(type) {
    switch (type) {
        case "user-reviews":
            return "reviews";
        case "customers-also-bought-apps-with-download-intent":
            return "view/customers-also-bought-apps-with-download-intent";
        case "collections":
            return "contents";
        case "searchLanding:see-all":
            return "view/see-all";
        case "search-hints":
            return "search/suggestions";
        default:
            return null;
    }
}
function hostForUrl(objectGraph, url, request) {
    var _a;
    const path = (_a = url.pathname) !== null && _a !== void 0 ? _a : "";
    let host = null;
    if (request.isStorePreviewRequest) {
        host = objectGraph.bag.mediaPreviewHost;
    }
    else if (request.isMediaRealmRequest) {
        host = objectGraph.bag.mediaRealmHost;
    }
    else if (path.includes("search/landing")) {
        // Special case <rdar://problem/50185140> RFW3: Use bag key "apps-media-api-edge-end-points" for "search/landing" end-point
        // until we figure out a better way to test the paths
        const useEdgeForSearchLanding = objectGraph.bag.edgeEndpoints.indexOf("landing") !== -1;
        host = useEdgeForSearchLanding ? objectGraph.bag.mediaEdgeHost(objectGraph) : objectGraph.bag.mediaHost;
    }
    else if (request.resourceType === "app-distribution" && isSome(objectGraph.bag.appDistributionMediaAPIHost)) {
        host = objectGraph.bag.appDistributionMediaAPIHost;
    }
    else if (request.isMixedMediaRequest && objectGraph.bag.mediaAPICatalogMixedShouldUseEdge) {
        // CatalogMixed endpoint should be routed to edge when the bag is enabled.
        host = objectGraph.bag.mediaEdgeHost(objectGraph);
    }
    else if (objectGraph.bag.edgeEndpoints.map((endpoint) => path.includes(endpoint)).reduce(truthReducer, false)) {
        if (path.includes("search") && !path.includes("view/see-all")) {
            host = objectGraph.bag.mediaEdgeSearchHost;
        }
        else {
            host = objectGraph.bag.mediaEdgeHost(objectGraph);
        }
    }
    else {
        host = objectGraph.bag.mediaHost;
    }
    if (serverData.isNull(host)) {
        host = "api.apps.apple.com";
    }
    return host;
}
const truthReducer = (accumulator, current) => accumulator || current;
/**
 * Performs a conversion for given attribute to fetch the customAttribute variant of it.
 * @param objectGraph The object graph
 * @param attribute Attribute to convert if needed, e.g. `artwork`
 * @returns `string` attribute that is custom equivalent of `attribute`, or `attribute` unmodified.
 */
function convertRequestAttributesToCustomAttributes(objectGraph, requestAttributes) {
    const convertedAttributes = requestAttributes.map((attribute) => {
        var _a;
        return (_a = attributes.attributeKeyAsCustomAttributeKey(attribute)) !== null && _a !== void 0 ? _a : attribute;
    });
    /**
     * `artwork` is an autoincluded resources, so `attributes` usually doesn't contain this explicitly :(
     * Per MAPI contract, we "autoinclude" `customArtwork` explicitly for requests with custon attributes.
     */
    convertedAttributes.push("customArtwork");
    /**
     * `iconArtwork` is not autoincluded, but we need to ensure it is always requested even alongside its
     * custom counterpart, `customIconArtwork`. This is because we might be viewing a macOS only app on iOS,
     * where custom attributes are supported, but not available for macOS apps.
     */
    if (requestAttributes.includes("iconArtwork")) {
        convertedAttributes.push("iconArtwork");
    }
    /**
     * `customDeepLink` is always desired as an included resource in case an app decides to use a custom tap destination.
     * Per MAPI contract, we "autoinclude" `customDeepLink` explicitly for all requests with custom attributes.
     */
    convertedAttributes.push("customDeepLink");
    return convertedAttributes;
}
/**
 * Performs the conversion for given field value (which may specify `attributes` keys) to customAttribute variant of it.
 */
function convertRequestFieldsToCustomFields(requestFields) {
    const convertedFields = requestFields.map((fieldName) => {
        var _a;
        return (_a = attributes.attributeKeyAsCustomAttributeKey(fieldName)) !== null && _a !== void 0 ? _a : fieldName;
    });
    // DON'T include `customArtwork` for request `field` conversion. Only specify if `artwork` was initially in `requestFields`.
    return convertedFields;
}
//# sourceMappingURL=url-builder.js.map