import { action, computed, makeObservable, observable, runInAction, } from 'mobx';
import { queryStatus, } from '../../model';
import { ConditionalModalState } from '../ConditionalModalState';
import { parseCsv } from './parseCsv';
import set from 'lodash/set';
const DEFAULT_BATCH_SIZE = 100;
function normalize(value) {
    return value.trim().toLowerCase();
}
function toMappedRows(rows, headers, mappingByHeader) {
    return rows.map((row) => {
        const mapped = {};
        headers.forEach((header, index) => {
            const mappedField = mappingByHeader[header];
            if (mappedField == null) {
                return;
            }
            set(mapped, mappedField, row[index] ?? '');
        });
        return mapped;
    });
}
export class ImportState {
    constructor(props) {
        this.discardChangesModal = new ConditionalModalState();
        this.currentStep = 'upload';
        this.importStatus = {
            ...queryStatus.idle,
        };
        this.parseError = null;
        this.fileName = '';
        this.csvHeaders = [];
        this.csvRows = [];
        this.mappingByHeader = {};
        this.processedCount = 0;
        this.totalCount = 0;
        this.result = {
            succeeded: 0,
            failed: 0,
            errors: [],
        };
        this.isBackingUp = false;
        this.isRestoring = false;
        this.restoreFn = null;
        this._cancelled = false;
        this._abortController = null;
        this.fields = (props.fields ?? []);
        this.bulkUploadItems = props.bulkUploadItems;
        this.backup = props.backup;
        this.collection = props.collection;
        this.importModalState = props.importModalState;
        this.translate = props.translate;
        this.errorHandler = props.errorHandler;
        this.showToast = props.showToast;
        this.batchSize = props.batchSize ?? DEFAULT_BATCH_SIZE;
        makeObservable(this, {
            currentStep: observable.ref,
            importStatus: observable.ref,
            parseError: observable.ref,
            fileName: observable.ref,
            csvHeaders: observable.ref,
            csvRows: observable.ref,
            mappingByHeader: observable.ref,
            processedCount: observable.ref,
            totalCount: observable.ref,
            result: observable.ref,
            isBackingUp: observable.ref,
            isRestoring: observable.ref,
            hasParsedRows: computed,
            hasMappings: computed,
            selectedFieldIds: computed,
            mappedRows: computed,
            onUploadContinue: action.bound,
            goToUpload: action.bound,
            setMapping: action.bound,
            close: action.bound,
            onFileSelected: action.bound,
            startImport: action.bound,
            reset: action.bound,
        });
    }
    init() {
        return () => undefined;
    }
    get hasParsedRows() {
        return this.csvHeaders.length > 0 && this.csvRows.length > 0;
    }
    get hasMappings() {
        return Object.values(this.mappingByHeader).some((value) => value != null);
    }
    get selectedFieldIds() {
        return new Set(Object.values(this.mappingByHeader).filter((v) => v != null));
    }
    get mappedRows() {
        return toMappedRows(this.csvRows, this.csvHeaders, this.mappingByHeader);
    }
    reset() {
        this._abortController?.abort();
        this._abortController = null;
        this.currentStep = 'upload';
        this.importStatus = {
            ...queryStatus.idle,
        };
        this.parseError = null;
        this.fileName = '';
        this.csvHeaders = [];
        this.csvRows = [];
        this.mappingByHeader = {};
        this.processedCount = 0;
        this.totalCount = 0;
        this.result = {
            succeeded: 0,
            failed: 0,
            errors: [],
        };
        this.isBackingUp = false;
        this.isRestoring = false;
        this.restoreFn = null;
        this._cancelled = false;
    }
    async close() {
        if (this.currentStep === 'progress') {
            this._cancelled = true;
            this._abortController?.abort();
            if (!this.isBackingUp) {
                await this._restoreBackup();
            }
            this.collection.clearResultAndMoveToStart({
                force: true,
            });
        }
        this.importModalState.close();
    }
    async _restoreBackup() {
        const restoreFn = this.restoreFn;
        if (!restoreFn) {
            return;
        }
        const restoreController = new AbortController();
        this._abortController = restoreController;
        this.isRestoring = true;
        try {
            await restoreFn(restoreController.signal);
        }
        catch (error) {
            console.error('Restore failed', error);
        }
        runInAction(() => {
            this.isRestoring = false;
            this.restoreFn = null;
        });
    }
    goToUpload() {
        this.currentStep = 'upload';
    }
    async discardAndGoBack() {
        this.discardChangesModal.open('goBack');
        const { result } = await this.discardChangesModal.modal.promise();
        if (result === 'confirmed') {
            this.goToUpload();
        }
    }
    async discardAndClose() {
        this.discardChangesModal.open('close');
        const { result } = await this.discardChangesModal.modal.promise();
        if (result === 'confirmed') {
            this.close();
        }
    }
    onUploadContinue() {
        if (this.hasParsedRows) {
            this.currentStep = 'configuration';
        }
    }
    getDefaultMapping(headers) {
        const mapping = {};
        const fields = this.fields.map((field) => ({
            id: field.id ?? '',
            header: field.header ?? '',
        }));
        headers.forEach((header) => {
            const normalizedHeader = normalize(header);
            const matched = fields.find((field) => normalize(field.id) === normalizedHeader ||
                normalize(field.header) === normalizedHeader);
            mapping[header] = matched?.id || null;
        });
        return mapping;
    }
    applyParsedCsv(fileName, parsedCsv) {
        this.fileName = fileName;
        this.parseError = null;
        this.csvHeaders = parsedCsv.headers;
        this.csvRows = parsedCsv.rows;
        this.mappingByHeader = this.getDefaultMapping(parsedCsv.headers);
        this.currentStep = 'configuration';
    }
    async onFileSelected(file) {
        if (file == null) {
            return;
        }
        try {
            const content = await file.text();
            const parsedCsv = parseCsv(content);
            if ('message' in parsedCsv) {
                runInAction(() => {
                    this.parseError = parsedCsv.message;
                    this.fileName = file.name;
                    this.csvHeaders = [];
                    this.csvRows = [];
                    this.mappingByHeader = {};
                    this.currentStep = 'upload';
                });
                return;
            }
            runInAction(() => {
                this.applyParsedCsv(file.name, parsedCsv);
            });
        }
        catch (e) {
            console.error(e);
            runInAction(() => {
                this.parseError = this.translate('cairo.import.parseFailed');
                this.currentStep = 'upload';
            });
        }
    }
    setMapping(header, fieldId) {
        this.mappingByHeader = {
            ...this.mappingByHeader,
            [header]: fieldId,
        };
    }
    async _uploadBatches(batches) {
        let processedRows = 0;
        for (const batch of batches) {
            if (this._cancelled) {
                break;
            }
            try {
                const batchResult = await this.bulkUploadItems(batch);
                this.result = {
                    succeeded: this.result.succeeded + batchResult.succeeded,
                    failed: this.result.failed + batchResult.failed,
                    errors: [...this.result.errors, ...batchResult.errors],
                };
            }
            catch (error) {
                this.result = {
                    succeeded: this.result.succeeded,
                    failed: this.result.failed + batch.length,
                    errors: this.result.errors,
                };
                break;
            }
            processedRows += batch.length;
            runInAction(() => {
                this.processedCount = Math.min(processedRows, this.totalCount);
            });
        }
        return this.result;
    }
    async _runBackup() {
        const backup = this.backup;
        if (!backup) {
            return false;
        }
        try {
            const result = await backup(this._abortController.signal);
            if (this._cancelled) {
                return true;
            }
            if (result?.restore) {
                this.restoreFn = result.restore;
            }
        }
        catch (error) {
            if (this._cancelled) {
                return true;
            }
            runInAction(() => {
                this.isBackingUp = false;
                this.importStatus = {
                    ...queryStatus.error,
                    error,
                };
            });
            return true;
        }
        runInAction(() => {
            this.isBackingUp = false;
        });
        return false;
    }
    _createRowsBatches(rows) {
        return rows.reduce((acc, row, index) => {
            if (index % this.batchSize === 0) {
                acc.push([]);
            }
            acc[acc.length - 1].push(row);
            return acc;
        }, []);
    }
    async startImport() {
        if (!this.hasMappings) {
            return;
        }
        this._cancelled = false;
        this._abortController = new AbortController();
        const rows = this.mappedRows;
        const totalCount = rows.length;
        const batches = this._createRowsBatches(rows);
        runInAction(() => {
            this.currentStep = 'progress';
            this.totalCount = totalCount;
            this.processedCount = 0;
            this.isBackingUp = !!this.backup;
        });
        const backupFailed = await this._runBackup();
        if (backupFailed) {
            return;
        }
        const promise = this._uploadBatches(batches);
        this.importStatus = {
            ...queryStatus.loading,
            promise,
        };
        try {
            await promise;
            runInAction(() => {
                if (this._cancelled) {
                    return;
                }
                this.importStatus = {
                    ...queryStatus.success,
                    data: this.result,
                };
                if (this.result.failed === 0) {
                    this.importModalState.close();
                    this.collection.clearResultAndMoveToStart({ force: true });
                }
            });
        }
        catch (error) {
            runInAction(() => {
                this.importStatus = {
                    ...queryStatus.error,
                    error,
                };
            });
        }
    }
}
//# sourceMappingURL=ImportState.js.map