import { CollectionOptimisticActions, CollectionState, ModalState, TaskState, } from '@wix/bex-core';
import { action, computed, makeObservable, observable } from 'mobx';
import { EventEmitter } from '@wix/bex-core/events';
import { ViewsService } from '../services';
import { v4 as uuid } from 'uuid';
import { ALL_ITEMS_SORT_IND, ALL_ITEMS_VIEW_ID, PREDEFINED_VIEWS_SORT_IND, SAVED_VIEWS_SORT_IND, } from '../model';
import { AutoCompleteReadonlyState } from '../components/CollectionViewsDropdown/AutoCompleteReadonlyState';
import { ViewsLocalSearchState } from './ViewsLocalSearchState';
const MAX_SAVED_VIEWS = 50;
const updatePresetViews = (presets, hiddenPresetIds) => {
    return presets.reduce((views, cat, ind) => [
        ...views,
        ...(cat.views
            ?.filter((p) => !hiddenPresetIds.includes(p.id))
            .map(({ isDefaultView, ...p }) => ({
            ...p,
            categoryId: cat.id,
            categorySortInd: `${ind + +PREDEFINED_VIEWS_SORT_IND}`,
            isPreset: true,
        })) ?? []),
    ], []);
};
const toNonPresetViews = (views) => {
    return views.map((v) => ({
        ...v,
        isPreset: false,
        categorySortInd: v.id === ALL_ITEMS_VIEW_ID ? ALL_ITEMS_SORT_IND : SAVED_VIEWS_SORT_IND,
    }));
};
const getBasicCategories = (presets) => {
    return presets.reduce((prev, { views, ...basicCategory }) => ({
        ...prev,
        [basicCategory.id]: basicCategory,
    }), {});
};
export class ViewsState {
    constructor(params) {
        this.saveViewModal = new ModalState();
        this.renameViewModal = new ModalState();
        this.autoCompleteReadonlyState = new AutoCompleteReadonlyState();
        this.events = new EventEmitter();
        this.initTask = new TaskState();
        const { container, fqdn, reportBI } = params;
        this.container = container;
        this.reportBI = reportBI;
        this.initialSelect = params.initialSelect;
        this.presets = params.presets ?? [];
        this.allItemsViewProps = params.allItemsViewProps ?? {};
        const viewsStorage = container._createComponentSettings({
            namespace: 'cairo',
        });
        const { environment } = container;
        const defaultViewStorage = environment.providerType === 'bm'
            ? viewsStorage
            : container.createEssentials({
                dataCapsule: {
                    namespace: 'cairo',
                    scope: environment.componentId,
                    storageStrategy: environment.providerType === 'giza'
                        ? 'userPreferences'
                        : undefined,
                },
            }).dataCapsule;
        this.viewsService = new ViewsService({
            viewsStorage,
            defaultViewStorage,
            fqdn,
            monitor: container.internalMonitor,
            errorHandler: container.errorHandler,
            translate: container.translate,
        });
        this.collection = new CollectionState({
            ...container,
            events: {},
            queryName: [fqdn, 'views'].join('/'),
            fetchData: async () => {
                const { viewsService, collection, allItemsViewProps, container } = this;
                if (collection.initTask.status.isLoading) {
                    await viewsService.migrateFromLocalStorage();
                    await this._migrateOrdersViews?.(this);
                    await this._fixMissingContactsViews?.(this);
                }
                const result = await viewsService.fetchViews();
                const { views = [], hiddenPresetIds = [] } = result ?? {};
                // Check if "All Items" view exists or was deleted by user
                const allItemsExists = views.some((v) => v.id === ALL_ITEMS_VIEW_ID);
                const allItemsWasDeleted = hiddenPresetIds.includes(ALL_ITEMS_VIEW_ID);
                const shouldAddAllItems = !allItemsExists && !allItemsWasDeleted && !allItemsViewProps.hidden;
                if (shouldAddAllItems) {
                    await container.initTask.status.promise;
                    const hasExistingDefault = views.some((v) => v.isDefaultView);
                    const allItemsView = {
                        id: ALL_ITEMS_VIEW_ID,
                        name: allItemsViewProps.name ??
                            container.translate('cairo.viewsDropdown.allItems.label'),
                        isDefaultView: !hasExistingDefault,
                        selectedColumns: allItemsViewProps.selectedColumns,
                        sortDirections: allItemsViewProps.sortDirections,
                        orderedColumns: allItemsViewProps.orderedColumns,
                        layout: allItemsViewProps.layout,
                    };
                    views.unshift(allItemsView);
                }
                const items = [];
                items.push(...(toNonPresetViews(views) ?? []));
                items.push(...updatePresetViews(this.presets, hiddenPresetIds));
                const allItemsIdx = items.findIndex((i) => i.id === ALL_ITEMS_VIEW_ID);
                if (allItemsIdx > 0) {
                    const [allItems] = items.splice(allItemsIdx, 1);
                    items.unshift(allItems);
                }
                return {
                    items,
                };
            },
            itemKey: ({ id }) => id,
            itemName: ({ name }) => name,
            filters: {},
        });
        this.localSearchState = new ViewsLocalSearchState({ views: this });
        this.optimisticActions = new CollectionOptimisticActions({
            container,
            collection: this.collection,
            orderBy: ({ sort }) => [
                ...(sort || []),
                {
                    fieldName: (item) => (item.id === ALL_ITEMS_VIEW_ID ? '0' : '1'),
                    order: 'asc',
                },
                {
                    fieldName: (item) => item.categorySortInd,
                    order: 'asc',
                },
            ],
        });
        this.basicCategories = getBasicCategories(this.presets);
        this.currentView = null;
        this.defaultViewId = null;
        makeObservable(this, {
            isReady: computed,
            isDefaultView: computed,
            currentView: observable.ref,
            defaultViewId: observable.ref,
            selectView: action,
            unselectView: action,
            setDefaultViewId: action,
            _onActionUndo: action,
            reachedViewsLimit: computed,
            isCurrentViewSelected: computed,
        });
    }
    _getAllItemsAndPresetViews() {
        return updatePresetViews(this.presets, []);
    }
    _getInitialSelectItem(items) {
        const { initialSelect, _initialViewIdFromUrl, defaultViewId } = this;
        if (initialSelect == null) {
            if (_initialViewIdFromUrl) {
                return items.find((item) => item.id === _initialViewIdFromUrl);
            }
            if (defaultViewId) {
                return items.find((item) => item.id === defaultViewId);
            }
            const defaultPresetId = this.getDefaultPreset() ?? ALL_ITEMS_VIEW_ID;
            return items.find((item) => item.id === defaultPresetId);
        }
        if (initialSelect === true) {
            return items[0];
        }
        return items.find((item, index) => initialSelect(item, index));
    }
    _onActionUndo(id, actionName, newName) {
        this.events.emit('undoAction', { actionName, viewId: id, newName });
        this.selectView({ id, needFiltersReset: false });
    }
    _onTryAgain(actionName) {
        this.events.emit('tryAgainAction', { actionName });
    }
    _onError(actionName, createdAt, oldViewId, viewId) {
        if (oldViewId) {
            this.selectView({ id: oldViewId, needFiltersReset: false });
        }
        else {
            this.unselectView();
        }
        this.events.emit('errorAction', {
            actionName,
            createdAt,
            viewId: viewId ?? oldViewId ?? '',
        });
    }
    init() {
        const { collection, localSearchState } = this;
        const disposers = [
            collection.init(),
            this.optimisticActions.init(),
            localSearchState.init(),
        ];
        this.initTask.runOnce(async () => {
            await collection.initTask.status.promise;
            this.setDefaultViewId(await this.viewsService.fetchDefaultViewId());
        });
        return () => {
            disposers.forEach((disposer) => disposer());
        };
    }
    getViews() {
        return this.collection.result.items.slice();
    }
    getView(viewId) {
        return this.getViews().find(({ id }) => id === viewId);
    }
    getDefaultPreset() {
        return this.presets
            .reduce((prev, cur) => [...prev, ...cur.views], [])
            .find((view) => view.isDefaultView)?.id;
    }
    get isDefaultView() {
        return this.currentView?.id === this.defaultViewId;
    }
    async saveViewChanges(viewBase) {
        if (!this.currentView) {
            return null;
        }
        const updatedView = {
            ...this.currentView,
            ...viewBase,
        };
        return this.saveView({
            newView: updatedView,
            hasUndo: true,
            oldViewId: this.currentView.id,
        });
    }
    createNewView(name, viewBase) {
        return {
            ...viewBase,
            categorySortInd: SAVED_VIEWS_SORT_IND,
            isPreset: false,
            name,
            id: uuid(),
            extra: this.currentView?.extra,
        };
    }
    async saveView({ newView, hasUndo = false, isNew = false, oldViewId, }) {
        const createdAt = Date.now();
        const immediateAction = () => {
            this.selectView({ id: newView.id });
        };
        this.optimisticActions.createOne(newView, {
            submit: async () => {
                await this.viewsService.saveView(newView);
                return [newView];
            },
            showUndoToast: hasUndo,
            onUndo: () => {
                this._onActionUndo(newView.id, isNew ? 'view-save-new' : 'view-save-changes');
            },
            onTryAgain: () => {
                immediateAction();
                this._onTryAgain(isNew ? 'view-save-new' : 'view-save-changes');
            },
            onError: (_) => {
                this._onError(isNew ? 'view-save-new' : 'view-save-changes', createdAt, oldViewId, newView.id);
            },
            successToast: {
                biName: isNew
                    ? 'cairo-save-new-view-success'
                    : 'cairo-save-view-changes-success',
                message: isNew
                    ? this.collection.translate('cairo.saveView.Success.toast', {
                        viewName: newView.name,
                    })
                    : this.collection.translate('cairo.manageView.saveChanges.Success.toast', {
                        viewName: newView.name,
                    }),
            },
            errorToast: (err) => {
                const resolvedError = this.container.errorHandler?.getResolvedError?.(err);
                return {
                    biName: isNew
                        ? 'cairo-save-new-view-error'
                        : 'cairo-save-view-changes-error',
                    message: resolvedError?.message,
                    ...(resolvedError?.action ? { action: resolvedError?.action } : {}),
                    type: 'ERROR',
                };
            },
        });
        immediateAction();
        return newView;
    }
    async deleteView(view) {
        const { viewsService, currentView } = this;
        const { id, isPreset } = view;
        const isCurrentViewSelected = currentView?.id === id;
        const idForUndo = currentView?.id;
        const prevDefaultView = this.defaultViewId;
        const createdAt = Date.now();
        const immediateAction = async () => {
            if (id === this.defaultViewId) {
                await this.viewsService.updateDefaultView(undefined);
                this.setDefaultViewId(null);
            }
            if (isCurrentViewSelected) {
                this.unselectView();
            }
        };
        this.optimisticActions.deleteOne(view, {
            submit: async () => {
                if (isPreset) {
                    await viewsService.updateHiddenPreset(id);
                }
                else {
                    await viewsService.deleteView(view);
                    // Track "All Items" deletion so it won't be recreated on next load
                    if (id === ALL_ITEMS_VIEW_ID) {
                        await viewsService.updateHiddenPreset(id);
                    }
                }
            },
            onUndo: () => {
                if (!idForUndo) {
                    return;
                }
                this.setDefaultViewId(prevDefaultView);
                this._onActionUndo(idForUndo, 'view-delete');
            },
            onTryAgain: async () => {
                await immediateAction();
                this._onTryAgain('view-delete');
            },
            onError: (_) => {
                this.setDefaultViewId(prevDefaultView);
                this._onError('view-delete', createdAt, idForUndo);
            },
            showUndoToast: true,
            successToast: {
                biName: 'cairo-delete-view-success',
                message: this.collection.translate('cairo.deleteView.Success.toast', {
                    viewName: view.name,
                }),
            },
            errorToast: (err) => {
                const resolvedError = this.container.errorHandler?.getResolvedError?.(err);
                return {
                    biName: 'cairo-delete-view-error',
                    message: resolvedError?.message,
                    ...(resolvedError?.action ? { action: resolvedError?.action } : {}),
                    type: 'ERROR',
                };
            },
        });
        immediateAction();
        return;
    }
    async renameView(newView, oldName) {
        const createdAt = Date.now();
        const immediateAction = () => {
            this.selectView({ id: newView.id });
        };
        this.optimisticActions.createOne(newView, {
            submit: async () => {
                await this.viewsService.saveView(newView, {
                    isRename: true,
                });
                return [newView];
            },
            onUndo: () => {
                this._onActionUndo(newView.id, 'view-rename', newView.name);
            },
            onTryAgain: () => {
                immediateAction();
                this._onTryAgain('view-rename');
            },
            onError: (_) => {
                this._onError('view-rename', createdAt, newView.id);
            },
            showUndoToast: true,
            successToast: {
                biName: 'cairo-rename-view-success',
                message: this.collection.translate('cairo.manageView.Rename.Success.toast', {
                    oldViewName: oldName,
                    newViewName: newView.name,
                }),
            },
            errorToast: (err) => {
                const resolvedError = this.container.errorHandler?.getResolvedError?.(err);
                return {
                    biName: 'cairo-rename-view-error',
                    message: resolvedError?.message,
                    ...(resolvedError?.action ? { action: resolvedError?.action } : {}),
                    type: 'ERROR',
                };
            },
        });
        immediateAction();
        return newView;
    }
    async setAsDefaultView(newView, isDefault) {
        const createdAt = Date.now();
        const prevDefaultView = this.defaultViewId;
        const biName = isDefault ? 'view-set-as-default' : 'view-unset-as-default';
        const immediateAction = () => {
            this.setDefaultViewId(isDefault ? newView.id : null);
        };
        this.optimisticActions.createOne(newView, {
            submit: async () => {
                await this.viewsService.updateDefaultView(isDefault ? newView : undefined);
                return [newView];
            },
            onUndo: () => {
                this.setDefaultViewId(prevDefaultView);
                this._onActionUndo(newView.id, biName);
            },
            onTryAgain: () => {
                immediateAction();
                this._onTryAgain(biName);
            },
            onError: (_) => {
                this.setDefaultViewId(prevDefaultView);
                this._onError(biName, createdAt, newView.id);
            },
            showUndoToast: isDefault,
            successToast: isDefault
                ? {
                    biName: 'cairo-set-default-view-success',
                    message: this.collection.translate('cairo.manageView.setAsDefault.success.toast', {
                        viewName: newView.name,
                    }),
                }
                : undefined,
            errorToast: (err) => {
                const resolvedError = this.container.errorHandler?.getResolvedError?.(err);
                return {
                    biName: 'cairo-set-default-view-error',
                    message: resolvedError?.message ??
                        this.collection.translate('cairo.manageView.setAsDefault.error.toast', {
                            viewName: newView.name,
                        }),
                    ...(resolvedError?.action ? { action: resolvedError?.action } : {}),
                    type: 'ERROR',
                };
            },
        });
        immediateAction();
        return newView;
    }
    get isReady() {
        const { isError, isFetching, isSuccess } = this.collection.result.status;
        return !isError && !isFetching && isSuccess;
    }
    setDefaultViewId(defaultViewId) {
        this.defaultViewId = defaultViewId;
    }
    unselectView() {
        this.currentView = null;
    }
    selectView({ id, needFiltersReset, }) {
        const view = this.getView(id);
        this.currentView = view ?? null;
        this.events.emit('selectAndRefresh', needFiltersReset);
    }
    get reachedViewsLimit() {
        return (this.getViews().filter(({ isPreset }) => !isPreset).length >=
            MAX_SAVED_VIEWS);
    }
    get isCurrentViewSelected() {
        return !!this.currentView?.id;
    }
    hasViewName(viewName) {
        return this.getViews()
            .map(({ name }) => name)
            .includes(viewName);
    }
}
//# sourceMappingURL=ViewsState.js.map