"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getContentTypeLabel = exports.setContentType = void 0;
exports.getCategory = getCategory;
exports.getTypes = getTypes;
exports.getTypeCategories = getTypeCategories;
exports.getContentIdentifier = getContentIdentifier;
exports.itemToContent = itemToContent;
exports.datasetToContent = datasetToContent;
exports.datasetToItem = datasetToItem;
exports.getServiceStatus = getServiceStatus;
const tslib_1 = require("tslib");
const collections_1 = require("../collections");
const categories_1 = require("../categories");
const internalContentUtils_1 = require("./_internal/internalContentUtils");
const util_1 = require("../util");
const compose_1 = require("./compose");
const get_family_1 = require("./get-family");
const slugs_1 = require("./slugs");
const arcgis_rest_feature_service_1 = require("@esri/arcgis-rest-feature-service");
const is_service_1 = require("../resources/is-service");
const getItemIdentifier_1 = require("../items/getItemIdentifier");
const slugConverters_1 = require("../items/_internal/slugConverters");
const get_prop_1 = require("../objects/get-prop");
const feature_service_urls_1 = require("../urls/feature-service-urls");
const includes_1 = require("../utils/includes");
const wait_1 = require("../utils/wait");
// TODO: remove this at next breaking version
/**
 * ```js
 * import { getCategory } from "@esri/hub-common";
 * //
 * getCategory('Feature Layer')
 * > 'dataset'
 * ```
 * **DEPRECATED: Use getFamily() instead**
 * returns the Hub category for a given item type
 * @param itemType The ArcGIS [item type](https://developers.arcgis.com/rest/users-groups-and-items/items-and-item-types.htm).
 * @returns the category of a given item type.
 */
/* istanbul ignore next deprecated */
function getCategory(itemType = "") {
    /* tslint:disable no-console */
    console.warn("DEPRECATED: Use getFamily() instead. getCategory will be removed at the next breaking version");
    /* tslint:enable no-console */
    const collection = (0, collections_1.getCollection)(itemType);
    // for backwards compatibility
    return collection === "feedback" ? "app" : collection;
}
/**
 * ```js
 * import { getTypes } from "@esri/hub-common";
 * //
 * getTypes('site')
 * > [ 'hub site application' ]
 * ```
 * To do.
 * @param category The ArcGIS Hub category.
 * @returns all the item types for the given category.
 *
 */
function getTypes(category = "") {
    return categories_1.categories[category.toLowerCase()];
}
/**
 * ```js
 * import { getTypeCategories } from "@esri/hub-common";
 * //
 * getTypeCategories(item)
 * > [ 'Hub Site Application' ]
 * ```
 * **DEPRECATED: getTypeCategories will be removed at the next breaking version**
 * @param item Item object.
 * @returns typeCategory of the input item.
 *
 */
/* istanbul ignore next deprecated */
function getTypeCategories(item = {}) {
    /* tslint:disable no-console */
    console.warn("DEPRECATED: getTypeCategories will be removed at the next breaking version");
    /* tslint:enable no-console */
    const type = (0, compose_1.normalizeItemType)(item);
    const category = getCategory(type);
    if (category) {
        // upper case first letter and return as element in array for backwards compatibility
        const chars = Array.from(category);
        chars[0] = chars[0].toUpperCase();
        return [chars.join("")];
    }
    else {
        return ["Other"];
    }
}
/**
 * ```js
 * import { getContentIdentifier } from "@esri/hub-common";
 * //
 * getContentIdentifier(content, site)
 * > 'f12hhjk32' // id
 * // OR
 * > 'content-slug' // human-readable slug
 * ```
 * Returns the preferred identifier for a piece of content (determined by content type):
 * - Content from the 'template' and 'feedback' families return the standard id field
 * - Pages that are linked to the site parameter will return the slug defined by the site. Otherwise, the page id will be returned
 * - All other content will return the highest available item in the following hierarchy:
 *   1. slug - includes org prefix if the site parameter is a portal or has an orgKey different from the slug prefix
 *   2. hubId
 *   3. id
 * @param content The IHubContent item
 * @param site The site to compare content against
 * @returns the preferred id for the given content.
 */
