import { Catalog } from "../search/Catalog";
import { HubItemEntity } from "../core/HubItemEntity";
import { getEditorConfig } from "../core/schemas/getEditorConfig";
import { enrichEntity } from "../core/enrichEntity";
import { DEFAULT_SITE } from "./defaults";
import { createSite, deleteSite, ENTERPRISE_SITE_ITEM_TYPE, fetchSite, HUB_SITE_ITEM_TYPE, updateSite, } from "./HubSites";
import { deepContains } from "../core/_internal/deepContains";
import { cloneObject } from "../util";
import { PropertyMapper } from "../core/_internal/PropertyMapper";
import { getPropertyMap } from "./_internal/getPropertyMap";
import { isDiscussable } from "../discussions/utils";
import { hubItemEntityFromEditor } from "../core/_internal/hubItemEntityFromEditor";
import { getWithDefault } from "../objects/get-with-default";
import { setProp } from "../objects/set-prop";
import { updateVersion } from "../versioning/updateVersion";
import { createVersion } from "../versioning/createVersion";
import { getVersion } from "../versioning/getVersion";
import { searchVersions } from "../versioning/searchVersions";
import { updateVersionMetadata } from "../versioning/updateVersionMetadata";
import { deleteVersion } from "../versioning/deleteVersion";
/**
 * Hub Site Class
 * NOTE: This is a minimal implementation. Create operations are not supported at this time
 */
export class HubSite extends 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.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 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 = getWithDefault(context, "portal.urlKey", "");
        }
        // ensure lower case
        partialSite.orgUrlKey = partialSite.orgUrlKey.toLowerCase();
        // extend the partial over the defaults
        const pojo = Object.assign(Object.assign({}, DEFAULT_SITE), partialSite);
        pojo.type = context.isPortal
            ? ENTERPRISE_SITE_ITEM_TYPE
            : 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.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 updateSite(this.entity, this.context.hubRequestOptions);
        }
        else {
            // create it
            this.entity = await 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 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 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 searchVersions(this.entity.id, this.context.userRequestOptions);
    }
    /**
     * Gets the specified version of the site
     * @param versionId
     * @returns
     */
    async getVersion(versionId) {
        return 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(getPropertyMap());
        const model = mapper.entityToStore(this.entity, {});
        return createVersion(model, this.context.userRequestOptions, options);
    }
    /**
     * Updates the specified version of the site
     * @param version
     * @returns
     */
    async updateVersion(version) {
        const mapper = new PropertyMapper(getPropertyMap());
        const model = mapper.entityToStore(this.entity, {});
        return updateVersion(model, version, this.context.userRequestOptions);
    }
    /**
     * Updates the specified version's metadata
     * @param version
     * @returns
     */
    async updateVersionMetadata(version) {
        return updateVersionMetadata(this.entity.id, version, this.entity.owner, this.context.userRequestOptions);
    }
    /**
     * Deletes the specified version of the entity
     * @returns
     */
    async deleteVersion(versionId) {
        return 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 getEditorConfig(i18nScope, type, this.entity, this.context);
    }
    /**
     * Return the project as an editor object
     * @param editorContext
     * @returns
     */
    async toEditor(_editorContext = {}, include = []) {
        // 1. optionally enrich entity and cast to editor
        const editor = include.length
            ? (await enrichEntity(cloneObject(this.entity), include, this.context.hubRequestOptions))
            : cloneObject(this.entity);
        // 2. Apply transforms to relevant entity values so they
        // can be consumed by the editor
        setProp("_followers.showFollowAction", this.entity.features["hub:site:feature:follow"], editor);
        const followersGroup = await this.getFollowersGroup();
        setProp("_followers.isDiscussable", 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
            };
        }
        return editor;
    }
    /**
     * Load the project from the editor object
     * @param editor
     * @returns
     */
    async fromEditor(editor) {
        var _a, _b, _c, _d;
        // 1. delegate to an item-specific fromEditor util to
        // handle shared "fromEditor" logic
        const res = await 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]) => {
            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;
        // assistant group permissions
        // 1. Remove any existing 'hub:site:assistant:access' group permissions
        entity.permissions = entity.permissions.filter((p) => p.permission !== "hub:site:assistant:access");
        // 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];
        }
        // 3. create or update the in-memory entity and save
        this.entity = entity;
        await this.save();
        return this.entity;
    }
}
//# sourceMappingURL=HubSite.js.map