/** @typedef { import('types/UploadchunkResponse.js').UploadchunkResponse} UploadchunkResponse */

export const CHUNK_SIZE = 8192 * 1024;

const PATCH_EXTENSIONS = new Set([
    'ips', 'bps', 'ups', 'aps', 'ppf', 'xdelta', "zip"
]);

/**
 * An uploaded file instance.
 * Create a new file data.
 *
 * @param {string} name Filename
 * @param {number} totalChunks Total number of chunks of the file.
 * @param rawFile The JS file element relation.
 */
export function FSFileData(name, totalChunks, rawFile ) {

    const extension = name.split('.').pop().toLowerCase();

    return {

        /**
         * Filename.
         * @type {string}
         */
        name,

        /**
         * Number of total chunks based on CHUNK_SIZE.
         * @type {number}
         */
        totalChunks,

        /**
         * The JS file element relation.
         */
        rawFile,

        /**
         * Upload progression value.
         * @type {number}
         */
        progressValue: 0,

        /**
         * Current chunk uploaded.
         * @type {number}
         */
        currentChunk: 0,

        /**
         * If the upload of the file is finished.
         * @type {boolean}
         */
        done: false,

        /**
         * If there is an error during file uploading.
         * @type {any|null}
         */
        error: null,

        /**
         * UUID v4 for the file.
         * @type {`${string}-${string}-${string}-${string}-${string}`}
         */
        uuid: crypto.randomUUID(),

        /**
         * Current file state
         */
        state: 'public',

        file_explorer: null,

        file_explorer_files: null,

        /**
         * For files already uploaded, download URL.
         */
        download_url: null,

        can_be_online_patched: PATCH_EXTENSIONS.has(extension),

        /**
         * If the online patcher is enabled
         */
        meta_online_patcher: false,

        /**
         * If this patch is a secondary patch.
         */
        meta_secondary_online_patcher: false,

        /**
         * If this patch can be played online.
         */
        meta_play_online: false,

        /**
         * Selected core for play online
         */
        meta_play_online_core: null,

        /**
         * If the threads are enabled for playing online.
         */
        meta_play_online_threads: null,

        /**
         * Look if this file is currently uploading.
         * @returns {boolean}
         */
        get isUploading()
        {
            return !this.done && !this.error;
        },

        /**
         * Build API url.
         * @param {string} section
         * @returns {string} The API url.
         */
        buildUrl(section)
        {
            return `/api/fs/upload-chunk/${section}`;
        },

        /**
         * Upload the file.
         * @param {string} section section of the file.
         * @returns {Promise<void>}
         */
        async upload(section)
        {

            if (!this.rawFile)
                return; // Can't upload in that case.

            /**
             * Get CSRF token for uploading request.
             * @type {string}
             */
            const CSRF = document.querySelector('meta[name=csrf-token]')?.content ?? '';

            for (let i = 0; i < this.totalChunks; i++) {

                if (this.error)
                    return; // Abort the process.

                const start = i * CHUNK_SIZE;
                const end = Math.min(start + CHUNK_SIZE, this.rawFile.size);
                const chunk = this.rawFile.slice(start, end);

                const formData = new FormData();

                formData.append('file', chunk);
                formData.append('file_uuid', this.uuid);
                formData.append('current_chunk', i);
                formData.append('total_chunks', this.totalChunks);
                formData.append('filename', this.rawFile.name);
                formData.append('_token', CSRF);

                // -----
                // UPLOAD TIME !
                // -----

                try {
                    const RESPONSE = await fetch(this.buildUrl(section), {method: 'POST', body: formData});

                    if (!RESPONSE.ok) // Problem with the request.
                        throw new Error(`${RESPONSE.status} ${RESPONSE.statusText}`);

                    /** @type {UploadchunkResponse} */
                    const DATA = await RESPONSE.json();

                    if (DATA.success !== true || DATA.uploaded !== true)
                        // The request reached the file server but could not be sent.
                        throw new Error(`${DATA.error}`);

                    this.currentChunk = i + 1;
                    this.progressValue = Math.round(((i + 1) / this.totalChunks) * 100);

                    if (DATA.finished === true) {
                        this.done = true;
                        return;
                    }

                } catch (err) {
                    this.error = 'Error on chunk ' + (i + 1) + '. ' + err.message;
                    this.progressValue = 0;
                    return;
                }

            }
        }

    }

}
