import { isNothing, isSome } from "@jet/environment";
import * as serverData from "../../../foundation/json-parsing/server-data";
import * as urls from "../../../foundation/network/urls";
import * as errors from "../../../foundation/util/errors";
import * as objects from "../../../foundation/util/objects";
import * as content from "../../content/content";
import * as deviceFamily from "../../content/device-family";
export function targetTypeForMetricsOptions(objectGraph, options) {
    let type = options.targetType;
    if (!type) {
        type = objectGraph.client.isVision ? "lockupSmall" : "lockup";
    }
    return type;
}
export function idTypeForMetricsOptions(options) {
    let type = options.idType;
    if (type === "none") {
        type = null;
    }
    else if (!type) {
        type = "its_id";
    }
    return type;
}
export function softwareTypeForData(objectGraph, data) {
    return content.isArcadeSupported(objectGraph, data) ? "Arcade" : null;
}
export function metricsKindFromData(objectGraph, data, attributePlatformOverride = undefined) {
    const type = serverData.asString(data, "type");
    const isMacType = deviceFamily.dataHasDeviceFamily(objectGraph, data, "mac", true);
    const isOnlyMacType = deviceFamily.dataOnlyHasDeviceFamily(objectGraph, data, "mac", true);
    const isIOSType = deviceFamily.dataHasAnyDeviceFamilies(objectGraph, data, ["iphone", "ipad", "ipod", "tvos", "watch"], true);
    const isOnlyIOSType = deviceFamily.dataOnlyHasDeviceFamilies(objectGraph, data, ["iphone", "ipad", "ipod", "tvos", "watch"], true);
    const isIOSDeviceType = objectGraph.client.isiOS || objectGraph.client.isTV || objectGraph.client.isWatch;
    const isAppleSiliconDeviceType = objectGraph.client.isMac && objectGraph.appleSilicon.isSupportEnabled;
    const isVisionDeviceType = objectGraph.client.isVision;
    const isVisionType = deviceFamily.dataHasDeviceFamily(objectGraph, data, "realityDevice", true);
    const isOnlyVisionType = deviceFamily.dataOnlyHasDeviceFamily(objectGraph, data, "realityDevice", true);
    // If:
    // - this is a mac only app, or
    // - it has multiple types but we're currently on the Mac, or
    // - it has multiple types and the plaform override is for Mac,
    // use the 'macSoftware' or 'macSoftwareBundle' kinds
    if (isOnlyMacType ||
        (isMacType && objectGraph.client.isMac) ||
        (isMacType && attributePlatformOverride === "osx")) {
        switch (type) {
            case "apps":
                return "macSoftware";
            case "app-bundles":
                return "macSoftwareBundle";
            default:
                break;
        }
    }
    // If:
    // - this is a Vision only app, or
    // - it has multiple types but we're currently on a Vision device, or
    // - it has multiple types and the platform override is for Vision,
    // use the 'visionSoftware' kind.
    if (isOnlyVisionType ||
        (isVisionType && objectGraph.client.isVision) ||
        (isVisionType && attributePlatformOverride === "xros")) {
        switch (type) {
            case "apps":
                return "visionSoftware";
            case "app-bundles":
                // To add if/when vision supports app bundles.
                break;
            default:
                break;
        }
    }
    // If this is an iOS only app or it has multiple types but we're currently:
    // - on an iOS device, or
    // - on an Apple Silicon Mac, or
    // - on a Vision device, or
    // - the platform override is for an iOS-like device
    // use the 'iosSoftware' or 'mobileSoftwareBundle' kinds
    if (isOnlyIOSType ||
        (isIOSType && isIOSDeviceType) ||
        (isIOSType && isAppleSiliconDeviceType) ||
        (isIOSType && isVisionDeviceType) ||
        (isIOSType && attributePlatformOverride === "ios") ||
        (isIOSType && attributePlatformOverride === "watch") ||
        (isIOSType && attributePlatformOverride === "appletvos")) {
        switch (type) {
            case "apps":
                return "iosSoftware";
            case "app-bundles":
                return "mobileSoftwareBundle";
            default:
                break;
        }
    }
    switch (type) {
        case "in-apps":
            return "softwareAddOn";
        case "groupings":
            return "grouping";
        case "editorial-elements":
        case "editorial-items":
            return "editorialItem";
        case "developers":
            return "artist";
        default:
            return null;
    }
}
export function emptyStringIfNullOrUndefined(object) {
    if (object === null || object === undefined) {
        return "";
    }
    return object;
}
export function extractSiriRefAppFromRefURL(urlString) {
    if (!urlString) {
        return null;
    }
    const refUrl = new urls.URL(urlString);
    let extracteRefApp = null;
    const query = refUrl.query;
    if (isSome(query)) {
        for (const key of Object.keys(query)) {
            if (key === "referrer") {
                if (query[key] === "siri") {
                    extracteRefApp = "com.apple.siri";
                }
                break;
            }
        }
    }
    return extracteRefApp;
}
export function sanitizedMetricsDictionary(dict) {
    var _a;
    if (isNothing(dict)) {
        return {};
    }
    return (_a = serverData.asInterface(sanitizeJson(serverData.asJSONData(dict)))) !== null && _a !== void 0 ? _a : {};
}
function sanitizeJson(json) {
    if (serverData.isNull(json)) {
        return null;
    }
    else if (json instanceof Array) {
        const arrayCopy = [];
        for (const value of json) {
            const sanitizedValue = sanitizeJson(value);
            if (serverData.isDefinedNonNull(sanitizedValue)) {
                arrayCopy.push(sanitizedValue);
            }
        }
        return arrayCopy;
    }
    else if (json instanceof Object) {
        const objectCopy = {};
        Object.keys(json).forEach((key, index, array) => {
            const value = json[key];
            const sanitizedValue = sanitizeJson(value);
            if (serverData.isDefinedNonNull(sanitizedValue)) {
                objectCopy[key] = sanitizedValue;
            }
        });
        return objectCopy;
    }
    return json;
}
export function searchTermFromRefURL(refUrlString) {
    if (!refUrlString) {
        return null;
    }
    const refUrl = new urls.URL(refUrlString);
    const queryItems = refUrl.query;
    const searchTerm = queryItems === null || queryItems === void 0 ? void 0 : queryItems["term"];
    const path = refUrl.pathname;
    if (serverData.isNull(searchTerm) || serverData.isNull(path)) {
        return null;
    }
    if (!path.endsWith("/search")) {
        return null;
    }
    // the url object has already urldecoded this query parameter
    const plainTerm = searchTerm;
    return plainTerm;
}
/**
 * Get a search term from a product URL, if one has been added.
 * @param productUrlString The URL of a product
 * @returns A string, if a search term exists.
 */
