"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ENTERPRISE_SITE_ITEM_TYPE = exports.HUB_SITE_ITEM_TYPE = void 0;
exports.createSite = createSite;
exports.updateSite = updateSite;
exports.deleteSite = deleteSite;
exports.fetchSite = fetchSite;
exports.convertModelToSite = convertModelToSite;
exports.convertItemToSite = convertItemToSite;
exports.enrichSiteSearchResult = enrichSiteSearchResult;
const arcgis_rest_portal_1 = require("@esri/arcgis-rest-portal");
const get_family_1 = require("../content/get-family");
const fetchSiteModel_1 = require("./fetchSiteModel");
const PropertyMapper_1 = require("../core/_internal/PropertyMapper");
const _enrichments_1 = require("../items/_enrichments");
const parseInclude_1 = require("../search/_internal/parseInclude");
const internalContentUtils_1 = require("../content/_internal/internalContentUtils");
const applyPermissionMigration_1 = require("./_internal/applyPermissionMigration");
const computeProps_1 = require("./_internal/computeProps");
const getPropertyMap_1 = require("./_internal/getPropertyMap");
const slugify_1 = require("../utils/slugify");
const ensure_unique_domain_name_1 = require("./domains/ensure-unique-domain-name");
const strip_protocol_1 = require("../urls/strip-protocol");
const api_1 = require("../api");
const themes_1 = require("./themes");
const util_1 = require("../util");
const addSiteDomains_1 = require("./domains/addSiteDomains");
const remove_domains_by_site_id_1 = require("./domains/remove-domains-by-site-id");
const reflectCollectionsToSearchCategories_1 = require("./_internal/reflectCollectionsToSearchCategories");
const convertCatalogToLegacyFormat_1 = require("./_internal/convertCatalogToLegacyFormat");
const convertFeaturesToLegacyCapabilities_1 = require("./_internal/capabilities/convertFeaturesToLegacyCapabilities");
const computeLinks_1 = require("./_internal/computeLinks");
const subdomains_1 = require("./_internal/subdomains");
const utils_1 = require("../discussions/utils");
const createModel_1 = require("../models/createModel");
const fetchModelFromItem_1 = require("../models/fetchModelFromItem");
const updateModel_1 = require("../models/updateModel");
const get_prop_1 = require("../objects/get-prop");
const set_prop_1 = require("../objects/set-prop");
const map_by_1 = require("../utils/map-by");
const handleDomainChanges_1 = require("./_internal/handleDomainChanges");
exports.HUB_SITE_ITEM_TYPE = "Hub Site Application";
exports.ENTERPRISE_SITE_ITEM_TYPE = "Site Application";
/**
 * Default values of a IHubSite
 * NOTE: This is different from the objects in ./defaults.ts
 * These are ONLY used when creating a new site via createSite function
 */
const DEFAULT_SITE = {
    name: "",
    tags: [],
    typeKeywords: ["Hub Site", "hubSite", "DELETEMESITE"],
    legacyCapabilities: [
        "api_explorer",
        "pages",
        "my_data",
        "social_logins",
        "json_chart_card",
        "document_iframes",
        "items_view",
        "app_page",
        "underlinedLinks",
        "globalNav",
        "socialSharing",
    ],
    catalog: {
        schemaVersion: 1,
    },
    subdomain: "",
    defaultHostname: "",
    customHostname: "",
    clientId: "",
    map: null,
    feeds: {},
    pages: [],
    theme: null,
    contentViews: {
        sidePanelOpen: {
            app: true,
            map: true,
            dataset: true,
            document: true,
            feedback: true,
        },
    },
    telemetry: {},
    layout: {
        sections: [],
        header: {
            component: {
                name: "site-header",
                settings: {
                    fullWidth: false,
                    title: "",
                    headerType: "default",
                    menuLinks: [],
                    schemaVersion: 3,
                    showTitle: true,
                },
            },
        },
    },
};
/**
 * Default values for a new HubSite Model
 * NOTE: This is different from the objects in ./defaults.ts
 * These are ONLY used when creating a new site via createSite function
 */
