import { UmbCollectionViewManager } from '../view/collection-view.manager.js';
import { UmbCollectionBulkActionManager } from '../bulk-action/collection-bulk-action.manager.js';
import { UmbCollectionSelectionManager } from '../selection/collection-selection.manager.js';
import { UMB_COLLECTION_CONTEXT } from './collection-default.context-token.js';
import { umbExtensionsRegistry } from '../../extension-registry/index.js';
import { UmbArrayState, UmbBasicState, UmbBooleanState, UmbNumberState, UmbObjectState, } from '../../../../libs/observable-api/index.js';
import { UmbChangeEvent } from '../../event/index.js';
import { UmbContextBase } from '../../../../libs/class-api/index.js';
import { UmbExtensionApiInitializer } from '../../../../libs/extension-api/index.js';
import { UmbPaginationManager, UmbDeprecation, debounce } from '../../utils/index.js';
import { UmbRequestReloadChildrenOfEntityEvent, UmbRequestReloadStructureForEntityEvent, } from '../../entity-action/index.js';
import { UMB_ACTION_EVENT_CONTEXT } from '../../action/index.js';
import { UMB_ENTITY_CONTEXT, UmbParentEntityContext } from '../../entity/index.js';
import { UMB_WORKSPACE_MODAL } from '../../workspace/index.js';
import { UmbModalRouteRegistrationController } from '../../router/index.js';
const LOCAL_STORAGE_KEY = 'umb-collection-view';
export class UmbDefaultCollectionContext extends UmbContextBase {
    #config;
    #workspacePathBuilder;
    #userDefinedProperties;
    #viewLayouts;
    #defaultViewAlias;
    #defaultFilter;
    #initResolver;
    #initialized;
    #actionEventContext;
    #parentEntityContext;
    constructor(host, defaultViewAlias, defaultFilter = {}) {
        super(host, UMB_COLLECTION_CONTEXT);
        this.#config = { pageSize: 50 };
        // TODO: replace with a state manager
        this._loading = new UmbObjectState(false);
        this.loading = this._loading.asObservable();
        this._items = new UmbArrayState([], (x) => x.unique);
        this.items = this._items.asObservable();
        this._totalItems = new UmbNumberState(0);
        this.totalItems = this._totalItems.asObservable();
        this._filter = new UmbObjectState({});
        this.filter = this._filter.asObservable();
        this._selectOnly = new UmbBooleanState(undefined);
        this.selectOnly = this._selectOnly.asObservable();
        this.#workspacePathBuilder = new UmbBasicState(undefined);
        this.workspacePathBuilder = this.#workspacePathBuilder.asObservable();
        this.#userDefinedProperties = new UmbArrayState([], (x) => x.alias);
        this.userDefinedProperties = this.#userDefinedProperties.asObservable();
        this.#viewLayouts = new UmbArrayState([], (x) => x.collectionView);
        this.viewLayouts = this.#viewLayouts.asObservable();
        this.pagination = new UmbPaginationManager();
        this.selection = new UmbCollectionSelectionManager(this);
        this.view = new UmbCollectionViewManager(this);
        this.bulkAction = new UmbCollectionBulkActionManager(this);
        this.#initialized = false;
        this._init = new Promise((resolve) => {
            if (this.#initialized) {
                resolve();
            }
            else {
                this.#initResolver = resolve;
            }
        });
        this.#parentEntityContext = new UmbParentEntityContext(this);
        this._configured = false;
        this.#onPageChange = (event) => {
            const target = event.target;
            const skipFilter = { skip: target.getSkip() };
            this.setFilter(skipFilter);
        };
        /* debouncing the load collection method because multiple filters can be set at the same time
        that will trigger multiple load calls with different filter arguments */
        this.loadCollection = debounce(() => this._requestCollection(), 100);
        this.#onReloadStructureRequest = (event) => {
            const items = this._items.getValue();
            const hasItem = items.some((item) => item.unique === event.getUnique());
            if (hasItem) {
                this._requestCollection();
            }
        };
        this.#onReloadChildrenRequest = async (event) => {
            // check if the collection is in the same context as the entity from the event
            const entityContext = await this.getContext(UMB_ENTITY_CONTEXT);
            if (!entityContext)
                return;
            const unique = entityContext.getUnique();
            const entityType = entityContext.getEntityType();
            if (unique === event.getUnique() && entityType === event.getEntityType()) {
                this._requestCollection();
            }
        };
        this.#defaultViewAlias = defaultViewAlias;
        this.#defaultFilter = defaultFilter;
        this.pagination.addEventListener(UmbChangeEvent.TYPE, this.#onPageChange);
        this.#listenToEntityEvents();
        // The parent entity context is used to get the parent entity for the collection items
        // All items in the collection are children of the current entity context
        this.consumeContext(UMB_ENTITY_CONTEXT, (context) => {
            this.observe(context?.unique, (currentEntityUnique) => {
                const currentEntityType = context?.getEntityType();
                const parent = currentEntityUnique && currentEntityType
                    ? {
                        unique: currentEntityUnique,
                        entityType: currentEntityType,
                    }
                    : undefined;
                this.#parentEntityContext?.setParent(parent);
            }, '_observeEntityContextUnique');
        });
    }
    setupView(viewElement) {
        // TODO: Consider to remove this one as well:
        new UmbModalRouteRegistrationController(viewElement, UMB_WORKSPACE_MODAL)
            .addAdditionalPath('entity/:entityType')
            .onSetup((params) => {
            return { data: { entityType: params.entityType, preset: {} } };
        })
            .onReject(() => {
            // TODO: Maybe this can be removed?
            this._requestCollection();
        })
            .onSubmit(() => {
            // TODO: Maybe this can be removed?
            this._requestCollection();
        })
            .observeRouteBuilder((routeBuilder) => {
            this.#workspacePathBuilder.setValue(routeBuilder);
        });
    }
    async #listenToEntityEvents() {
        this.consumeContext(UMB_ACTION_EVENT_CONTEXT, (context) => {
            this.#actionEventContext = context;
            context?.removeEventListener(UmbRequestReloadStructureForEntityEvent.TYPE, this.#onReloadStructureRequest);
            context?.removeEventListener(UmbRequestReloadChildrenOfEntityEvent.TYPE, this.#onReloadChildrenRequest);
            context?.addEventListener(UmbRequestReloadStructureForEntityEvent.TYPE, this.#onReloadStructureRequest);
            context?.addEventListener(UmbRequestReloadChildrenOfEntityEvent.TYPE, this.#onReloadChildrenRequest);
        });
    }
    _configure() {
        if (!this.#config)
            return;
        this.#configureSelection();
        this.bulkAction.setConfig(this.#config.bulkActionConfiguration);
        // Observe bulk actions to enable selection when bulk actions are available
        // Bulk Actions are an integrated part of a Collection so we handle it here instead of a configuration
        this.observe(this.bulkAction.hasBulkActions, (hasBulkActions) => {
            // Allow selection if there are bulk actions available
            if (hasBulkActions) {
                // TODO: This is a temporary workaround until we support two types of selection (bulk action selection and normal selection)
                // We have to use the same selection configuration for both types of selection to ensure that selection works as expected in multi vs single select mode (ex: pickers).
                // We currently disable bulk actions in pickers until we have a solution in place for supporting both types of selection.
                // With this workaround the experience will be that a collection, supporting bulk actions configured as single select, will only be able to select one item at a time.
                const config = this.#config?.selectionConfiguration;
                const selectable = config?.selectable ?? true;
                const multiple = config?.multiple ?? true;
                this.selection.setSelectable(selectable);
                this.selection.setMultiple(multiple);
            }
        }, 'umbCollectionHasBulkActionsObserver');
        this.pagination.setPageSize(this.#config.pageSize ?? 50);
        const filterValue = this._filter.getValue();
        this._filter.setValue({
            ...this.#defaultFilter,
            ...this.#config,
            ...filterValue,
            skip: filterValue.skip ?? 0,
            take: this.pagination.getPageSize(),
        });
        this.#userDefinedProperties.setValue(this.#config?.userDefinedProperties ?? []);
        this.#configureViews();
        this._configured = true;
    }
    #checkIfInitialized() {
        if (this._repository) {
            this.#initialized = true;
            this.#initResolver?.();
        }
    }
    #observeRepository(repositoryAlias) {
        if (!repositoryAlias)
            throw new Error('Tree must have a repository alias.');
        new UmbExtensionApiInitializer(this, umbExtensionsRegistry, repositoryAlias, [this._host], (permitted, ctrl) => {
            this._repository = permitted ? ctrl.api : undefined;
            this.#checkIfInitialized();
        });
    }
    #onPageChange;
    /**
     * Sets the configuration for the collection.
     * @param {UmbCollectionConfiguration} config
     * @memberof UmbCollectionContext
     */
    setConfig(config) {
        this.#config = config;
        this._configure();
    }
    getConfig() {
        return this.#config;
    }
    set manifest(manifest) {
        if (this._manifest === manifest)
            return;
        this._manifest = manifest;
        this.#observeRepository(this._manifest?.meta.repositoryAlias);
    }
    get manifest() {
        return this._manifest;
    }
    getEmptyLabel() {
        return this.manifest?.meta.noItemsLabel ?? this.#config?.noItemsLabel ?? '#collection_noItemsTitle';
    }
    /**
     * Requests the collection from the repository.
     * @returns {Promise<void>}
     * @deprecated Deprecated since v.17.0.0. Use `loadCollection` instead.
     * @memberof UmbCollectionContext
     */
    async requestCollection() {
        new UmbDeprecation({
            removeInVersion: '19.0.0',
            deprecated: 'requestCollection',
            solution: 'Use .loadCollection method instead',
        }).warn();
        return this._requestCollection();
    }
    async _requestCollection() {
        await this._init;
        if (!this._configured)
            this._configure();
        if (!this._repository)
            throw new Error(`Missing repository for ${this._manifest}`);
        this._loading.setValue(true);
        const filter = this._filter.getValue();
        const { data } = await this._repository.requestCollection(filter);
        if (data) {
            this._items.setValue(data.items);
            this._totalItems.setValue(data.total);
            this.pagination.setTotalItems(data.total);
        }
        this._loading.setValue(false);
    }
    /**
     * Sets the filter for the collection and refreshes the collection.
     * @param {Partial<FilterModelType>} filter
     * @memberof UmbCollectionContext
     */
    setFilter(filter) {
        this._filter.setValue({ ...this._filter.getValue(), ...filter });
        this.loadCollection();
    }
    updateFilter(filter) {
        this._filter.setValue({ ...this._filter.getValue(), ...filter });
    }
    getLastSelectedView(unique) {
        if (!unique)
            return;
        const layouts = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY) ?? '{}') ?? {};
        if (!layouts)
            return;
        return layouts[unique];
    }
    setLastSelectedView(unique, viewAlias) {
        if (!unique)
            return;
        const layouts = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY) ?? '{}') ?? {};
        if (!layouts)
            return;
        layouts[unique] = viewAlias;
        localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(layouts));
    }
    #onReloadStructureRequest;
    #onReloadChildrenRequest;
    destroy() {
        this.#actionEventContext?.removeEventListener(UmbRequestReloadStructureForEntityEvent.TYPE, this.#onReloadStructureRequest);
        this.#actionEventContext?.removeEventListener(UmbRequestReloadChildrenOfEntityEvent.TYPE, this.#onReloadChildrenRequest);
        super.destroy();
    }
    /**
     * Sets the manifest for the collection.
     * @param {ManifestCollection} manifest - The manifest for the collection.
     * @memberof UmbCollectionContext
     * @deprecated Use set the `.manifest` property instead.
     */
    setManifest(manifest) {
        if (this._manifest === manifest)
            return;
        this._manifest = manifest;
        if (!this._manifest)
            return;
        this.#observeRepository(this._manifest.meta.repositoryAlias);
    }
    /**
     * Returns the manifest for the collection.
     * @returns {ManifestCollection} - The manifest for the collection.
     * @memberof UmbCollectionContext
     * @deprecated Use the `.manifest` property instead.
     */
    getManifest() {
        new UmbDeprecation({
            removeInVersion: '18.0.0',
            deprecated: 'getManifest',
            solution: 'Use .manifest property instead',
        }).warn();
        return this._manifest;
    }
    /**
     * Returns the items in the collection.
     * @returns {Array<CollectionItemType>} - The items in the collection.
     */
    getItems() {
        return this._items.getValue();
    }
    /**
     * Returns the href for a specific collection item.
     * Override this method in specialized collection contexts to provide item-specific hrefs.
     * @param {CollectionItemType} _item  - The collection item to get the href for.
     * @returns {Promise<string | undefined>} - Undefined. The collection item does not link to anything by default.
     */
    async requestItemHref(_item) {
        return undefined;
    }
    #configureSelection() {
        const selectionConfiguration = this.#config?.selectionConfiguration;
        this.selection.setConfig(selectionConfiguration);
        const selectOnly = selectionConfiguration?.selectOnly;
        this._selectOnly.setValue(selectOnly === true);
        // If there is an selection, and selectOnly is not explicitly set, set selectOnly in context when there is more than 0 items selected.
        this.observe(this.selection.selection, (selection) => {
            if (selectOnly === undefined) {
                this._selectOnly.setValue(selection.length > 0);
            }
        });
    }
    #configureViews() {
        const viewManagerConfig = { defaultViewAlias: this.#defaultViewAlias };
        const layouts = this.#config?.layouts;
        if (layouts && layouts.length > 0) {
            this.#viewLayouts.setValue(layouts);
            viewManagerConfig.viewsOverride = layouts;
        }
        this.view.setConfig(viewManagerConfig);
    }
}
