import { UmbArrayState } from '../../../libs/observable-api/index.js';
import { umbExtensionsRegistry } from '../extension-registry/index.js';
import { UmbExtensionApiInitializer } from '../../../libs/extension-api/index.js';
import { UmbControllerBase } from '../../../libs/class-api/index.js';
import { UMB_ACTION_EVENT_CONTEXT } from '../action/index.js';
import { UmbEntityUpdatedEvent } from '../entity-action/index.js';
import { UmbDeprecation } from '../utils/index.js';
const ObserveRepositoryAlias = Symbol();
export class UmbRepositoryItemsManager extends UmbControllerBase {
    #init;
    #initResolve;
    #currentRequest;
    #eventContext;
    // the init promise is used externally for recognizing when the manager is ready.
    get init() {
        return this.#init;
    }
    #uniques;
    #items;
    #statuses;
    // TODO: Align with the other manager(details), and make a generic type/base for these. v.17.0 [NL]
    /**
     * Creates an instance of UmbRepositoryItemsManager.
     * @param {UmbControllerHost} host - The host for the controller.
     * @param {string} repositoryAlias - The alias of the repository to use.
     * @memberof UmbRepositoryItemsManager
     */
    constructor(host, repositoryAlias) {
        super(host);
        this.#uniques = new UmbArrayState([], (x) => x);
        this.uniques = this.#uniques.asObservable();
        this.#items = new UmbArrayState([], (x) => x.unique);
        this.items = this.#items.asObservable();
        this.#statuses = new UmbArrayState([], (x) => x.unique);
        this.statuses = this.#statuses.asObservable();
        this.#onEntityUpdatedEvent = (event) => {
            const eventUnique = event.getUnique();
            const items = this.getItems();
            if (items.length === 0)
                return;
            // Ignore events if the entity is not in the list of items.
            const item = items.find((item) => item.unique === eventUnique);
            if (!item)
                return;
            this.#reloadItem(item.unique);
        };
        this.#init = new Promise((resolve) => {
            this.#initResolve = resolve;
        });
        if (repositoryAlias) {
            this.#initItemRepository(repositoryAlias);
        }
        this.observe(this.uniques, (uniques) => {
            if (uniques.length === 0) {
                this.#items.setValue([]);
                this.#statuses.setValue([]);
                return;
            }
            // TODO: This could be optimized so we only load the appended items, but this requires that the response checks that an item is still present in uniques. [NL]
            // Check if we already have the statuses, and then just sort them:
            const statuses = this.#statuses.getValue();
            if (uniques.length === statuses.length &&
                uniques.every((unique) => statuses.find((status) => status.unique === unique))) {
                const items = this.#items.getValue();
                this.#items.setValue(this.#sortByUniques(items));
                this.#statuses.setValue(this.#sortByUniques(statuses));
            }
            else {
                // We need to load new items, so ...
                this.#requestItems();
            }
        }, null);
        this.consumeContext(UMB_ACTION_EVENT_CONTEXT, (context) => {
            this.#eventContext?.removeEventListener(UmbEntityUpdatedEvent.TYPE, this.#onEntityUpdatedEvent);
            this.#eventContext = context;
            this.#eventContext?.addEventListener(UmbEntityUpdatedEvent.TYPE, this.#onEntityUpdatedEvent);
        });
    }
    /**
     * Sets the item repository to use for this manager.
     * @param {(UmbItemRepository<ItemType> | undefined)} itemRepository - The item repository to set.
     * @memberof UmbRepositoryItemsManager
     */
    setItemRepository(itemRepository) {
        this.repository = itemRepository;
        this.#initResolve(undefined);
    }
    /**
     * Gets the item repository used by this manager.
     * @returns {(UmbItemRepository<ItemType> | undefined)} The item repository.
     * @memberof UmbRepositoryItemsManager
     */
    getItemRepository() {
        return this.repository;
    }
    getUniques() {
        return this.#uniques.getValue();
    }
    setUniques(uniques) {
        this.#uniques.setValue(uniques ?? []);
    }
    getItems() {
        return this.#items.getValue();
    }
    itemByUnique(unique) {
        return this.#items.asObservablePart((items) => items.find((item) => item.unique === unique));
    }
    /**
     * @deprecated - This is resolved by setUniques, no need to update statuses.
     * @param unique {string} - The unique identifier of the item to remove the status of.
     */
    removeStatus(unique) {
        new UmbDeprecation({
            removeInVersion: '18.0.0',
            deprecated: 'removeStatus',
            solution: 'Statuses are removed automatically when setting uniques',
        }).warn();
        this.#statuses.filter((status) => status.unique !== unique);
    }
    async getItemByUnique(unique) {
        // TODO: Make an observeOnce feature, to avoid this amount of code: [NL]
        const ctrl = this.observe(this.itemByUnique(unique), () => { }, null);
        const result = await ctrl.asPromise();
        ctrl.destroy();
        return result;
    }
    async #requestItems() {
        await this.#init;
        if (!this.repository)
            throw new Error('Repository is not initialized');
        const requestedUniques = this.getUniques();
        this.#statuses.setValue(
        // No need to do sorting here as we just got the unique in the right order above.
        requestedUniques.map((unique) => ({
            state: {
                type: 'loading',
            },
            unique,
        })));
        // TODO: Test if its just some items that is gone now, if so then just filter them out. (maybe use code from #removeItem)
        // This is where this.#getUnique comes in play. Unless that can come from the repository, but that collides with the idea of having a multi-type repository. If that happens.
        const request = this.repository.requestItems(requestedUniques);
        this.#currentRequest = request;
        const { asObservable, data, error } = await request;
        if (this.#currentRequest !== request) {
            // You are not the newest request, so please back out.
            return;
        }
        if (error) {
            this.#statuses.replace(requestedUniques.map((unique) => ({
                state: {
                    type: 'error',
                    error: '#general_error',
                },
                unique,
            })));
            return;
        }
        // find uniques not resolved:
        if (data) {
            // find rejected uniques:
            const rejectedUniques = requestedUniques.filter((unique) => !data.find((item) => item.unique === unique));
            const resolvedUniques = requestedUniques.filter((unique) => !rejectedUniques.includes(unique));
            this.#items.remove(rejectedUniques);
            this.#statuses.replace([
                ...rejectedUniques.map((unique) => ({
                    state: {
                        type: 'error',
                        error: '#general_notFound',
                    },
                    unique,
                })),
                ...resolvedUniques.map((unique) => ({
                    state: {
                        type: 'success',
                    },
                    unique,
                })),
            ]);
        }
        if (asObservable) {
            this.observe(asObservable(), (data) => {
                this.#items.setValue(this.#sortByUniques(data));
            }, ObserveRepositoryAlias);
        }
        else if (data) {
            this.#items.setValue(data);
        }
    }
    async #reloadItem(unique) {
        await this.#init;
        if (!this.repository)
            throw new Error('Repository is not initialized');
        const { data, error } = await this.repository.requestItems([unique]);
        if (error) {
            this.#statuses.updateOne(unique, {
                state: {
                    type: 'error',
                    error: '#general_notFound',
                },
            });
        }
        if (data) {
            const items = this.getItems();
            const item = items.find((item) => item.unique === unique);
            if (item) {
                const index = items.indexOf(item);
                const newItems = [...items];
                newItems[index] = data[0];
                this.#items.setValue(this.#sortByUniques(newItems));
                // No need to update statuses here, as the item is the same, just updated.
            }
        }
    }
    #sortByUniques(data) {
        if (!data)
            return [];
        const uniques = this.getUniques();
        return [...data].sort((a, b) => {
            const aIndex = uniques.indexOf(a.unique ?? '');
            const bIndex = uniques.indexOf(b.unique ?? '');
            return aIndex - bIndex;
        });
    }
    #onEntityUpdatedEvent;
    async #initItemRepository(itemRepositoryAlias) {
        new UmbExtensionApiInitializer(this, umbExtensionsRegistry, itemRepositoryAlias, [this], (permitted, repository) => {
            this.repository = permitted ? repository.api : undefined;
            this.#initResolve(undefined);
        });
    }
    destroy() {
        this.#eventContext?.removeEventListener(UmbEntityUpdatedEvent.TYPE, this.#onEntityUpdatedEvent);
        super.destroy();
    }
}