const DEFAULT_SITE_MODEL = {
    item: {
        // type: intentionally left out as we need to
        // set that based on portal/enterprise
        title: "",
        description: "",
        snippet: "",
        tags: [],
        typeKeywords: ["Hub Site", "hubSite", "DELETEMESITE"],
        properties: {
            // sites don't have slugs
            // slug: "",
            orgUrlKey: "",
            defaultHostname: "",
            customHostname: "",
            clientId: "",
            subdomain: "",
            schemaVersion: 1.5,
        },
        url: "",
    },
    data: {
        catalog: {
            groups: [],
        },
        feeds: {},
        values: {
            title: "",
            defaultHostname: "",
            customHostname: "",
            subdomain: "",
            faviconUrl: "",
            uiVersion: "2.4",
            clientId: "",
            map: {
                basemaps: {},
            },
            defaultExtent: {},
            pages: [],
            theme: {},
            layout: {
                sections: [],
                header: {
                    component: {
                        name: "site-header",
                        settings: {
                            fullWidth: false,
                            iframeHeight: "150px",
                            iframeUrl: "",
                            links: [],
                            logoUrl: "",
                            title: "default site",
                            markdown: "",
                            headerType: "default",
                            schemaVersion: 3,
                            showLogo: true,
                            showTitle: true,
                            logo: {
                                display: {},
                                state: "valid",
                            },
                            shortTitle: "",
                            menuLinks: [],
                            socialLinks: {
                                facebook: {},
                                twitter: {},
                                instagram: {},
                                youtube: {},
                            },
                        },
                    },
                    showEditor: false,
                },
                footer: {
                    component: {
                        name: "site-footer",
                        settings: {
                            footerType: "none",
                            markdown: "",
                            schemaVersion: 2.1,
                        },
                    },
                    showEditor: false,
                },
            },
            contentViews: {
                sidePanelOpen: {
                    app: true,
                    map: true,
                    dataset: true,
                    document: true,
                    feedback: true,
                },
            },
        },
    },
};
// TODO: Add OperationStack & Error Handling
/**
 * Create a new Hub Site
 *
 * Minimum properties are `name` and `org`
 * @param partialSite
 * @param requestOptions
 */
async function createSite(partialSite, requestOptions) {
    const portal = requestOptions.portalSelf;
    // Ensure we are always using the urlKey from the portal, and downcasing
    const orgUrlKey = (portal.urlKey || "").toLowerCase();
    partialSite.orgUrlKey = orgUrlKey;
    const site = Object.assign(Object.assign({}, DEFAULT_SITE), partialSite);
    // Set the type based on the environment we are working in
    site.type = requestOptions.isPortal
        ? exports.ENTERPRISE_SITE_ITEM_TYPE
        : exports.HUB_SITE_ITEM_TYPE;
    if (!site.subdomain) {
        site.subdomain = (0, slugify_1.slugify)(site.name);
    }
    site.subdomain = await (0, ensure_unique_domain_name_1.ensureUniqueDomainName)(site.subdomain, requestOptions);
    // Domains
    if (!requestOptions.isPortal) {
        // now that we know the subdomain is available, set the defaultHostname
        site.defaultHostname = `${site.subdomain}-${orgUrlKey}.${(0, strip_protocol_1.stripProtocol)((0, api_1.getHubApiUrl)(requestOptions))}`;
        // set the url
        site.url = `https://${site.customHostname ? site.customHostname : site.defaultHostname}`;
    }
    else {
        // Portal Sites use subdomain in hash based router
        // Also in Enterprise orgUrlKey is undefined.
        (0, subdomains_1.handleSubdomainChange)(site, requestOptions);
    }
    // override only if not set...
    if (!site.theme) {
        site.theme = (0, themes_1.getOrgDefaultTheme)(portal);
    }
    if (!site.defaultExtent) {
        site.defaultExtent = portal.defaultExtent;
    }
    if (!site.culture) {
        site.culture = portal.culture;
    }
    // pull the basemap from portalSelf
    if (!(0, get_prop_1.getProp)(site, "map.basemaps.primary")) {
        (0, set_prop_1.setProp)("map.basemaps.primary", portal.defaultBasemap, site);
    }
    // Put the title into the header
    if (!(0, get_prop_1.getProp)(site, "layout.header.component.settings.title")) {
        (0, set_prop_1.setProp)("layout.header.component.settings.title", site.name, site);
    }
    site.typeKeywords = (0, utils_1.setDiscussableKeyword)(site.typeKeywords, site.isDiscussable);
    // Now convert the IHubSite into an IModel
    const mapper = new PropertyMapper_1.PropertyMapper((0, getPropertyMap_1.getPropertyMap)());
    let model = mapper.entityToStore(site, (0, util_1.cloneObject)(DEFAULT_SITE_MODEL));
    // At this point `data.catalog` may have become a full IHubCatalog object due to an in-memory
    // migration. However, we can't persist an IHubCatalog in `data.catalog` without breaking
    // the application, since most of the app relies on the old catalog structure. As such,
    // we convert any changes made to the catalog scope into the old format and merge the changes
    // with the existing structure on the most current model.
    // TODO: Remove once the application is plumbed to work off an IHubCatalog
    if ((0, get_prop_1.getProp)(model, "data.catalog.scopes")) {
        model.data.catalog = (0, convertCatalogToLegacyFormat_1.catalogToLegacy)(model.data.catalog);
    }
    // create the backing item
    model = await (0, createModel_1.createModel)(model, requestOptions);
    // Register as an app
    // NOTE: Site registration needs to happen via the hub api domain api calls
    // See https://devtopia.esri.com/dc/hub/issues/6390 for info
    // Register domain and at the same time register the site as an application
    // for portal, this will return a single entry with just the clientKey
    const domainResponses = await (0, addSiteDomains_1.addSiteDomains)(model, requestOptions);
    model.data.values.clientId = domainResponses[0].clientKey;
    // update the model
    const updatedModel = await (0, updateModel_1.updateModel)(model, requestOptions);
    // convert the model into a IHubSite and return
    return mapper.storeToEntity(updatedModel, {});
}
/**
 * Update a Hub Site
 *
 * This checks for and applies domain changes
 * @param site
 * @param requestOptions
 * @returns
 */
