"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.HubSite = void 0;
const Catalog_1 = require("../search/Catalog");
const HubItemEntity_1 = require("../core/HubItemEntity");
const getEditorConfig_1 = require("../core/schemas/getEditorConfig");
const enrichEntity_1 = require("../core/enrichEntity");
const defaults_1 = require("./defaults");
const HubSites_1 = require("./HubSites");
const deepContains_1 = require("../core/_internal/deepContains");
const util_1 = require("../util");
const PropertyMapper_1 = require("../core/_internal/PropertyMapper");
const getPropertyMap_1 = require("./_internal/getPropertyMap");
const utils_1 = require("../discussions/utils");
const hubItemEntityFromEditor_1 = require("../core/_internal/hubItemEntityFromEditor");
const get_with_default_1 = require("../objects/get-with-default");
const set_prop_1 = require("../objects/set-prop");
const updateVersion_1 = require("../versioning/updateVersion");
const createVersion_1 = require("../versioning/createVersion");
const getVersion_1 = require("../versioning/getVersion");
const searchVersions_1 = require("../versioning/searchVersions");
const updateVersionMetadata_1 = require("../versioning/updateVersionMetadata");
const deleteVersion_1 = require("../versioning/deleteVersion");
/**
 * Hub Site Class
 * NOTE: This is a minimal implementation. Create operations are not supported at this time
 */