function getContentIdentifier(content, site) {
    // We don't currently support slugs for hub initiative templates, solutions or surveys
    if ((0, includes_1.includes)(["template", "feedback"], content.family)) {
        return content.id;
    }
    // If it is a hub page linked to a site, return the page slug at the
    // site data instead. Because this one is the original one that was used
    // to create the page url (not mutable once created) and the slug (below)
    // generated by the hub-indexer could simply change with page name.
    if ((0, internalContentUtils_1.isPageType)(content.type, content.typeKeywords)) {
        // check if the page is linked to the current site by the old site pages hash
        const pages = (0, get_prop_1.getProp)(site, "data.values.pages") || [];
        // if so, return the page slug otherwise the page id
        const page = pages.find((p) => p.id === content.id);
        if (page) {
            return page.slug;
        }
        // if not, use the slug if configured (per brolly, without org prefix; and per AT without the id suffix) and if not, the id
        const pageIdentifier = (0, getItemIdentifier_1.getItemIdentifier)(content.item);
        return pageIdentifier.split(slugConverters_1.SLUG_ORG_SEPARATOR_URI).pop();
    }
    // If a slug is present, always return it
    if (content.slug) {
        let slug;
        const orgKey = (0, get_prop_1.getProp)(site, "domainInfo.orgKey");
        // Use namespaced slug when on the umbrella site
        if ((0, get_prop_1.getProp)(site, "data.values.isUmbrella")) {
            slug = content.slug;
        }
        else {
            // Use shortened slug if the slug's namespace is the same as the orgKey
            slug = (0, slugs_1.removeContextFromSlug)(content.slug, orgKey);
        }
        return slug;
    }
    return content.hubId || content.id;
}
/**
 * Convert a Portal item to Hub content
 *
 * @param item Portal Item
 * @returns Hub content
 * @export
 */
function itemToContent(item) {
    return (0, compose_1.composeContent)(item);
}
/**
 * Convert a Hub API dataset resource to Hub Content
 *
 * @param {DatasetResource} Dataset resource
 * @returns {IHubContent} Hub content object
 * @export
 */
function datasetToContent(dataset) {
    // extract item from dataset, create content from the item
    const item = datasetToItem(dataset);
    // extract enrichments from attributes
    const { 
    // item enrichments
    errors, boundary, metadata, slug, groupIds, orgId, orgName, organization, orgExtent, 
    // map and feature server enrichments
    server, layers, layer, recordCount, statistics, 
    // additional attributes needed
    extent, searchDescription, } = dataset.attributes;
    // get the layerId from the layer
    const layerId = layer && layer.id;
    // re-assemble the org as an enrichment
    const org = orgId && {
        id: orgId,
        name: orgName || organization,
        extent: orgExtent,
    };
    // compose a content out of the above
    return (0, compose_1.composeContent)(item, {
        layerId,
        slug,
        errors,
        // setting this to null signals to enrichMetadata to skip this
        metadata: metadata || null,
        groupIds,
        org,
        server,
        layers,
        recordCount,
        boundary,
        extent,
        searchDescription,
        statistics,
    });
}
/**
 * Convert a Hub API dataset resource to a portal item
 *
 * @param {DatasetResource} Dataset resource
 * @returns {IItem} portal item
 * @export
 */
function datasetToItem(dataset) {
    if (!dataset) {
        return;
    }
    const { id, attributes } = dataset;
    if (!attributes) {
        return;
    }
    // parse item id
    const { itemId } = (0, slugs_1.parseDatasetId)(id);
    // read item properties from attributes
    // NOTE: we attempt to read all item properties
    // even though some may not be currently returned
    const { 
    // start w/ item properties from
    // https://developers.arcgis.com/rest/users-groups-and-items/item.htm
    owner, orgId, created, 
    // the Hub API returns item.modified in attributes.itemModified (below)
    modified, 
    // NOTE: we use attributes.name to store the title or the service/layer name
    // but in Portal name is only used for file types to store the file name (read only)
    name, title, type, typeKeywords, description, snippet, tags, thumbnail, 
    // the Hub API returns item.extent in attributes.itemExtent (below)
    // extent,
    categories, contentStatus, 
    // the Hub API doesn't currently return spatialReference
    spatialReference, 
    // the Hub API doesn't currently return accessInformation
    accessInformation, licenseInfo, culture, url, access, 
    // the Hub API doesn't currently return proxyFilter
    proxyFilter, properties, 
    // the Hub API doesn't currently return appCategories, industries,
    // languages, largeThumbnail, banner, screenshots, listed, ownerFolder
    appCategories, industries, languages, largeThumbnail, banner, screenshots, listed, ownerFolder, size, 
    // the Hub API doesn't currently return protected
    protected: isProtected, commentsEnabled, 
    // the Hub API doesn't currently return numComments, numRatings,
    // avgRating, numViews, itemControl, scoreCompleteness
    numComments, numRatings, avgRating, numViews, itemControl, scoreCompleteness, 
    // additional attributes we'll need
    // to derive the above values when missing
    itemExtent, itemModified, modifiedProvenance, serviceSpatialReference, } = attributes;
    // layer datasets will get their type from the layer
    // so we will need to derive the item type from the URL
    const serviceType = url && (0, feature_service_urls_1.getServiceTypeFromUrl)(url);
    // build and return an item from properties
    // NOTE: we currently do NOT provide default values
    // (i.e. null for scalar attributes, [] for arrays, etc)
    // for attributes that are not returned by the Hub API
    // this helps distinguish an item that comes from the API
    // but forces all consumers to do handle missing properties
    return {
        id: itemId,
        owner: owner,
        orgId,
        created: created,
        // for feature layers, modified will usually come from the layer so
        // we prefer itemModified, but fall back to modified if it came from the item
        modified: (itemModified ||
            (modifiedProvenance === "item.modified" && modified)),
        title: (title || name),
        type: serviceType || type,
        typeKeywords,
        description,
        tags,
        snippet,
        thumbnail,
        extent: itemExtent ||
            /* istanbul ignore next: API should always return itemExtent, but we default to [] just in case */ [],
        categories,
        contentStatus,
        spatialReference: spatialReference || serviceSpatialReference,
        accessInformation,
        licenseInfo,
        culture,
        url,
        access,
        size,
        protected: isProtected,
        proxyFilter,
        properties,
        appCategories,
        industries,
        languages,
        largeThumbnail,
        banner,
        screenshots,
        listed,
        ownerFolder,
        commentsEnabled,
        numComments,
        numRatings,
        avgRating,
        numViews,
        itemControl,
        scoreCompleteness,
    };
}
/**
 * returns a new content that has the specified type and
 * and updated related properties like, family, etc
 * @param content orignal content
 * @param type new type
 * @returns new content
 */
