import { CollectionState, CollectionOptimisticActions, parseFqdn, } from '@wix/bex-core';
import { ToolbarCollectionState } from '../ToolbarCollectionState';
import { KanbanStageState } from './KanbanStageState';
import { observable, makeObservable, runInAction, action, computed, reaction, comparer, } from 'mobx';
import { KanbanDragAndDropState } from '../../components/KanbanDragAndDropDndKit/KanbanCardDragAndDrop';
import { v4 as uuid } from 'uuid';
import { EventEmitter } from 'events';
import { KanbanStateBIReporter } from './KanbanStateBIReporter';
export const KanbanComponentType = 'TableKanbanSwitch';
export class KanbanState {
    constructor(params) {
        this.kanbanStageStates = {};
        this.stageStateDisposers = {};
        this.isRefetching = false;
        this.isChangingFiltersValue = false;
        this.events = new EventEmitter();
        this._searchResultsBiEventCallback = null;
        this.container = params.container;
        this.kanbanId = params.kanbanId;
        this.items = params.items;
        this.toolbarQueryCollectionState =
            this.createToolbarQueryCollectionState(params);
        this.stageType = params.stages.stageType;
        this.stagesCollectionState = this.createStagesCollectionState(params);
        this.stagesOptimisticActions = new CollectionOptimisticActions({
            collection: this.stagesCollectionState,
            container: this.container,
        });
        this.toolbar = new ToolbarCollectionState({
            collection: this.toolbarQueryCollectionState,
            container: params.container,
            componentType: KanbanComponentType,
            reportBi: (ev) => {
                // Filter out BI events to avoid duplicates - KanbanStateBIReporter handles them
                const isSearchEvent = ev.src === 144 && (ev.evid === 116 || ev.evid === 117); // cairoFilterToggled or cairoSearchResults or cairoLoadEnd
                const isLoadEndEvent = ev.src === 144 && ev.evid === 111; // cairoLoadEnd
                if (!isSearchEvent && !isLoadEndEvent) {
                    this.container.reportBi(ev);
                }
            },
        });
        this.dragAndDropState = new KanbanDragAndDropState({
            kanbanState: this,
        });
        this.kanbanStateBIReporter = new KanbanStateBIReporter(this);
        makeObservable(this, {
            kanbanStageStates: observable,
            updateStage: action,
            stages: computed,
            isRefetching: observable,
            refetchKanban: action,
            hasStagesError: computed,
            isLoading: computed,
            isLoadingStages: computed,
        });
        this.debouncedStagesRefresh = this.container.lodash.debounce(async () => {
            this.isChangingFiltersValue = false;
            const currentSearchTerm = this.queryComputed.search || '';
            if (currentSearchTerm) {
                this._searchResultsBiEventCallback =
                    this.kanbanStateBIReporter.reportSearchTriggered(currentSearchTerm);
            }
            else {
                this._searchResultsBiEventCallback = null;
            }
            await this.initializeStageStates();
            if (this._searchResultsBiEventCallback) {
                this._searchResultsBiEventCallback();
                this._searchResultsBiEventCallback = null;
            }
        }, 300);
    }
    get query() {
        return this.toolbarQueryCollectionState.query;
    }
    get filters() {
        return this.toolbarQueryCollectionState.filters;
    }
    get queryComputed() {
        return this.query.asComputed;
    }
    triggerDebouncedStagesRefresh() {
        this.isChangingFiltersValue = true;
        this.debouncedStagesRefresh();
    }
    async init() {
        const disposers = [
            this.stagesCollectionState.init(),
            this.stagesOptimisticActions.init(),
            this.kanbanStateBIReporter.initAdditionalFeaturesAvailability(),
            this.toolbar.init({ featuresInitializers: [] }),
            this.dragAndDropState.init(),
            reaction(() => ({
                filters: this.queryComputed.filters,
                search: this.queryComputed.search,
            }), () => {
                this.triggerDebouncedStagesRefresh();
            }, {
                fireImmediately: false,
                equals: comparer.structural,
            }),
        ];
        this.kanbanStateBIReporter.init();
        await this.loadStages();
        await this.initializeStageStates();
        this.kanbanStateBIReporter.appLoaded?.();
        return () => {
            Object.keys(this.stageStateDisposers).forEach((stageId) => {
                this.disposeStageState(stageId);
            });
            disposers.forEach((d) => d?.());
        };
    }
    async loadStages() {
        await this.stagesCollectionState?.initTask?.status?.promise?.catch((error) => {
            this.showErrorToast(error);
        });
    }
    async initializeStageStates() {
        if (!this.items.fetchAllByStageIds) {
            return;
        }
        try {
            const stageData = await this.fetchStagesData();
            this.processAllStages(stageData);
        }
        catch (error) {
            this.showErrorToast(error);
        }
    }
    get stages() {
        return this.stagesCollectionState.result.keyedItems.map((item) => item.item);
    }
    getStageState(stage) {
        return this.kanbanStageStates[this.stagesCollectionState.itemKey(stage)];
    }
    getStageStateByKey(stageKey) {
        return this.kanbanStageStates[stageKey];
    }
    get isLoading() {
        return (this.isLoadingStages ||
            Object.values(this.kanbanStageStates).some((stage) => stage.isLoading));
    }
    get isLoadingStages() {
        return (this.stagesCollectionState.initTask.status.isLoading ||
            this.isRefetching ||
            Object.keys(this.kanbanStageStates).length !==
                this.stagesCollectionState.result.keyedItems.length);
    }
    isInSearchMode() {
        const search = this.queryComputed.search;
        return search && search.length > 0;
    }
    isInFilterMode() {
        return this.queryComputed.hasActiveFilters;
    }
    getIsChangingFiltersValue() {
        return this.isChangingFiltersValue;
    }
    stageKey(stage) {
        return this.stagesCollectionState.itemKey(stage);
    }
    get hasStagesError() {
        return this.stagesCollectionState.result.status?.isError;
    }
    // Public methods
    async refetchKanban() {
        const startTime = performance.now();
        this.toolbar.toolbarBIReporter.loadStart();
        runInAction(() => {
            this.isRefetching = true;
        });
        try {
            await this.stagesCollectionState.clearResultBeforeRefresh();
            await this.stagesCollectionState.refreshAllPages();
            await this.initializeStageStates();
        }
        catch (error) {
            this.showErrorToast(error);
        }
        finally {
            // Report refetch completion with timing
            this.kanbanStateBIReporter.reportOnLoadEnd(Math.round(performance.now() - startTime));
            runInAction(() => {
                this.isRefetching = false;
            });
        }
    }
    // Private methods
    showErrorToast(error) {
        const resolvedError = this.container.errorHandler?.getResolvedError?.(error);
        resolvedError && this.container.errorHandler?.showError?.(resolvedError);
    }
    createToolbarQueryCollectionState(params) {
        return new CollectionState({
            errorMonitor: this.container.errorMonitor,
            history: this.container.history,
            lodash: this.container.lodash,
            onlineState: this.container.onlineState,
            queryCache: this.container.queryCache,
            translate: this.container.translate,
            fqdn: params.fqdn ? parseFqdn(params.fqdn) : undefined,
            queryName: `kanban-toolbar-query-items-${this.kanbanId}`,
            fetchData: () => Promise.resolve({
                items: [],
                total: 0,
                cursor: null,
            }),
            itemKey: () => '',
            itemName: () => '',
            filters: params.items.filters ?? {},
            errorHandler: this.container.errorHandler,
            paginationMode: 'cursor',
        });
    }
    createStagesCollectionState(params) {
        return new CollectionState({
            errorMonitor: this.container.errorMonitor,
            history: this.container.history,
            lodash: this.container.lodash,
            onlineState: this.container.onlineState,
            queryCache: this.container.queryCache,
            translate: this.container.translate,
            queryName: `kanban-stages-${params.kanbanId}`,
            fetchData: params.stages.fetchData,
            itemKey: params.stages.stageKey,
            itemName: params.stages.stageName,
            filters: {},
            errorHandler: this.container.errorHandler,
            paginationMode: 'cursor',
        });
    }
    async fetchStagesData() {
        const stageIds = this.getStageIds();
        return this.items.fetchAllByStageIds({
            ids: stageIds,
            filters: this.queryComputed.filters,
            search: this.queryComputed.search,
        });
    }
    getStageIds() {
        return this.stagesCollectionState.result.keyedItems.map((item) => this.stagesCollectionState.itemKey(item.item));
    }
    processAllStages(stageData) {
        this.cleanupUnusedStageStates();
        this.processCurrentStages(stageData);
    }
    cleanupUnusedStageStates() {
        // Get current stage IDs to identify which ones are no longer needed
        const currentStageIds = new Set(this.stagesCollectionState.result.keyedItems.map((item) => this.stagesCollectionState.itemKey(item.item)));
        // Clean up stage states that no longer exist
        Object.keys(this.kanbanStageStates).forEach((stageId) => {
            if (!currentStageIds.has(stageId)) {
                this.disposeStageState(stageId);
            }
        });
    }
    disposeStageState(stageId) {
        // Dispose of the stage state using stored disposer
        const disposer = this.stageStateDisposers[stageId];
        if (disposer) {
            disposer();
            Reflect.deleteProperty(this.stageStateDisposers, stageId);
        }
        runInAction(() => {
            Reflect.deleteProperty(this.kanbanStageStates, stageId);
        });
    }
    processCurrentStages(stageData) {
        this.stagesCollectionState.result.keyedItems.forEach((keyedStage) => {
            const stage = keyedStage.item;
            runInAction(() => {
                const stageId = this.stagesCollectionState.itemKey(stage);
                const hasDataForStage = !!stageData[stageId];
                if (!this.kanbanStageStates[stageId]) {
                    this.createNewStageState(stage, stageId, stageData[stageId], hasDataForStage);
                }
                else {
                    this.updateExistingStageState(stage, stageId, stageData[stageId], hasDataForStage);
                }
            });
        });
    }
    createNewStageState(stage, stageId, stageData, hasDataForStage) {
        this.kanbanStageStates[stageId] = new KanbanStageState({
            container: this.container,
            fetchData: this.items.fetchData,
            itemKey: this.items.itemKey,
            itemName: this.items.itemName,
            initialPage: hasDataForStage
                ? this.getInitialPageData(stageData)
                : {
                    items: [],
                    total: 0,
                    cursor: null,
                },
            name: this.stagesCollectionState.itemName(stage),
            key: this.stagesCollectionState.itemKey(stage),
            kanbanId: this.kanbanId,
            stage,
            query: this.query,
        });
        const disposer = this.kanbanStageStates[stageId].init();
        this.stageStateDisposers[stageId] = disposer;
    }
    updateExistingStageState(stage, stageId, stageData, hasDataForStage) {
        const stageState = this.kanbanStageStates[stageId];
        stageState.name = this.stagesCollectionState.itemName(stage);
        runInAction(() => {
            const initialPageData = this.getInitialPageData(hasDataForStage
                ? stageData
                : {
                    items: [],
                    total: 0,
                    cursor: null,
                });
            stageState.itemsCollectionState.setInitialPage(initialPageData);
            // Directly update QueryResultState to trigger MobX reactivity for keyedItems
            const items = initialPageData.items || [];
            const total = initialPageData.total ?? items.length;
            const cursor = initialPageData.cursor;
            stageState.itemsCollectionState.result.setPages({
                processed: [items],
                unprocessed: [items],
            });
            stageState.itemsCollectionState.result.setTotals({
                processed: total,
                unprocessed: total,
            });
            stageState.setTotal(stageData?.total ?? 0);
            if (cursor != null) {
                stageState.itemsCollectionState.paginationMode.setCursor(cursor);
            }
        });
    }
    getInitialPageData(stageData) {
        return {
            items: stageData?.items,
            total: stageData?.total,
            cursor: stageData?.cursor,
        };
    }
    getStageIdFromItem(item) {
        return item[this.items.stageField];
    }
    // optimistic actions
    updateStage(originalStage, updatedStage) {
        const stageId = this.stagesCollectionState.itemKey(originalStage);
        const stageState = this.getStageStateByKey(stageId);
        this.stagesCollectionState.optimisticActions.updateOne(updatedStage, {
            submit: async (items) => {
                // just return the item that was passed in
                return Promise.resolve(items[0]);
            },
            keepPosition: true,
        });
        runInAction(() => {
            if (stageState.name !== this.stagesCollectionState.itemName(updatedStage)) {
                stageState.name = this.stagesCollectionState.itemName(updatedStage);
            }
        });
    }
    setStages(stages) {
        runInAction(() => {
            const getKey = (stage) => this.stagesCollectionState.itemKey(stage);
            const currentStageIds = new Set(Object.keys(this.kanbanStageStates));
            const newStageIds = new Set(stages.map(getKey));
            this.stagesCollectionState.result.setPages({
                processed: [stages],
                unprocessed: [stages],
            });
            this.stagesCollectionState.result.setTotals({
                processed: stages.length,
                unprocessed: stages.length,
            });
            currentStageIds.forEach((stageId) => {
                if (!newStageIds.has(stageId)) {
                    this.disposeStageState(stageId);
                }
            });
            stages.forEach((stage) => {
                const stageId = getKey(stage);
                if (!this.kanbanStageStates[stageId]) {
                    this.createNewStageState(stage, stageId, { items: [], total: 0, cursor: null }, false);
                }
                else {
                    this.kanbanStageStates[stageId].name =
                        this.stagesCollectionState.itemName(stage);
                }
            });
        });
    }
    createStage(stage) {
        this.stagesCollectionState.optimisticActions.createOne(stage, {
            submit: (_items) => Promise.resolve(),
        });
        runInAction(() => {
            const stageId = this.stagesCollectionState.itemKey(stage);
            this.createNewStageState(stage, stageId, {
                items: [],
                total: 0,
                cursor: null,
            }, false);
        });
    }
    scrollToStage({ stageKey }) {
        this.events.emit('scrollToStage', { stageKey });
    }
    scrollToCardInStage({ stageKey, cardKey, }) {
        this.events.emit('scrollToCardInStage', { stageKey, cardKey });
    }
    deleteStage(stage) {
        const stageId = this.stagesCollectionState.itemKey(stage);
        this.stagesCollectionState.optimisticActions.deleteOne(stage, {
            submit: (_items) => Promise.resolve(),
        });
        this.disposeStageState(stageId);
    }
    reorderStages(fromIndex, toIndex) {
        const keyedItems = this.stagesCollectionState.result.keyedItems;
        if (fromIndex === toIndex) {
            return;
        }
        const from = keyedItems[fromIndex];
        const over = keyedItems[toIndex];
        let after;
        if (toIndex === 0) {
            after = null;
        }
        else if (fromIndex < toIndex) {
            after = over;
        }
        else {
            after = keyedItems[toIndex - 1];
        }
        const actionMove = {
            moveId: uuid(),
            from,
            over,
            after,
            isFromHandle: false,
            filtersKeyHash: this.stagesCollectionState.query.filtersKeyHash,
            query: this.stagesCollectionState.originQuery,
        };
        this.stagesCollectionState.optimisticActions._orderByMode = 'moves';
        this.stagesCollectionState.optimisticActions.move(actionMove, {
            collectionSnapshot: {},
        });
    }
    moveItemBetweenStages(originalItem, updatedItem, fromStageId, toStageId) {
        this.getStageStateByKey(fromStageId).deleteItem(originalItem);
        this.getStageStateByKey(toStageId).createItem(updatedItem);
    }
    // optimistic actions card
    deleteCard(card) {
        const stageId = this.getStageIdFromItem(card);
        this.getStageStateByKey(stageId).deleteItem(card);
    }
    createCard(card) {
        const stageId = this.getStageIdFromItem(card);
        this.getStageStateByKey(stageId).createItem(card);
    }
    updateCard(card) {
        const stageId = this.getStageIdFromItem(card);
        this.getStageStateByKey(stageId).updateItem(card);
    }
    // Helper functions for BI reporting
    getStageIndexByKey(stageKey) {
        return this.stages.findIndex((stage) => this.stagesCollectionState.itemKey(stage) === stageKey);
    }
    getStageNameByKey(stageKey) {
        const stage = this.stages.find((s) => this.stagesCollectionState.itemKey(s) === stageKey);
        return stage ? this.stagesCollectionState.itemName(stage) : '';
    }
    getStageById(stageKey) {
        return this.stages.find((stage) => this.stagesCollectionState.itemKey(stage) === stageKey);
    }
}
//# sourceMappingURL=KanbanState.js.map