class HubSite extends HubItemEntity_1.HubItemEntity {
    /**
     * Private constructor so we don't have `new` all over the place. Allows for
     * more flexibility in how we create the HubSiteManager over time.
     * @param context
     */
    constructor(site, context) {
        super(site, context);
        this._catalogCache = {};
        this._catalog = Catalog_1.Catalog.fromJson(site.catalog, this.context);
    }
    /**
     * Catalog instance for this site. Note: Do not hold direct references to this object; always access it from the site.
     * @returns
     */
    get catalog() {
        return this._catalog;
    }
    /**
     * Create an instance from an IHubSite object
     * @param json - JSON object to create a HubSite from
     * @param context - ArcGIS context
     * @returns
     */
    static fromJson(json, context) {
        // merge what we have with the default values
        const pojo = this.applyDefaults(json, context);
        return new HubSite(pojo, context);
    }
    /**
     *
     * NOT IMPLEMENTED YET: Create a new HubSite, returning a HubSite instance.
     * By default, this does not save the site to the backing store.
     * @param partialSite
     * @param context
     * @returns
     */
    static async create(partialSite, context, save = false) {
        const pojo = this.applyDefaults(partialSite, context);
        // return an instance of HubProject
        const instance = HubSite.fromJson(pojo, context);
        if (save) {
            await instance.save();
        }
        return instance;
    }
    /**
     * Fetch a Site from the backing store and return a HubSite instance.
     * @param identifier - Identifier of the site to load
     * @param context
     * @returns
     */
    static async fetch(identifier, context) {
        // fetch the site by id or slug
        try {
            const site = await (0, HubSites_1.fetchSite)(identifier, context.hubRequestOptions);
            // create an instance of HubSite from the site
            return HubSite.fromJson(site, context);
        }
        catch (ex) {
            if (ex.message ===
                "CONT_0001: Item does not exist or is inaccessible.") {
                throw new Error(`Site not found.`);
            }
            else {
                throw ex;
            }
        }
    }
    /**
     * Ensure the Site has the baseline set of properties
     * that are required for a HubSite.
     * @param partialSite
     * @param context
     * @returns
     */
    static applyDefaults(partialSite, context) {
        // ensure we have the orgUrlKey
        if (!partialSite.orgUrlKey) {
            // if we're in enterprise, there is no portalUrlKey
            partialSite.orgUrlKey = (0, get_with_default_1.getWithDefault)(context, "portal.urlKey", "");
        }
        // ensure lower case
        partialSite.orgUrlKey = partialSite.orgUrlKey.toLowerCase();
        // extend the partial over the defaults
        const pojo = Object.assign(Object.assign({}, defaults_1.DEFAULT_SITE), partialSite);
        pojo.type = context.isPortal
            ? HubSites_1.ENTERPRISE_SITE_ITEM_TYPE
            : HubSites_1.HUB_SITE_ITEM_TYPE;
        return pojo;
    }
    /**
     * Apply a new state to the instance
     * @param changes
     */
    update(changes) {
        if (this.isDestroyed) {
            throw new Error("HubSite is already destroyed.");
        }
        // merge partial onto existing entity
        this.entity = Object.assign(Object.assign({}, this.entity), changes);
        // update internal instances
        if (changes.catalog) {
            this._catalog = Catalog_1.Catalog.fromJson(this.entity.catalog, this.context);
        }
    }
    /**
     * Save the HubSite to the backing store.
     * Currently Sites are stored as Items in Portal
     * @returns
     */
    async save() {
        if (this.isDestroyed) {
            throw new Error("HubSite is already destroyed.");
        }
        // get the catalog, and permission configs
        this.entity.catalog = this._catalog.toJson();
        if (this.entity.id) {
            // update it
            this.entity = await (0, HubSites_1.updateSite)(this.entity, this.context.hubRequestOptions);
        }
        else {
            // create it
            this.entity = await (0, HubSites_1.createSite)(this.entity, this.context.hubRequestOptions);
        }
        // call the after save hook on superclass
        await super.afterSave();
        return;
    }
    /**
     * Delete the HubSite from the store
     * set a flag to indicate that it is destroyed
     * @returns
     */
    async delete() {
        if (this.isDestroyed) {
            throw new Error("HubSite is already destroyed.");
        }
        this.isDestroyed = true;
        // Delegate to module fn
        await (0, HubSites_1.deleteSite)(this.entity.id, this.context.hubRequestOptions);
    }
    /**
     * Check if a particular entity is contained is this HubSite.
     *
     * By default, this checks the Site catalog for the entity, by executing a search.
     *
     * Transitive containment is supported by passing in an array of `IDeepCatalogInfo`
     * objects, in the order of the containment hierarchy.
     *
     * Scenario:
     * - Site `00a`'s Catalog contains Initiative `00b`.
     * - Initiative `00b`'s Catalog contains Site `00c`.
     * - Site `00c`'s catalog contains Dataset `00d`.
     *
     * Check if Dataset `00d` can be displayed in Site `00a`, pass in the following
     * ```js
     * [
     *  {id: '00c', entityType:"item"}, // site
     *  {id: '00b', entityType:"item"}, // initiative
     * ]
     * ```
     * The site catalog and id will be added in automatically.
     *
     * If you already have the `IHubCatalog` for the site or initiative, you can
     * pass that in as well, and it will save a request.
     *
     * This function will also build a cache of the catalogs so subsequent calls
     * will be faster.
     * @param identifier
     * @param hierarchy
     * @returns
     */
    async contains(identifier, hierarchy = []) {
        // Apply any cached catalogs
        hierarchy.forEach((entry) => {
            if (this._catalogCache[entry.id]) {
                entry.catalog = this._catalogCache[entry.id];
            }
        });
        // Add the site and it's catalog into the hierarchy as the last entry
        const hierarchyWithSiteCatalog = [
            ...hierarchy,
            ...[
                {
                    id: this.id,
                    hubEntityType: "site",
                    catalog: this._catalog.toJson(),
                },
            ],
        ];
        // delegate to fn
        const response = await (0, deepContains_1.deepContains)(identifier, "content", // NOTE: this is hardcoded for now!
        hierarchyWithSiteCatalog, this.context);
        // cache the catalogs
        Object.keys(response.catalogInfo).forEach((key) => {
            // don't cache the site's catalog
            if (key !== this.id) {
                this._catalogCache[key] = response.catalogInfo[key].catalog;
            }
        });
        return response;
    }
    //#region IWithVersioningBehavior
    /*
      NOTE: we will be further fleshing this out in the near future
        we will want to make it easier to apply a version to a site and work with it
        at a minimum, we will probably want something like this:
        async applyVersion(versionOrIdentifier: string | IVersion) {
          const version = typeof versionOrIdentifier === "string" ? await this.getVersion(versionOrIdentifier) : versionOrIdentifier;
          const mapper = new PropertyMapper<IHubSite>(getPropertyMap());
          const model = mapper.objectToModel(this.entity, {} as IModel);
          const versionedModel = applyVersion(model, version);
          const versionedEntity = mapper.modelToObject(versionedModel, this.entity);
          return this.update(versionedEntity);
        }
    */
    /**
     * Gets all the versions of the site
     * @returns
     */
    async searchVersions() {
        return (0, searchVersions_1.searchVersions)(this.entity.id, this.context.userRequestOptions);
    }
    /**
     * Gets the specified version of the site
     * @param versionId
     * @returns
     */
    async getVersion(versionId) {
        return (0, getVersion_1.getVersion)(this.entity.id, versionId, this.context.userRequestOptions);
    }
    /**
     * Creates a new version of the site
     * @param options
     * @returns
     */
    async createVersion(options) {
        const mapper = new PropertyMapper_1.PropertyMapper((0, getPropertyMap_1.getPropertyMap)());
        const model = mapper.entityToStore(this.entity, {});
        return (0, createVersion_1.createVersion)(model, this.context.userRequestOptions, options);
    }
    /**
     * Updates the specified version of the site
     * @param version
     * @returns
     */
    async updateVersion(version) {
        const mapper = new PropertyMapper_1.PropertyMapper((0, getPropertyMap_1.getPropertyMap)());
        const model = mapper.entityToStore(this.entity, {});
        return (0, updateVersion_1.updateVersion)(model, version, this.context.userRequestOptions);
    }
    /**
     * Updates the specified version's metadata
     * @param version
     * @returns
     */
    async updateVersionMetadata(version) {
        return (0, updateVersionMetadata_1.updateVersionMetadata)(this.entity.id, version, this.entity.owner, this.context.userRequestOptions);
    }
    /**
     * Deletes the specified version of the entity
     * @returns
     */
    async deleteVersion(versionId) {
        return (0, deleteVersion_1.deleteVersion)(this.entity.id, versionId, this.entity.owner, this.context.userRequestOptions);
    }
    //#endregion IWithVersioningBehavior
    /*
     * Get the editor config for the HubProject entity.
     * @param i18nScope translation scope to be interpolated into the uiSchema
     * @param type editor type - corresonds to the returned uiSchema
     * @param options optional hash of dynamic uiSchema element options
     */
    async getEditorConfig(i18nScope, type) {
        // delegate to the schema subsystem
        return (0, getEditorConfig_1.getEditorConfig)(i18nScope, type, this.entity, this.context);
    }
    /**
     * Return the project as an editor object
     * @param editorContext
     * @returns
     */
    async toEditor(_editorContext = {}, include = []) {
        var _a, _b;
        // 1. optionally enrich entity and cast to editor
        const editor = include.length
            ? (await (0, enrichEntity_1.enrichEntity)((0, util_1.cloneObject)(this.entity), include, this.context.hubRequestOptions))
            : (0, util_1.cloneObject)(this.entity);
        // 2. Apply transforms to relevant entity values so they
        // can be consumed by the editor
        (0, set_prop_1.setProp)("_followers.showFollowAction", this.entity.features["hub:site:feature:follow"], editor);
        const followersGroup = await this.getFollowersGroup();
        (0, set_prop_1.setProp)("_followers.isDiscussable", (0, utils_1.isDiscussable)(followersGroup), editor);
        editor.isDiscussable = this.entity.features["hub:site:feature:discussions"];
        // used by the site URL composite field
        if (!editor._urlInfo) {
            const { url, subdomain, defaultHostname } = editor;
            editor._urlInfo = {
                url,
                subdomain,
                defaultHostname,
                // NOTE: once the editor handles custom hostnames,
                // we'll need to add that here
                // customHostname
            };
        }
        // ensure we only attempt to transform examplePrompts if the array exists and has entries
        if (((_b = (_a = this.entity.assistant) === null || _a === void 0 ? void 0 : _a.examplePrompts) === null || _b === void 0 ? void 0 : _b.length) > 0) {
            (0, set_prop_1.setProp)("assistant.examplePrompts", this.entity.assistant.examplePrompts.map((ep, i) => ({
                key: i.toString(),
                label: ep,
            })), editor);
        }
        return editor;
    }
    /**
     * Load the project from the editor object
     * @param editor
     * @returns
     */
    async fromEditor(editor) {
        var _a, _b, _c, _d, _e, _f;
        // 1. delegate to an item-specific fromEditor util to
        // handle shared "fromEditor" logic
        const res = await (0, hubItemEntityFromEditor_1.hubItemEntityFromEditor)(editor, this.context);
        const entity = res.entity;
        // iterate over the res object keys and set the values
        // on the HubSite instance
        Object.entries(res).forEach(([key, value]) => {
            (0, set_prop_1.setProp)(key, value, this);
        });
        // 2. handle site-specific operations
        // a. set whether or not the followers group is discussable
        if ((_a = editor._followers) === null || _a === void 0 ? void 0 : _a.isDiscussable) {
            await this.setFollowersGroupIsDiscussable(editor._followers.isDiscussable);
        }
        // b. set the followers group access
        if ((_b = editor._followers) === null || _b === void 0 ? void 0 : _b.groupAccess) {
            await this.setFollowersGroupAccess(editor._followers.groupAccess);
        }
        // c. set site feature flags
        entity.features = Object.assign(Object.assign({}, entity.features), { "hub:site:feature:follow": (_c = editor._followers) === null || _c === void 0 ? void 0 : _c.showFollowAction, "hub:site:feature:discussions": editor.isDiscussable });
        // d. handle site URL info
        const { url, subdomain, defaultHostname } = editor._urlInfo;
        entity.url = url;
        entity.subdomain = subdomain;
        entity.defaultHostname = defaultHostname;
        // e. assistant group permissions
        // e. 1. Remove any existing 'hub:site:assistant:access' group permissions
        entity.permissions = entity.permissions.filter((p) => p.permission !== "hub:site:assistant:access");
        // e. 2. Add new group permissions from assistant.accessGroups if any exist
        if (((_d = entity.assistant.accessGroups) === null || _d === void 0 ? void 0 : _d.length) > 0) {
            const assistantPermissions = entity.assistant.accessGroups.map((groupId) => ({
                permission: "hub:site:assistant:access",
                collaborationType: "group",
                collaborationId: groupId,
            }));
            entity.permissions = [...entity.permissions, ...assistantPermissions];
        }
        // f. transform example prompts back to strings
        if (((_f = (_e = editor.assistant) === null || _e === void 0 ? void 0 : _e.examplePrompts) === null || _f === void 0 ? void 0 : _f.length) > 0) {
            entity.assistant.examplePrompts = editor.assistant.examplePrompts.map((ep) => ep.label);
        }
        // 3. create or update the in-memory entity and save
        this.entity = entity;
        await this.save();
        return this.entity;
    }
}
exports.HubSite = HubSite;
//# sourceMappingURL=HubSite.js.map