async function updateSite(site, requestOptions) {
    var _a, _b, _c, _d, _e, _f;
    // ensure the orgUrlKey is lowercase
    site.orgUrlKey = site.orgUrlKey.toLowerCase();
    if (requestOptions.isPortal) {
        (0, subdomains_1.handleSubdomainChange)(site, requestOptions);
    }
    site.typeKeywords = (0, utils_1.setDiscussableKeyword)(site.typeKeywords, site.isDiscussable);
    // Fetch backing model from the portal
    const currentModel = await (0, fetchSiteModel_1.fetchSiteModel)(site.id, requestOptions);
    // Note: Although we are fetching the model, and applying changes onto it,
    // we are not attempting to handle "concurrent edit" conflict resolution
    // but this is where we would apply that sort of logic
    const mapper = new PropertyMapper_1.PropertyMapper((0, getPropertyMap_1.getPropertyMap)());
    let modelToUpdate = mapper.entityToStore(site, currentModel);
    // ============================================================
    // Entity to Store can not handle scenarios where deep properties have been removed
    // via a run-time migration.
    // At this time we don't have a comprehensize solution for this, so we just remove
    // the well-known-invalid properties from the model here.
    // NOTES:
    // * Add test logic into the `describe("updateSite removes properties:"...` section
    // * We ignore coverage on the delete statements below because they
    // use the optional property chaining, but we don't want to add a bunch of
    // complex test cases just to satisfy the coverage tool.
    // The tests do verify that the properties are removed.
    // ============================================================
    // Old telemetry props: migrated to correct property path in _migrateTelemetryConfig
    /* istanbul ignore next */
    (_b = (_a = modelToUpdate.data) === null || _a === void 0 ? void 0 : _a.values) === null || _b === void 0 ? true : delete _b.telemetry;
    /* istanbul ignore next */
    (_d = (_c = modelToUpdate.item) === null || _c === void 0 ? void 0 : _c.properties) === null || _d === void 0 ? true : delete _d.telemetry;
    // old slug
    /* istanbul ignore next */
    (_f = (_e = modelToUpdate.item) === null || _e === void 0 ? void 0 : _e.properties) === null || _f === void 0 ? true : delete _f.slug;
    // handle any domain changes
    // we'd like to base this check on existence of the domain service,
    // but we only have requestOptions and not a context
    if (!requestOptions.isPortal) {
        await (0, handleDomainChanges_1.handleDomainChanges)(modelToUpdate, currentModel, requestOptions);
    }
    // Persist the new catalog to new property until the app is fully migrated
    modelToUpdate.data.catalogV2 = site.catalog;
    // Because some old (but critical) application code still uses `data.values.searchCategories`
    // as the source of truth for collection display configuration, we port all display changes
    // in `data.catalog.collections` to the search category format.
    // TODO: Remove once the app no longer relies on `data.values.searchCategories`
    modelToUpdate = (0, reflectCollectionsToSearchCategories_1.reflectCollectionsToSearchCategories)(modelToUpdate);
    // At this point `data.catalog` has become a full IHubCatalog object due to an in-memory
    // migration. However, we can't persist an IHubCatalog in `data.catalog` without breaking
    // the application, since most of the app relies on the old catalog structure. As such,
    // we convert any changes made to the catalog scope into the old format and merge the changes
    // with the existing structure on the most current model.
    // TODO: Remove once the application is plumbed to work off an IHubCatalog
    modelToUpdate = (0, convertCatalogToLegacyFormat_1.convertCatalogToLegacyFormat)(modelToUpdate, currentModel);
    /**
     * Site capabilities are currently saved as an array on the
     * site.data.values.capabilities. We want to migragte these
     * legacy capabilities over to features in the new permissions
     * system; however, we must continue persisting updates to
     * these features in the legacy capabilities array until the
     * existing site capabilities in our application are plumbed
     * to work off of permissions
     * TODO: Remove once site capabilities use permissions
     */
    modelToUpdate = (0, convertFeaturesToLegacyCapabilities_1.convertFeaturesToLegacyCapabilities)(modelToUpdate, currentModel);
    // send updates to the Portal API and get back the updated site model
    const updatedSiteModel = await (0, updateModel_1.updateModel)(modelToUpdate, requestOptions);
    // convert that back into a IHubSite and return it
    const updatedSite = convertModelToSite(updatedSiteModel, requestOptions);
    return updatedSite;
}
/**
 * Remove a Hub Site
 *
 * This simply removes the Site item, and it's associated domain records.
 * This does not remove any Teams/Groups or content associated with the
 * Site
 * @param id
 * @param requestOptions
 * @returns
 */