export function searchTermFromProductURL(productUrlString) {
    if (isNothing(productUrlString)) {
        return null;
    }
    const productUrl = new urls.URL(productUrlString);
    const queryItems = productUrl.query;
    const searchTerm = queryItems === null || queryItems === void 0 ? void 0 : queryItems["searchTerm"];
    const path = productUrl.pathname;
    if (isNothing(searchTerm) || isNothing(path)) {
        return null;
    }
    if (!path.includes("/app")) {
        return null;
    }
    const plainTerm = searchTerm;
    return plainTerm;
}
/**
 * Convert a product's data `type` and top lockup icon into a `MetricsPlatformDisplayStyle` object.
 * This is used to determine how an app icon is presented (i.e. as watch, as atv) for metrics.
 * @param objectGraph Current object graph
 * @param data Server data for the app
 * @param artwork The product's top lockup icon.
 * @param clientIdentifierOverride The preferred client identifier, if any.
 * @returns a MetricsPlatformDisplayStyle object.
 */
// <rdar://problem/47715014> Metrics: Send editorial intent buy param to finance for watch apps
export function metricsPlatformDisplayStyleFromData(objectGraph, data, artwork, clientIdentifierOverride) {
    if (!data || !artwork) {
        return "unknown";
    }
    if (data.type === "app-bundles") {
        return "bundle";
    }
    const artworkStyle = artwork.style;
    if (isNothing(artworkStyle)) {
        return "unknown";
    }
    switch (artworkStyle) {
        case "roundedRect":
        case "roundedRectPrerendered": {
            return "ios";
        }
        case "unadorned": {
            return "mac";
        }
        case "tvRect": {
            return "tv";
        }
        case "round":
        case "roundPrerendered": {
            const attributePlatform = content.iconAttributePlatform(objectGraph, data, clientIdentifierOverride !== null && clientIdentifierOverride !== void 0 ? clientIdentifierOverride : undefined);
            if (attributePlatform === "xros") {
                return "vision";
            }
            else {
                return "watch";
            }
        }
        case "pill": {
            return "messages";
        }
        case "iap": {
            return "iap";
        }
        default: {
            errors.unreachable(artworkStyle);
            return "unknown";
        }
    }
}
// region Search GhostHint
/**
 * Move ghostHint fields for click events
 * @param eventFields Fields of event to modify in place.
 */
