import { TagExposure, } from '@wix/bex-utils/@wix/ambassador-os-tags-v1-tag/types';
import { cairoTagsAssignTag } from '@wix/bex-utils/@wix/bi-logger-os-data/v2';
import { v4 as uuid } from 'uuid';
import { action, computed, makeObservable, observable } from 'mobx';
import { CollectionOptimisticActions } from '../CollectionOptimisticActions';
import { TaskState } from '../TaskState';
import { CollectionState } from '../CollectionState';
import { fqdnToString } from '../../util';
export class TagsAssignmentState {
    constructor({ tagsManagementState, entityId, origin, tagsBIReporter, initialTags, ...params }) {
        this.initTask = new TaskState();
        this.assignTag = async ({ tag, onSubmit, isFromSearch, optimisticActionParams, }) => {
            if (this.assignedTagIds.includes(tag.id)) {
                return;
            }
            const newIndex = this._getNewIndex();
            await this.optimisticActions.createOne({ ...tag, id: tag.id, index: newIndex }, {
                ...optimisticActionParams,
                submit: async (_) => {
                    await this._assignTag({
                        tagIds: this.assignedTagIds,
                        tag,
                        onSubmit,
                        isFromSearch,
                    });
                    return [{ ...tag, id: tag.id, index: newIndex }];
                },
            });
        };
        this.unassignTag = async ({ tag, onSubmit, isFromSearch = false, optimisticActionParams, }) => {
            const tagToUnassign = this.collection.result.items.find((t) => t.id === tag.id);
            await this.optimisticActions.deleteOne(tagToUnassign, {
                ...optimisticActionParams,
                submit: async () => {
                    this._invokeBiOnAssignUnassign({
                        actionName: 'Remove',
                        tag,
                        isFromSearch,
                    });
                    await onSubmit?.({
                        allTags: {
                            privateTags: {
                                tagIds: this.assignedTagIds.filter((id) => id !== tag.id),
                            },
                        },
                        addedTags: {
                            privateTags: {
                                tagIds: [],
                            },
                        },
                        removedTags: {
                            privateTags: {
                                tagIds: [tag.id],
                            },
                        },
                    });
                },
            });
        };
        this.createAndAssignTag = async ({ name, id = uuid(), filteredListSize, onSubmit, assignOptimisticActionParams, createOptimisticActionParams, }) => {
            const newIndex = this._getNewIndex();
            this.optimisticActions.createOne({ id, index: newIndex, name }, {
                ...assignOptimisticActionParams,
                submit: async (_) => {
                    const newTag = await this._createAndAssign({
                        name,
                        id,
                        filteredListSize,
                        onSubmit,
                        createOptimisticActionParams,
                    });
                    return [{ ...newTag, id: newTag.id, index: newIndex }];
                },
            });
        };
        this._assignTag = async ({ tagIds, tag, onSubmit, isFromSearch, }) => {
            this._invokeBiOnAssignUnassign({
                actionName: 'Assign',
                tag,
                isFromSearch,
            });
            await onSubmit?.({
                allTags: {
                    privateTags: {
                        tagIds: Array.from(new Set([...tagIds, tag.id])),
                    },
                },
                addedTags: { privateTags: { tagIds: [tag.id] } },
                removedTags: { privateTags: { tagIds: [] } },
            });
        };
        this._createAndAssign = async ({ name, id, filteredListSize, onSubmit, createOptimisticActionParams, }) => {
            const isFromSearch = true;
            let assignError;
            let newTag = this.tagsManagementState.items.find((tag) => tag.name === name);
            if (newTag) {
                await this._assignTag({
                    tagIds: this.assignedTagIds.filter((tagId) => tagId !== id),
                    tag: newTag,
                    onSubmit,
                    isFromSearch,
                });
                return newTag;
            }
            newTag = await this._createTag({
                name,
                id,
                filteredListSize,
                isFromSearch,
                createOptimisticActionParams,
                onCreateSuccess: async (newTag) => {
                    try {
                        await this._assignTag({
                            tagIds: this.assignedTagIds.filter((tagId) => tagId !== id),
                            tag: newTag,
                            onSubmit,
                            isFromSearch,
                        });
                    }
                    catch (error) {
                        assignError = error;
                    }
                },
            });
            // We want to throw assign exception in assign optimistic context so assign tryAgain (and not create) will be triggered
            if (assignError) {
                throw assignError;
            }
            return newTag;
        };
        this._createTag = async ({ name, id, filteredListSize, isFromSearch, onCreateSuccess, createOptimisticActionParams, }) => {
            return new Promise(async (resolve) => {
                await this.tagsManagementState.createTag({
                    name,
                    id,
                    filteredListSize,
                    origin: this.origin,
                    isFromSearch,
                    tagsBIReporter: this.tagsBIReporter,
                    optimisticActionParams: createOptimisticActionParams,
                    onSuccess: async (newTag) => {
                        await onCreateSuccess?.(newTag);
                        resolve(newTag);
                    },
                });
            });
        };
        this.tagsManagementState = tagsManagementState;
        this.origin = origin;
        this.tagsBIReporter = tagsBIReporter;
        this.fqdn = params.fqdn;
        this.initialTags = Array.from(new Set(initialTags?.privateTags?.tagIds || [])).map((id, index) => ({
            index,
            id,
        }));
        makeObservable(this, {
            initialTags: observable.ref,
            init: action,
            assignTag: action,
            createAndAssignTag: action,
            unassignTag: action,
            replaceAssigned: action,
            clear: action,
            assignedTags: computed,
            initialTagIds: computed,
            isDirty: computed,
        });
        this.collection = new CollectionState({
            ...tagsManagementState.container,
            events: {},
            queryName: [this.fqdnString, origin, entityId].join('/'),
            fetchData: async () => {
                if (!this._isFqdnValid() || !this.initialTags.length) {
                    return { items: [], total: 0 };
                }
                const fetchedTags = await this.tagsManagementState.tagsService.fetchTagsByIds({
                    fqdn: this.fqdnString,
                    tagIds: this.initialTagIds,
                    exposure: TagExposure.PRIVATE,
                }, {}, {
                    serverErrorOverride: () => ({
                        message: this.tagsManagementState.container.translate('cairo.tags.collectionPage.popover.loadTags.error.body'),
                        action: {
                            text: this.tagsManagementState.container.translate('cairo.tags.loadTags.error.CTA'),
                            onClick: () => {
                                void this.collection.clearResultAndMoveToStart({
                                    force: true,
                                });
                            },
                        },
                    }),
                });
                const tagsById = new Map(fetchedTags.map((tag) => [tag.id, tag]));
                const assignedTags = this.initialTags
                    .map(({ id, index }) => {
                    const tag = tagsById.get(id);
                    if (!tag) {
                        return undefined;
                    }
                    return { ...tag, index };
                })
                    .filter((tag) => !!tag);
                return {
                    items: assignedTags,
                    total: this.initialTags.length,
                };
            },
            itemKey: (tag) => tag.id,
            itemName: (tag) => tag.id,
            filters: {},
        });
        this.optimisticActions = new CollectionOptimisticActions({
            collection: this.collection,
            container: tagsManagementState.container,
            orderBy: () => [
                {
                    fieldName: ({ index }) => index,
                    order: 'asc',
                },
            ],
        });
    }
    init({ entityId }) {
        this.entityId = entityId ?? undefined;
        const { collection, optimisticActions } = this;
        const disposers = [collection.init(), optimisticActions.init()];
        this.initTask.runOnce(async () => {
            await collection.initTask.status.promise;
            this.tagsManagementState.init();
            await this.tagsManagementState.initTask.status.promise;
        });
        return () => {
            disposers.forEach((disposer) => disposer());
        };
    }
    get fqdnString() {
        return fqdnToString(this.fqdn);
    }
    get isLoading() {
        const { status } = this.tagsManagementState.initTask;
        return status.isIdle || status.isLoading || this.initTask.status.isLoading;
    }
    get isError() {
        return (this.tagsManagementState.initTask.status.isError ||
            this.initTask.status.isError);
    }
    retry() {
        this.initTask.runOnce();
    }
    get assignedTags() {
        return this.collection.result.items;
    }
    get assignedTagIds() {
        const { isIdle, isLoading } = this.collection.status;
        /**
         * During collection fetch, the returned items are 0, which impacts assignedTagIds
         * and sets isDirty to true during the rendering lifecycle, causing flakiness in the UI.
         */
        return isIdle || isLoading
            ? this.initialTagIds
            : this.collection.result.items.map((tag) => tag.id);
    }
    get initialTagIds() {
        return this.initialTags.map((tag) => tag.id);
    }
    clear() {
        this.collection.result.clear();
    }
    replaceAssigned(tagIds) {
        const unique = Array.from(new Set(tagIds));
        const current = this.initialTagIds;
        if (unique.length === current.length &&
            unique.every((id, i) => id === current[i])) {
            return;
        }
        this.initialTags = unique.map((id, index) => ({ id, index }));
        this.collection.clearResultAndMoveToStart({ force: true });
    }
    get isDirty() {
        if (this.initialTagIds.length !== this.assignedTagIds.length) {
            return true;
        }
        const unassigendTagIds = this.initialTagIds.filter((id) => !this.assignedTagIds.includes(id));
        const assignedTagIds = this.assignedTagIds.filter((id) => !this.initialTagIds.includes(id));
        return unassigendTagIds.length !== 0 || assignedTagIds.length !== 0;
    }
    _isFqdnValid() {
        return this.fqdn.resource !== undefined;
    }
    _invokeBiOnAssignUnassign({ actionName, tag, isFromSearch = false, }) {
        this.tagsBIReporter.reportBi(cairoTagsAssignTag({
            actionName,
            isFromSearch,
            itemId: this.entityId,
            numItems: 1,
            numTags: this.tagsManagementState.items.length,
            numTagsAssigned: this.assignedTags.length,
            tagName: tag.name,
            origin: this.origin,
        }));
    }
    _getNewIndex() {
        const max = Math.max(0, ...this.collection.result.items.map((tag) => tag.index));
        return max + 1;
    }
}
//# sourceMappingURL=TagsAssignmentState.js.map