import { UmbTemporaryFileRepository } from './temporary-file.repository.js';
import { UmbTemporaryFileConfigRepository } from './config/index.js';
import { TemporaryFileStatus, } from './types.js';
import { observeMultiple, UmbArrayState } from '../../../libs/observable-api/index.js';
import { UmbControllerBase } from '../../../libs/class-api/index.js';
import { UmbLocalizationController } from '../../../libs/localization-api/index.js';
import { UMB_NOTIFICATION_CONTEXT } from '../notification/index.js';
import { formatBytes, getFileExtension } from '../utils/index.js';
import { UmbApiError, UmbCancelError } from '../resources/index.js';
export class UmbTemporaryFileManager extends UmbControllerBase {
    #temporaryFileRepository;
    #temporaryFileConfigRepository;
    #localization;
    #notificationContext;
    #queue;
    constructor(host) {
        super(host);
        this.#temporaryFileRepository = new UmbTemporaryFileRepository(this._host);
        this.#temporaryFileConfigRepository = new UmbTemporaryFileConfigRepository(this._host);
        this.#localization = new UmbLocalizationController(this._host);
        this.#queue = new UmbArrayState([], (item) => item.temporaryUnique);
        this.queue = this.#queue.asObservable();
        this.consumeContext(UMB_NOTIFICATION_CONTEXT, (instance) => {
            this.#notificationContext = instance;
        });
    }
    /**
     * Gets the temporary file configuration.
     * @returns {Promise<UmbTemporaryFileConfigRepository>} The temporary file configuration.
     */
    async getConfiguration() {
        await this.#temporaryFileConfigRepository.initialized;
        return this.#temporaryFileConfigRepository;
    }
    async uploadOne(uploadableItem, options) {
        this.#queue.setValue([]);
        const item = {
            ...uploadableItem,
            status: TemporaryFileStatus.WAITING,
        };
        this.#queue.appendOne(item);
        return (await this.#handleQueue({ ...options, chunkSize: 1 }))[0];
    }
    upload(queueItems, options) {
        this.#queue.setValue([]);
        const items = queueItems.map((item) => ({ ...item, status: TemporaryFileStatus.WAITING }));
        this.#queue.append(items);
        return this.#handleQueue({ ...options });
    }
    removeOne(unique) {
        this.#queue.removeOne(unique);
    }
    remove(uniques) {
        this.#queue.remove(uniques);
    }
    removeAll() {
        this.#queue.setValue([]);
    }
    async #handleQueue(options) {
        const filesCompleted = [];
        const queue = this.#queue.getValue();
        if (!queue.length)
            return filesCompleted;
        const handler = async (item) => {
            const completedUpload = await this.#handleUpload(item);
            filesCompleted.push(completedUpload);
            if (options?.callback)
                await options.callback(completedUpload);
        };
        const chunkSize = options?.chunkSize ?? 5;
        const chunks = Math.ceil(queue.length / chunkSize);
        for (let i = 0; i < chunks; i++) {
            const chunk = queue.slice(i * chunkSize, i * chunkSize + chunkSize);
            await Promise.all(chunk.map(handler));
        }
        return filesCompleted;
    }
    #notifyOnFileSizeLimitExceeded(maxFileSize, item) {
        this.#notificationContext?.peek('warning', {
            data: {
                headline: 'Upload',
                message: `
	${this.#localization.term('media_invalidFileSize')}: ${item.file.name} (${formatBytes(item.file.size)}).

	${this.#localization.term('media_maxFileSize')} ${maxFileSize > 0 ? formatBytes(maxFileSize) : 'N/A'}.
						`,
            },
        });
    }
    async #validateItem(item) {
        const config = await this.getConfiguration();
        let maxFileSize = await this.observe(config.part('maxFileSize')).asPromise();
        if (maxFileSize) {
            // Convert from kilobytes to bytes
            maxFileSize *= 1024;
            if (item.file.size > maxFileSize) {
                this.#notifyOnFileSizeLimitExceeded(maxFileSize, item);
                return false;
            }
        }
        const fileExtension = (getFileExtension(item.file.name) ?? '').toLowerCase();
        const [allowedExtensions, disallowedExtensions] = await this.observe(observeMultiple([
            this.#temporaryFileConfigRepository.part('allowedUploadedFileExtensions'),
            this.#temporaryFileConfigRepository.part('disallowedUploadedFilesExtensions'),
        ])).asPromise();
        const allowedLower = allowedExtensions?.map((x) => x.toLowerCase());
        const disallowedLower = disallowedExtensions?.map((x) => x.toLowerCase());
        if ((allowedLower?.length && !allowedLower.includes(fileExtension)) ||
            (disallowedLower?.length && disallowedLower.includes(fileExtension))) {
            this.#notificationContext?.peek('warning', {
                data: {
                    message: `${this.#localization.term('media_disallowedFileType')}: ${fileExtension}`,
                },
            });
            return false;
        }
        return true;
    }
    async #handleUpload(item) {
        if (!item.temporaryUnique)
            throw new Error(`Unique is missing for item ${item}`);
        const isValid = await this.#validateItem(item);
        if (!isValid) {
            this.#queue.updateOne(item.temporaryUnique, {
                ...item,
                status: TemporaryFileStatus.ERROR,
            });
            return { ...item, status: TemporaryFileStatus.ERROR };
        }
        const { error } = await this.#temporaryFileRepository.upload(item.temporaryUnique, item.file, (evt) => {
            // Update progress in percent if a callback is provided
            if (item.onProgress)
                item.onProgress((evt.loaded / evt.total) * 100);
        }, item.abortController?.signal ?? item.abortSignal);
        let status = TemporaryFileStatus.SUCCESS;
        if (error) {
            status = this.#handleError(error, item);
        }
        this.#queue.updateOne(item.temporaryUnique, { ...item, status });
        return { ...item, status };
    }
    #handleError(error, item) {
        if (error instanceof UmbCancelError) {
            // Ignore the error if the upload was cancelled
            return TemporaryFileStatus.CANCELLED;
        }
        if (UmbApiError.isUmbApiError(error)) {
            // Handle the error based on the status code
            if (error.status === 413) {
                // Special handling for when the request body is too large
                const maxFileSizeGuestimate = parseInt(/(\d+) bytes/.exec(error.problemDetails.title)?.[1] ?? '0', 10);
                this.#notifyOnFileSizeLimitExceeded(maxFileSizeGuestimate, item);
            }
            else {
                this.#notificationContext?.peek('danger', {
                    data: {
                        headline: this.#localization.term('errors_receivedErrorFromServer'),
                        message: error.problemDetails.title,
                    },
                });
            }
        }
        else {
            this.#notificationContext?.peek('danger', {
                data: {
                    headline: this.#localization.term('errors_receivedErrorFromServer'),
                    message: error.message,
                },
            });
        }
        return TemporaryFileStatus.ERROR;
    }
}