export function adjustGhostHintFieldsForClick(eventFields) {
    /**
     * Copy `searchGhostHintPrefix` to `searchPrefix` if no prefix is present.
     * - JS-built search actions specify searchPrefix (matches prefixTerm of hint request).
     * - Native-built search actions don't specify searchPrefix (dynamic).
     */
    const existingSearchPrefix = serverData.asString(eventFields, "searchPrefix");
    const ghostHintPrefix = serverData.asString(eventFields, "searchGhostHintPrefix");
    if (serverData.isNull(existingSearchPrefix) && isSome(ghostHintPrefix) && (ghostHintPrefix === null || ghostHintPrefix === void 0 ? void 0 : ghostHintPrefix.length) > 0) {
        eventFields["searchPrefix"] = ghostHintPrefix;
    }
    /**
     * Delete `searchGhostHintTerm` if phase is pending (i.e. only send if displayed or rejected) per POR.
     */
    const ghostHintTermPhase = serverData.asString(eventFields, "searchGhostHintTermPhase");
    if (ghostHintTermPhase === "pending") {
        delete eventFields["searchGhostHintTerm"];
    }
}
/**
 * Move ghostHint fields for seach events
 * @param eventFields Fields of event to modify in place.
 */
export function adjustGhostHintFieldsForSearch(eventFields) {
    var _a;
    /**
     * Copy `searchGhostHintPrefix` to `searchPrefix` if no prefix is present.
     * - JS-built search actions specify actionDetails.searchPrefix (matches prefixTerm of hint request).
     * - Native-built search actions don't specify actionDetails.searchPrefix (dynamic).
     */
    const actionDetails = (_a = eventFields["actionDetails"]) !== null && _a !== void 0 ? _a : {};
    const existingSearchPrefix = actionDetails["searchPrefix"];
    const ghostHintPrefix = serverData.asString(eventFields, "searchGhostHintPrefix");
    if (serverData.isNull(existingSearchPrefix) && isSome(ghostHintPrefix) && (ghostHintPrefix === null || ghostHintPrefix === void 0 ? void 0 : ghostHintPrefix.length) > 0) {
        actionDetails["searchPrefix"] = ghostHintPrefix;
        eventFields["actionDetails"] = actionDetails;
    }
    /**
     * Delete `searchGhostHintTerm` if phase is pending (i.e. only send if displayed or rejected) per POR.
     */
    const ghostHintTermPhase = serverData.asString(eventFields, "searchGhostHintTermPhase");
    if (ghostHintTermPhase === "pending") {
        delete eventFields["searchGhostHintTerm"];
    }
    /**
     * Prune `searchGhostHintTerm` if `actionType` is `input`, i.e. is from hints page loading.
     * This is a side-effect of when the event is fired, and otherwise doesn't belong there.
     */
    if (eventFields["actionType"] === "input") {
        delete eventFields["searchGhostHintTerm"];
    }
}
/**
 * Clean up extraneous generated fields. These extra fields are speculative
 * to allow some additional JS customization if needed for SSS.
 * @param eventFields Event fields to modify in place.
 */
