import { createExportAsyncJob, getExportAsyncJob, } from '@wix/bex-utils/@wix/ambassador-fedinfra-exportservice-v1-export-async-job/http';
import { Status as JobStatus, } from '@wix/bex-utils/@wix/ambassador-fedinfra-exportservice-v1-export-async-job/types';
import { action, computed, makeObservable, observable, runInAction, } from 'mobx';
import { queryStatus, } from '../../model';
import { WixPatternsError, } from '../../services';
import mapValues from 'lodash/mapValues';
import { EventEmitter } from 'events';
import { ExportStateBIReporter } from './ExportStateBIReporter';
import { getJobItemsAbsoluteCounter } from '../getJobItemsAbsoluteCounter';
import { getExportAsyncJobErrors } from './getExportAsyncJobErrors';
import { createExportAsyncJobErrors } from './createExportAsyncJobErrors';
import { commonExportAsyncJobErrors } from './commonExportAsyncJobErrors';
function defaultPlatformizedQuery() {
    return {
        filter: {},
        sort: [],
    };
}
export class ExportState {
    constructor(props) {
        this.exportStatus = {
            ...queryStatus.idle,
        };
        this.config = {
            pollingDelay: 2000,
            stateJob: {
                maxPolling: 14,
                maxRetries: 1,
            },
            queryJobRetries: {
                delay: 5000,
                max: 4,
            },
        };
        this.exportStartTime = 0;
        this.totalForExport = null;
        this.selectedExportOption = 'all';
        this.iterationsCountPoll = {
            iterationsCount: 0,
            pollCount: 0,
            retries: 0,
        };
        this.queryJobRetries = {
            count: 0,
        };
        this.events = new EventEmitter();
        this.counter = 0;
        this.absoluteCounter = 0;
        this.retries = 0;
        this.currentStep = 'config';
        this.totalForExportGettersMap = {
            selected: action(() => {
                const { collection } = this;
                const { bulkSelect: { selectedCountOrTotal }, } = collection;
                return selectedCountOrTotal;
            }),
            filtered: action(() => {
                const { collection } = this;
                const { result: { total, supportTotal }, } = collection;
                if (!supportTotal) {
                    return null;
                }
                return total;
            }),
            all: action(async () => {
                const { collection } = this;
                const allItemsQuery = {
                    ...collection.query.asComputed,
                    filtersKey: mapValues(collection.query.customFilters, (filter) => filter != null ? null : undefined),
                    rawFilters: {},
                    filters: {},
                };
                const queryKey = collection._queryKey(allItemsQuery);
                const allItemsQueryResult = await collection.queryClient.fetchQuery({
                    queryFn: () => collection._queryFn(allItemsQuery),
                    queryKey,
                });
                return allItemsQueryResult.total;
            }),
        };
        this.beforeunload = (e) => {
            e.preventDefault();
            e.returnValue = '';
        };
        this.customTotal = props.customTotal;
        this.differentQueryEndpointThanCollection =
            props.differentQueryEndpointThanCollection;
        this.translate = props.translate;
        this.createExportAsyncJobRequest = props.createExportAsyncJobRequest;
        this.saveAs = props.saveAs;
        this.window = props.window;
        this.httpClient = props.httpClient;
        this.errorHandler = props.errorHandler;
        this.onlineState = props.onlineState;
        this.exportModalState = props.exportModalState;
        this.collectionPageState = props.collectionPageState;
        this.showToast = props.showToast;
        this.internalMonitor = props.internalMonitor;
        this.collection = props.collection;
        this.reportBi = props.reportBi;
        this.disabledRadios = [];
        this.transformPlatformizedQuery = props.transformPlatformizedQuery;
        this.bi = new ExportStateBIReporter(this);
        makeObservable(this, {
            exportStatus: observable.ref,
            iterationsCountPoll: observable.ref,
            isExporting: computed,
            totalForExport: observable.ref,
            selectedExportOption: observable.ref,
            disabledRadios: observable.ref,
            counter: observable.ref,
            absoluteCounter: observable.ref,
            onExportToClick: action.bound,
            onExportCloseAttemptClick: action.bound,
            onCancelExportCloseButtonClick: action.bound,
            onCancelExportGoBackButtonClick: action.bound,
            onExportSecondaryActionAttempt: action.bound,
            onRetryExportToClick: action.bound,
            onFailedClose: action.bound,
            onFailedCancel: action.bound,
            onCancelClick: action.bound,
            onCloseClick: action.bound,
            init: action.bound,
            setTotalForExport: action,
            onExportTypeChange: action,
            hasFilters: computed,
            totalForExportMap: computed,
            hasSelectedRows: computed,
            isAllSelected: computed,
            currentStep: observable.ref,
            _onJobSuccess: action,
            _checkIfStaleJob: action,
            _throwJobError: action,
            _updateJobProgress: action,
        });
    }
    get isModalOpen() {
        return this.exportModalState.isOpen;
    }
    get isExporting() {
        const { exportStatus } = this;
        return exportStatus.isLoading || exportStatus.isSuccess;
    }
    _cancelJob() {
        // const { events } = this;
        // events.once('afterJobCreated', ({ job }) => {
        //   const { httpClient } = this;
        //   httpClient.request(
        //     cancelExportAsyncJob({
        //       jobId: job.id as string,
        //     }),
        //   );
        // });
    }
    _reset() {
        this.exportStatus = {
            ...queryStatus.idle,
        };
        this.currentStep = 'config';
        this.counter = 0;
        this.absoluteCounter = 0;
        this.totalForExport = null;
    }
    onExportTypeChange(selectedExportOption) {
        this.bi.onExportTypeChange({
            prev: this.selectedExportOption,
            next: selectedExportOption,
        });
        this.selectedExportOption = selectedExportOption;
    }
    init() {
        const { onlineState } = this;
        this.maybeToast?.remove();
        if (this.hasSelectedRows) {
            this.disabledRadios = this.hasFilters ? [] : ['filtered'];
            this.selectedExportOption = 'selected';
        }
        else if (this.hasFilters) {
            this.disabledRadios = ['selected'];
            this.selectedExportOption = 'filtered';
        }
        else {
            this.disabledRadios = ['selected', 'filtered'];
            this.selectedExportOption = 'all';
        }
        this.bi.clickOpenModal();
        const onOnlineChanged = action((isOnline) => {
            if (isOnline && this.currentStep === 'error') {
                this.currentStep = 'progress';
                this.events.emit('online');
            }
        });
        onlineState.events.on('change', onOnlineChanged);
        return () => {
            window.removeEventListener('beforeunload', this.beforeunload);
            this.events.removeAllListeners('undoCancel');
            this.events.removeAllListeners('online');
            onlineState.events.off('change', onOnlineChanged);
        };
    }
    get hasFilters() {
        return this.collection.query.hasActiveFilters;
    }
    get hasSelectedRows() {
        return (this.collection.bulkSelect.selectedIds.length !== undefined &&
            this.collection.bulkSelect.selectedIds.length > 0);
    }
    get isAllSelected() {
        return this.collection.bulkSelect.allSelected;
    }
    get totalForExportMap() {
        return {
            selected: this.totalForExportGettersMap.selected(),
            filtered: this.totalForExportGettersMap.filtered(),
        };
    }
    async setTotalForExport(params) {
        const { selectedExportOption, collection } = this;
        const { result: { supportTotal }, } = collection;
        if (this.differentQueryEndpointThanCollection === true) {
            return;
        }
        const collectionTotalPromise = (() => {
            if (selectedExportOption === 'selected') {
                return this.totalForExportGettersMap.selected();
            }
            if (selectedExportOption === 'filtered' && supportTotal) {
                return this.totalForExportGettersMap.filtered();
            }
            return this.totalForExportGettersMap.all();
        })();
        if (this.customTotal) {
            const totalForExport = await this.customTotal({
                ...params,
                collectionTotalPromise,
            });
            runInAction(() => {
                this.totalForExport = totalForExport;
            });
            return;
        }
        const totalForExport = await collectionTotalPromise;
        runInAction(() => {
            this.totalForExport = totalForExport;
        });
    }
    onCancelClick() {
        const { bi, exportModalState } = this;
        bi.onCancelClick();
        exportModalState.close();
    }
    onCancelExportVerifyButtonClick() {
        const { bi, exportModalState } = this;
        bi.onCancelExportButtonClick();
        exportModalState.close();
    }
    onCancelExportGoBackButtonClick() {
        this.currentStep = 'progress';
        const { bi, events } = this;
        bi.onCancelExportGoBackButtonClick();
        events.emit('undoCancel');
    }
    onCancelExportCloseButtonClick() {
        this.currentStep = 'progress';
        const { bi, events } = this;
        bi.onCancelExportCloseButtonClick();
        events.emit('undoCancel');
    }
    onExportCloseAttemptClick() {
        this.currentStep = 'cancel';
        const { bi } = this;
        bi.onExportCloseAttemptClick();
    }
    onExportSecondaryActionAttempt() {
        this.currentStep = 'cancel';
        const { bi } = this;
        bi.onExportCancelAttemptClick();
    }
    onCloseClick() {
        const { bi, exportModalState } = this;
        bi.onCloseClick();
        exportModalState.close();
    }
    async onExportToClick() {
        const { internalMonitor, bi } = this;
        this.retries += 1;
        this.iterationsCountPoll = {
            iterationsCount: 0,
            retries: 0,
            pollCount: 0,
        };
        this.queryJobRetries = {
            count: 0,
        };
        this.exportStartTime = performance.now();
        internalMonitor.interactionStarted('export-to');
        bi.onExportToClick();
        const reportExportEnd = bi.onExportStart();
        this._exportTo({
            onExportEnd: action((params) => {
                if (params.result !== 'fail') {
                    internalMonitor.interactionEnded('export-to');
                }
                reportExportEnd(params);
            }),
        });
    }
    async onRetryExportToClick() {
        const { bi } = this;
        bi.onRetryExportToClick();
        await this.onExportToClick();
    }
    async onFailedClose() {
        const { bi, exportModalState } = this;
        bi.onFailedClose();
        exportModalState.close();
    }
    async onFailedCancel() {
        const { bi, exportModalState } = this;
        bi.onFailedCancel();
        exportModalState.close();
    }
    async _onJobSuccess(job, params) {
        const { events, exportModalState, translate: t } = this;
        const { onExportEnd, query, saveAs } = params;
        if (job.data?.signedUrl == null) {
            return;
        }
        if (this.currentStep === 'cancel') {
            events.once('undoCancel', async () => {
                await new Promise((resolve) => setTimeout(resolve, 1000));
                return this._onJobSuccess(job, { onExportEnd, query, saveAs });
            });
            return;
        }
        runInAction(() => {
            this.exportStatus = {
                ...queryStatus.success,
                data: job,
            };
            this.counter = 100;
            this._updateAbsoluteCounter(job, params);
        });
        window.removeEventListener('beforeunload', this.beforeunload);
        await new Promise((res) => setTimeout(res, 1000)); // wait bit before starting the download for the progress indication animation completes
        const signedUrl = job.data.signedUrl;
        onExportEnd({ job, query, result: 'success', saveAs });
        const biState = this.bi.beforeToast();
        const maybeToast = this.showToast?.({
            message: t('cairo.export.successMessage'),
            biName: 'cairo-export-success-toast',
            timeout: 'NONE',
            type: 'SUCCESS',
            onCloseClick: () => {
                this.bi.onToastDismissed({
                    ...biState,
                    by: 'manual',
                });
                this.maybeToast = undefined;
            },
            action: {
                text: t('cairo.export.downloadManually'),
                uiType: 'LINK',
                navigationTarget: {
                    url: signedUrl,
                    openInNewTab: true,
                },
                onClick: () => {
                    this.bi.onToastActionClick(biState);
                    this.maybeToast = undefined;
                },
            },
        });
        this.maybeToast = maybeToast;
        this.window.download(signedUrl, saveAs);
        if (maybeToast) {
            new Promise((resolve) => setTimeout(resolve, 6000)).then(() => {
                if (maybeToast === this.maybeToast) {
                    maybeToast.remove();
                    this.bi.onToastDismissed({
                        ...biState,
                        by: 'automatic',
                    });
                }
            });
        }
        exportModalState.close();
    }
    _throwJobError(job, { onExportEnd, query }) {
        if (job.data?.error?.applicationError?.code === 'job_cancelled_by_client') {
            onExportEnd({ job, query, result: 'stopped by user' });
            return;
        }
        onExportEnd({ job, query, result: 'fail' });
        throw Object.assign(new Error(`ExportAsyncJob.${job.data?.error?.applicationError?.code}`), {
            response: {
                data: job.data?.error,
            },
            job,
            query,
        });
    }
    _checkIfStaleJob(job, { onExportEnd, query }) {
        const { internalMonitor } = this;
        if (job.status === JobStatus.FINISHED && !job.data?.signedUrl) {
            internalMonitor.interactionStarted('export-to-finished-without-download-url');
            internalMonitor.interactionEnded('export-to-finished-without-download-url');
        }
        if (job.data?.iterationsCount === this.iterationsCountPoll.iterationsCount) {
            this.iterationsCountPoll = {
                ...this.iterationsCountPoll,
                pollCount: this.iterationsCountPoll.pollCount + 1,
            };
            if (job.data?.completionStep !== 'upload' &&
                this.iterationsCountPoll.pollCount > this.config.stateJob.maxPolling) {
                if (this.iterationsCountPoll.retries < this.config.stateJob.maxRetries) {
                    internalMonitor.interactionStarted('export-to-max-polling-stale-job');
                    internalMonitor.interactionEnded('export-to-max-polling-stale-job');
                    runInAction(() => {
                        this.iterationsCountPoll = {
                            iterationsCount: 0,
                            pollCount: 0,
                            retries: this.iterationsCountPoll.retries + 1,
                        };
                    });
                    window.removeEventListener('beforeunload', this.beforeunload);
                    this._exportTo({ onExportEnd });
                    return 'restarted';
                }
                onExportEnd({ job, query, result: 'fail' });
                throw Object.assign(new Error('ExportAsyncJob.max_polling_stale_job'), {
                    response: {
                        data: {
                            applicationError: {
                                code: 'max_polling_stale_job',
                            },
                        },
                    },
                });
            }
        }
        else {
            this.iterationsCountPoll = {
                ...this.iterationsCountPoll,
                iterationsCount: job.data?.iterationsCount,
                pollCount: 1,
            };
        }
        return;
    }
    _updateAbsoluteCounter(job, { query }) {
        if (job?.data == null) {
            return;
        }
        const { totalForExport } = this;
        const newAbsoluteCounter = getJobItemsAbsoluteCounter(job, {
            query,
            totalForExport,
        });
        this.absoluteCounter =
            totalForExport != null &&
                newAbsoluteCounter >= totalForExport &&
                !this.exportStatus.isSuccess
                ? this.absoluteCounter +
                    Math.floor(Math.random() * (newAbsoluteCounter - this.absoluteCounter))
                : newAbsoluteCounter;
    }
    _updateJobProgress(job, params) {
        this._updateAbsoluteCounter(job, params);
        const { totalForExport } = this;
        if (totalForExport) {
            if (this.absoluteCounter) {
                const newCounter = Math.round((this.absoluteCounter / totalForExport) * 100);
                this.counter =
                    newCounter === 100
                        ? this.counter + Math.round(Math.random() * (99 - this.counter))
                        : newCounter;
            }
        }
        else if (this.counter < 90 && this.absoluteCounter > 0) {
            this.counter += 10;
        }
    }
    async _poll({ jobId, saveAs, query, onExportEnd, }) {
        const { httpClient, events, onlineState, translate, errorHandler } = this;
        let current = null;
        try {
            // eslint-disable-next-line no-constant-condition
            while (true) {
                if (!this.isModalOpen) {
                    const currentJob = await this.exportStatus.promise;
                    if (currentJob) {
                        onExportEnd({
                            job: currentJob,
                            query,
                            result: 'stopped by user',
                        });
                    }
                    break;
                }
                const getExportAsyncJobResponse = await errorHandler
                    .withErrorHandler(() => httpClient.request(getExportAsyncJob({ jobId })), getExportAsyncJobErrors({
                    translate,
                    retryFn: this.onRetryExportToClick,
                }), {
                    statusCodesOverrides: commonExportAsyncJobErrors({
                        translate,
                    }),
                })
                    .catch((error) => {
                    errorHandler.getResolvedError?.(error);
                    return { error };
                });
                if (getExportAsyncJobResponse &&
                    !('data' in getExportAsyncJobResponse)) {
                    const error = getExportAsyncJobResponse.error;
                    if (!onlineState.isOnline) {
                        events.once('online', () => {
                            this._poll({
                                jobId,
                                onExportEnd,
                                query,
                                saveAs,
                            });
                        });
                        throw new WixPatternsError({
                            errorCode: 'GetExportAsyncJob.offline',
                            originalException: error,
                            extraData: {
                                response: {
                                    data: {
                                        applicationError: {
                                            code: 'offline',
                                        },
                                    },
                                },
                            },
                        });
                    }
                    if (this.queryJobRetries.count > this.config.queryJobRetries.max) {
                        onExportEnd({ job: current, query, result: 'fail' });
                        throw new WixPatternsError({
                            originalException: error,
                            errorCode: 'GetExportAsyncJob.failed',
                        });
                    }
                    this.queryJobRetries = {
                        ...this.queryJobRetries,
                        count: this.queryJobRetries.count + 1,
                    };
                    setTimeout(() => {
                        this._poll({
                            jobId,
                            onExportEnd,
                            query,
                            saveAs,
                        });
                    }, this.config.queryJobRetries.delay);
                    break;
                }
                const job = getExportAsyncJobResponse?.data.job;
                if (job?.data == null) {
                    throw new WixPatternsError({
                        errorCode: 'GetExportAsyncJob.invalid',
                    });
                }
                current = job;
                this.queryJobRetries = {
                    count: 0,
                };
                runInAction(() => {
                    this.exportStatus = {
                        ...queryStatus.loading,
                        promise: Promise.resolve(job),
                    };
                });
                const isModalOpen = runInAction(() => this.isModalOpen);
                if (!isModalOpen) {
                    break;
                }
                if (job.data.signedUrl) {
                    await this._onJobSuccess(job, {
                        onExportEnd,
                        saveAs,
                        query,
                    });
                    break;
                }
                if (job.status === JobStatus.FAILED ||
                    job.data.error?.applicationError != null) {
                    if (job.data.error?.applicationError?.code ===
                        'media_generate_signed_url') {
                        // await this._retryGenerateUrl(job);
                        // continue;
                    }
                    this._throwJobError(job, {
                        onExportEnd,
                        query,
                        saveAs,
                    });
                    break;
                }
                const checkIfStaleJobResult = this._checkIfStaleJob(job, {
                    onExportEnd,
                    saveAs,
                    query,
                });
                if (checkIfStaleJobResult === 'restarted') {
                    return;
                }
                await runInAction(async () => {
                    if (!this.totalForExport &&
                        (job.data?.iterationsCount == null || job.data.iterationsCount <= 1)) {
                        this.totalForExport = job.data?.pagingMetadata?.total;
                    }
                });
                this._updateJobProgress(job, { onExportEnd, query, saveAs });
                await new Promise((res) => setTimeout(res, this.config.pollingDelay));
            }
        }
        catch (error) {
            console.error(error);
            this.internalMonitor.reportError({ error });
            runInAction(() => {
                this.currentStep = 'error';
                this.exportStatus = {
                    ...queryStatus.error,
                    error,
                };
            });
        }
    }
    async _exportTo({ onExportEnd, }) {
        const { createExportAsyncJobRequest, httpClient, errorHandler, transformPlatformizedQuery, selectedExportOption, events, onlineState, translate, } = this;
        this._reset();
        window.addEventListener('beforeunload', this.beforeunload);
        const platformizedQuery = defaultPlatformizedQuery();
        const { bulkSelect: { selectedIds, allSelected: selectAll, uncheckedInCurrentCollection: uncheckedValues, }, query: { asComputed: computedQuery }, } = this.collection;
        const wixPatternsQuery = selectedExportOption !== 'all'
            ? computedQuery
            : {
                ...computedQuery,
                filtersKey: {},
                rawFilters: {},
                filters: {},
            };
        const exportPublicState = {
            platformizedQuery,
            wixPatternsQuery,
            selectedItems: selectedExportOption === 'selected' ? selectedIds : undefined,
            isSelectAll: selectAll,
            uncheckedValues,
        };
        const query = (await transformPlatformizedQuery?.(exportPublicState)) ??
            platformizedQuery;
        const totalForExportPromise = this.setTotalForExport({
            ...exportPublicState,
            platformizedQuery: query,
        });
        const saveAs = this.saveAs?.({
            platformizedQuery,
            wixPatternsQuery,
            selectedItems: selectedExportOption === 'selected' ? selectedIds : undefined,
            isSelectAll: selectAll,
        }) ?? this.collectionPageState.pageTitle;
        runInAction(() => {
            this.currentStep = 'progress';
        });
        const _createExport = async () => {
            try {
                const { data: createExportAsyncJobResponse } = await errorHandler
                    .withErrorHandler(() => httpClient.request(createExportAsyncJob({
                    fields: [],
                    methodMetadata: {},
                    ...createExportAsyncJobRequest,
                    saveAs,
                    query,
                })), createExportAsyncJobErrors({
                    translate,
                }), {
                    statusCodesOverrides: commonExportAsyncJobErrors({
                        translate,
                    }),
                })
                    .catch((err) => {
                    if (!onlineState.isOnline) {
                        events.once('online', () => {
                            this._exportTo({
                                onExportEnd,
                            });
                        });
                        throw Object.assign(new Error('CreateExportAsyncJob.offline'), {
                            response: {
                                data: {
                                    applicationError: {
                                        code: 'offline',
                                    },
                                },
                            },
                        });
                    }
                    onExportEnd({ job: null, query, result: 'fail' });
                    throw err;
                });
                return createExportAsyncJobResponse;
            }
            catch (error) {
                runInAction(() => {
                    this.currentStep = 'error';
                    this.exportStatus = {
                        ...queryStatus.error,
                        error,
                    };
                });
                throw error;
            }
        };
        try {
            const createExportAsyncJobResponse = await _createExport();
            if (!createExportAsyncJobResponse.job?.id) {
                throw new Error(`Invalid response from createExportAsyncJob "jobId=${createExportAsyncJobResponse.job?.id}"`);
            }
            await totalForExportPromise;
            runInAction(() => {
                this.exportStatus = {
                    ...queryStatus.loading,
                    promise: Promise.resolve(createExportAsyncJobResponse.job),
                };
            });
            this.events.emit('afterJobCreated', {
                job: createExportAsyncJobResponse.job,
            });
            runInAction(() => {
                this.counter = 1;
            });
            await this._poll({
                onExportEnd,
                saveAs,
                query,
                jobId: createExportAsyncJobResponse.job.id,
            });
        }
        catch (error) {
            console.error(error);
            this.internalMonitor.reportError({ error });
        }
        window.removeEventListener('beforeunload', this.beforeunload);
    }
}
//# sourceMappingURL=ExportState.js.map