const setContentType = (content, type) => {
    // get family and normalized type based on new type
    const normalizedType = (0, compose_1.normalizeItemType)(Object.assign(Object.assign({}, content.item), { type }));
    const family = (0, get_family_1.getFamily)(normalizedType);
    const contentTypeIcon = (0, compose_1.getContentTypeIcon)(normalizedType);
    const contentTypeLabel = (0, exports.getContentTypeLabel)(normalizedType, content.isProxied);
    const updated = Object.assign(Object.assign({}, content), { type: normalizedType, family,
        contentTypeIcon,
        contentTypeLabel });
    // update the relative URL to the content
    // which is based on type and family
    return appendContentUrls(updated, {
        relative: getContentRelativeUrl(updated),
    });
};
exports.setContentType = setContentType;
/**
 * Compute the content type label
 * @param contentType
 * @param isProxied
 * @returns content type label
 */
const getContentTypeLabel = (contentType, isProxied) => {
    return isProxied ? "CSV" : (0, util_1.camelize)(contentType || "");
};
exports.getContentTypeLabel = getContentTypeLabel;
// URL helpers
const appendContentUrls = (content, newUrls) => {
    // merge new urls into existing ones and return a new content
    const urls = Object.assign(Object.assign({}, content.urls), newUrls);
    return Object.assign(Object.assign({}, content), { urls });
};
const getContentRelativeUrl = (content, siteIdentifier) => {
    return (0, internalContentUtils_1.getHubRelativeUrl)(content.type, siteIdentifier || content.identifier, content.typeKeywords);
};
// Tests can be found in packages/common/test/content/content.test.ts
const availability = (status) => {
    return {
        kind: "service",
        service: {
            availability: status,
        },
    };
};
/**
 * Get the status of a content item
 * @param entity the content item
 * @returns the status of the content item
 */
async function getServiceStatus(entity, options) {
    // get the request options for the `getService` call, and set a default timeout if one is not provided
    const { timeout = 3000 } = options, requestOptions = tslib_1.__rest(options, ["timeout"]);
    const { url } = entity;
    const hasUrl = !!url;
    if (!hasUrl) {
        return availability("available");
    }
    const hasQueryParams = url.includes("?");
    const isServiceBackedEntity = (0, is_service_1.isService)(hasQueryParams ? url.split("?")[0] : url); // remove any query params
    if (isServiceBackedEntity) {
        // set up our two promises: one to get the service definition and one to sleep for 3 seconds
        const definitionPromise = (0, arcgis_rest_feature_service_1.getService)(Object.assign({ url }, requestOptions))
            .then(() => {
            // if the service is returned, then we consider it available
            return availability("available");
        })
            .catch((error) => {
            // see interface IHubServiceBackedContentStatus for possible
            // availability values and what each signifies
            if (error.response) {
                const statusCode = error.response.error.code;
                const requiresAuth = [401, 403, 499].includes(statusCode);
                return requiresAuth
                    ? availability("auth-required")
                    : availability("unavailable");
            }
            else {
                // sometimes, the 499 status code is returned as a "failed to fetch" error
                return availability("auth-required");
            }
        });
        // race the two promises
        const status = Promise.race([definitionPromise, await (0, wait_1.wait)(timeout)]).then((result) => {
            // if result is undefined, the service is slow
            // otherwise, the service is available OR unavailable, depending on how the promise resolves
            return result ? result : availability("slow");
        });
        return status;
    }
    else {
        // if we don't have a url or it is not a service backed entity, assume it is available
        return availability("available");
    }
}
//# sourceMappingURL=contentUtils.js.map