export function removeExtraGhostHintFields(eventFields) {
    // Prune prefix annotation.
    delete eventFields["searchGhostHintPrefix"];
    // Prune phase annotation.
    delete eventFields["searchGhostHintTermPhase"];
    // Prune historical annotation.
    delete eventFields["searchGhostHintTermLastDisplayed"];
}
// endregion
// region Arcade Upsell Marketing Items
/**
 * Returns a dictionary of fields pulled out from the meta.metrics dictionary associated with a marketing item response.
 * This data comes from Mercury, and we simply pull out relevant fields to be hoisted into the top-level base field on
 * metrics events (page/impression/click).
 * @param marketingItemData The marketing item response data.
 */
export function marketingItemTopLevelBaseFieldsFromData(objectGraph, marketingItemData) {
    if (!serverData.isDefinedNonNull(marketingItemData)) {
        return null;
    }
    const fieldsData = {};
    const marketingDictionary = serverData.asDictionary(marketingItemData, "meta.metrics");
    if (!serverData.isDefinedNonNullNonEmpty(marketingDictionary)) {
        return null;
    }
    const channelPartner = serverData.asString(marketingDictionary, "channelPartner");
    if (isSome(channelPartner) && (channelPartner === null || channelPartner === void 0 ? void 0 : channelPartner.length) > 0) {
        fieldsData["channelPartner"] = channelPartner;
    }
    const eligibilityType = serverData.asString(marketingDictionary, "eligibilityType");
    if (isSome(eligibilityType) && (eligibilityType === null || eligibilityType === void 0 ? void 0 : eligibilityType.length) > 0) {
        fieldsData["eligibilityType"] = eligibilityType;
    }
    const upsellScenario = serverData.asString(marketingDictionary, "upsellScenario");
    if (isSome(upsellScenario) && (upsellScenario === null || upsellScenario === void 0 ? void 0 : upsellScenario.length) > 0) {
        fieldsData["upsellScenario"] = upsellScenario;
    }
    fieldsData["marketing"] = {
        marketingItemId: marketingItemData.id,
    };
    return fieldsData;
}
// endregion
// region On Device Personalization
/**
 * Merges the provided reco metrics data with the on device personalization metrics data.
 * @param metricsData The input reco metrics data
 * @param onDevicePersonalizationProcessingType The type of processing that occurred on the data
 * @param onDevicePersonalizationMetricsData The metrics data provided by the on device personalization framework
 * @returns Reco metrics data, or null
 */
export function combinedRecoMetricsDataFromMetricsData(metricsData, onDevicePersonalizationProcessingType, onDevicePersonalizationMetricsData) {
    let combinedMetricsData = null;
    if (serverData.isDefinedNonNull(metricsData)) {
        combinedMetricsData = objects.shallowCopyOf(metricsData);
    }
    if (serverData.isDefinedNonNull(onDevicePersonalizationProcessingType)) {
        if (serverData.isNull(combinedMetricsData)) {
            combinedMetricsData = {};
        }
        combinedMetricsData["odpModuleUpdate"] = onDevicePersonalizationProcessingType.toString();
    }
    if (serverData.isDefinedNonNullNonEmpty(onDevicePersonalizationMetricsData)) {
        if (serverData.isNull(combinedMetricsData)) {
            combinedMetricsData = {};
        }
        combinedMetricsData["userSegment"] = onDevicePersonalizationMetricsData;
    }
    return combinedMetricsData;
}
// endregion
// region Metrics ID
/**
 * Clean up dsid fields. We want this as a last line of defense for removing dsid.
 * @param eventFields Event fields to modify in place.
 */
export function removeDSIDFields(eventFields) {
    // Prune dsid
    delete eventFields["dsid"];
    delete eventFields["DSID"];
}
// endregion
//# sourceMappingURL=util.js.map