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';
/**
 * @export
 * @class UmbRepositoryDetailsManager
 * @augments {UmbControllerBase}
 * @template DetailType
 */
export class UmbRepositoryDetailsManager extends UmbControllerBase {
    #init;
    #eventContext;
    // the init promise is used externally for recognizing when the manager is ready.
    get init() {
        return this.#init;
    }
    #uniques;
    #entries;
    #statuses;
    /**
     * Creates an instance of UmbRepositoryDetailsManager.
     * @param {UmbControllerHost} host - The host for the controller.
     * @param {string} repository - The alias of the repository to use.
     * @memberof UmbRepositoryDetailsManager
     */
    constructor(host, repository) {
        super(host);
        this.#uniques = new UmbArrayState([], (x) => x);
        this.uniques = this.#uniques.asObservable();
        this.#entries = new UmbArrayState([], (x) => x.unique);
        this.entries = this.#entries.asObservable();
        this.#statuses = new UmbArrayState([], (x) => x.unique);
        this.statuses = this.#statuses.asObservable();
        this.#onEntityUpdatedEvent = (event) => {
            const eventUnique = event.getUnique();
            const items = this.getEntries();
            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.#reloadDetails(item.unique);
        };
        this.#entries.sortBy((a, b) => {
            const uniques = this.getUniques();
            const aIndex = uniques.indexOf(a.unique);
            const bIndex = uniques.indexOf(b.unique);
            return aIndex - bIndex;
        });
        if (typeof repository === 'string') {
            this.#init = new UmbExtensionApiInitializer(this, umbExtensionsRegistry, repository, [this], (permitted, repository) => {
                this.repository = permitted ? repository.api : undefined;
            }).asPromise();
        }
        else {
            this.repository = repository;
            this.#init = Promise.resolve();
        }
        this.observe(this.uniques, (uniques) => {
            // remove entries based on no-longer existing uniques:
            const removedEntries = this.#entries
                .getValue()
                .filter((entry) => !uniques.includes(entry.unique))
                .map((x) => x.unique);
            this.#statuses.remove(removedEntries);
            this.#entries.remove(removedEntries);
            removedEntries.forEach((entry) => {
                this.removeUmbControllerByAlias('observeEntry_' + entry);
            });
            this.#requestNewDetails(uniques);
        }, 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);
        });
    }
    /**
     * Clear the manager
     * @memberof UmbRepositoryDetailsManager
     */
    clear() {
        this.#uniques.setValue([]);
        this.#entries.setValue([]);
        this.#statuses.setValue([]);
    }
    /**
     * Get the uniques in the manager
     * @returns {Array<string>} - The uniques in the manager.
     * @memberof UmbRepositoryDetailsManager
     */
    getUniques() {
        return this.#uniques.getValue();
    }
    /**
     * Set the uniques in the manager
     * @param {(string[] | undefined)} uniques
     * @memberof UmbRepositoryDetailsManager
     */
    setUniques(uniques) {
        this.#uniques.setValue(uniques ?? []);
    }
    /**
     * Add a unique to the manager
     * @param {string} unique
     * @memberof UmbRepositoryDetailsManager
     */
    addUnique(unique) {
        this.#uniques.appendOne(unique);
    }
    /**
     * Add an entry to the manager
     * @param {DetailType} data
     * @memberof UmbRepositoryDetailsManager
     */
    addEntry(data) {
        const unique = data.unique;
        this.#entries.appendOne(data);
        // Add status BEFORE unique to prevent #requestNewDetails from trying to fetch
        // (the observation callback on #uniques checks #statuses to skip already-loaded entries)
        this.#statuses.appendOne({
            state: {
                type: 'success',
            },
            unique,
        });
        this.#uniques.appendOne(unique);
        // Notice in this case we do not have a observable from the repo, but it should maybe be fine that we just listen for ACTION EVENTS.
    }
    /**
     * Get all entries in the manager
     * @returns {Array<DetailType>} - The entries in the manager.
     * @memberof UmbRepositoryDetailsManager
     */
    getEntries() {
        return this.#entries.getValue();
    }
    /**
     * Get an entry observable by unique
     * @param {string} unique
     * @returns {Observable<DetailType | undefined>} - The entry observable.
     * @memberof UmbRepositoryDetailsManager
     */
    entryByUnique(unique) {
        return this.#entries.asObservablePart((items) => items.find((item) => item.unique === unique));
    }
    async #requestNewDetails(uniques) {
        if (!uniques?.length)
            return;
        await this.#init;
        if (!this.repository)
            throw new Error('Repository is not initialized');
        const newRequestedUniques = uniques.filter((unique) => {
            const item = this.#statuses.getValue().find((status) => status.unique === unique);
            return !item;
        });
        if (newRequestedUniques.length === 0)
            return;
        // Use bulk endpoint when available, fall back to individual requests
        if (this.repository.requestByUniques) {
            this.#requestDetailsBulk(newRequestedUniques);
        }
        else {
            newRequestedUniques.forEach((unique) => {
                this.#requestDetails(unique);
            });
        }
    }
    async #requestDetailsBulk(uniques) {
        await this.#init;
        if (!this.repository?.requestByUniques)
            throw new Error('Repository does not support requestByUniques');
        for (const unique of uniques) {
            this.#setStatus(unique, 'loading');
        }
        const { data, error, asObservable } = await this.repository.requestByUniques(uniques);
        if (error) {
            for (const unique of uniques) {
                this.#setError(unique);
            }
            return;
        }
        if (data) {
            const currentUniques = this.getUniques();
            for (const unique of uniques) {
                // Check it still exists in uniques (may have been removed while request was in flight)
                if (!currentUniques.includes(unique)) {
                    this.#statuses.removeOne(unique);
                    continue;
                }
                const item = data.find((d) => d.unique === unique);
                if (item) {
                    this.#setSuccess(unique, item);
                }
                else {
                    // Item was requested but not returned — treat as not found
                    this.#setError(unique);
                }
            }
            // Set up per-item store observation for reactivity
            const successUniques = uniques.filter((u) => currentUniques.includes(u) && data.some((d) => d.unique === u));
            for (const unique of successUniques) {
                const observable = asObservable?.();
                if (observable) {
                    this.observe(observable, (items) => {
                        const item = items?.find((d) => d.unique === unique);
                        if (item) {
                            this.#entries.updateOne(unique, item);
                        }
                        else {
                            this.#entries.removeOne(unique);
                        }
                    }, 'observeEntry_' + unique);
                }
            }
        }
    }
    async #reloadDetails(unique) {
        return await this.#requestDetails(unique);
    }
    async #requestDetails(unique) {
        await this.#init;
        if (!this.repository)
            throw new Error('Repository is not initialized');
        this.#setStatus(unique, 'loading');
        const { data, error, asObservable } = await this.repository.requestByUnique(unique);
        if (error) {
            this.#setError(unique);
        }
        if (data) {
            // Check it still exists in uniques:
            const uniques = this.getUniques();
            if (!uniques.includes(unique)) {
                this.#statuses.removeOne(unique);
                return;
            }
            this.#setSuccess(unique, data);
            const observable = asObservable?.();
            if (observable) {
                this.#observeEntry(unique, observable);
            }
        }
    }
    #setStatus(unique, type) {
        this.#statuses.appendOne({
            state: { type },
            unique,
        });
    }
    #setError(unique) {
        this.#statuses.appendOne({
            state: {
                type: 'error',
                error: '#general_notFound',
            },
            unique,
        });
        this.#entries.removeOne(unique);
        this.removeUmbControllerByAlias('observeEntry_' + unique);
    }
    #setSuccess(unique, data) {
        this.#entries.appendOne(data);
        this.#setStatus(unique, 'success');
    }
    #observeEntry(unique, observable) {
        this.observe(observable, (data) => {
            if (data) {
                this.#entries.updateOne(unique, data);
            }
            else {
                this.#entries.removeOne(unique);
            }
        }, 'observeEntry_' + unique);
    }
    #onEntityUpdatedEvent;
    destroy() {
        this.#eventContext?.removeEventListener(UmbEntityUpdatedEvent.TYPE, this.#onEntityUpdatedEvent);
        super.destroy();
    }
}