async function deleteSite(id, requestOptions) {
    // For AGO we need to remove the domain records for the site
    if (!requestOptions.isPortal) {
        await (0, remove_domains_by_site_id_1.removeDomainsBySiteId)(id, requestOptions);
    }
    // now we can remove the item
    const ro = Object.assign(Object.assign({}, requestOptions), { id });
    await (0, arcgis_rest_portal_1.removeItem)(ro);
    return;
}
/**
 * Returns site model given various kinds of identifier
 *
 * @param identifier - a site item ID, site hostname, enterprise site slug, or full site URL
 * @param requestOptions
 * @private // remove when we remove existing fetchSite function
 */
async function fetchSite(identifier, requestOptions) {
    // get the model
    const model = await (0, fetchSiteModel_1.fetchSiteModel)(identifier, requestOptions);
    // convert to IHubSite
    return convertModelToSite(model, requestOptions);
}
/**
 * @internal
 * Convert an IModel for a Hub Site Item into an IHubSite
 * @param model
 * @param requestOptions
 * @returns
 */
function convertModelToSite(model, requestOptions) {
    // Add permissions based on Groups
    // This may get moved to a formal schema migration in the future but for now
    // we can do it here as there is no ux for managing permissions yet.
    const migrated = (0, applyPermissionMigration_1.applyPermissionMigration)(model);
    // convert to site
    const mapper = new PropertyMapper_1.PropertyMapper((0, getPropertyMap_1.getPropertyMap)());
    const site = mapper.storeToEntity(migrated, {});
    // compute additional properties
    return (0, computeProps_1.computeProps)(model, site, requestOptions);
}
/**
 * Convert a Hub Site Application item into a Hub Site, fetching any
 * additional information that may be required
 * @param item
 * @param auth
 * @returns
 */
async function convertItemToSite(item, requestOptions) {
    const model = await (0, fetchModelFromItem_1.fetchModelFromItem)(item, requestOptions);
    return convertModelToSite(model, requestOptions);
}
/**
 * Fetch Site specific enrichments
 * @param item
 * @param include
 * @param requestOptions
 * @returns
 */
async function enrichSiteSearchResult(item, include, requestOptions) {
    // we send old hub sites through this enrichment and
    // artificially change their type so they appear to be
    // newer "Hub Site Application"s - note this change
    // won't actually be persisted
    item.type =
        item.type === "Web Mapping Application"
            ? "Hub Site Application"
            : item.type;
    // Create the basic structure
    const result = {
        access: item.access,
        id: item.id,
        type: item.type,
        name: item.title,
        owner: item.owner,
        typeKeywords: item.typeKeywords,
        tags: item.tags,
        categories: item.categories,
        summary: item.snippet || item.description,
        createdDate: new Date(item.created),
        createdDateSource: "item.created",
        updatedDate: new Date(item.modified),
        updatedDateSource: "item.modified",
        family: (0, get_family_1.getFamily)(item.type),
        links: {
            self: "not-implemented",
            siteRelative: "not-implemented",
            thumbnail: "not-implemented",
        },
        location: (0, internalContentUtils_1.deriveLocationFromItem)(item),
        rawResult: item,
    };
    // default includes
    const DEFAULTS = [];
    // merge includes
    include = [...DEFAULTS, ...include].filter(util_1.unique);
    // Parse the includes into a valid set of enrichments
    const specs = include.map(parseInclude_1.parseInclude);
    // Extract out the low-level enrichments needed
    const enrichments = (0, map_by_1.mapBy)("enrichment", specs).filter(util_1.unique);
    // fetch the enrichments
    let enriched = {};
    if (enrichments.length) {
        // TODO: Look into caching for the requests in fetchItemEnrichments
        enriched = await (0, _enrichments_1.fetchItemEnrichments)(item, enrichments, requestOptions);
    }
    // map the enriched props onto the result
    specs.forEach((spec) => {
        result[spec.prop] = (0, get_prop_1.getProp)(enriched, spec.path);
    });
    // Handle links
    result.links = (0, computeLinks_1.computeLinks)(item, requestOptions);
    return result;
}
//# sourceMappingURL=HubSites.js.map