// Copyright Darko Gjorgjijoski <info@codeverve.com>
// 2020. All Rights Reserved.
// This file is licensed under the GPLv2 License.
// License text available at https://opensource.org/licenses/gpl-2.0.php

'use strict';

/**
 * VimeifyChunkedUploader - A vanilla JavaScript chunked file uploader
 * Replaces Dropzone.js with a focused, lightweight implementation
 */
class VimeifyChunkedUploader {
    /**
     * Default configuration options
     */
    static defaults = {
        url: '',
        paramName: 'file',
        maxFileSize: 0,
        maxFiles: 1,
        acceptedFiles: '',
        chunkSize: 2 * 1024 * 1024, // 2MB default
        parallelChunkUploads: false,
        parallelUploads: 1,
        retryChunks: true,
        retryChunksLimit: 3,
        handler: 'vimeify'
    };

    /**
     * File status constants
     */
    static STATUS = {
        PENDING: 'pending',
        UPLOADING: 'uploading',
        SUCCESS: 'success',
        ERROR: 'error',
        CANCELED: 'canceled'
    };

    /**
     * Speed test state (shared across instances)
     */
    static isSlow = null;
    static speedTestSettings = {
        maxTime: 3000,
        payloadSize: 100 * 1024
    };

    /**
     * Constructor
     * @param {HTMLElement} element - The dropzone container element
     * @param {Object} options - Configuration options
     */
    constructor(element, options = {}) {
        this.element = element;
        this.options = { ...VimeifyChunkedUploader.defaults, ...options };
        this.files = [];
        this.loading = 0;
        this.events = {};
        this.dataTransfer = {};

        this._parseDataAttributes();
        this._init();
    }

    /**
     * Parse data attributes from the element
     */
    _parseDataAttributes() {
        const el = this.element;

        this.dataTransfer = {
            key: el.dataset.fieldKey || '',
            postMaxSize: parseInt(el.dataset.maxSize, 10) || 0,
            name: el.dataset.inputName || 'file'
        };

        if (el.dataset.maxFileNumber) {
            this.options.maxFiles = parseInt(el.dataset.maxFileNumber, 10);
        }
        if (el.dataset.maxSize) {
            this.options.maxFileSize = parseInt(el.dataset.maxSize, 10);
        }
        if (el.dataset.fileChunkSize) {
            this.options.chunkSize = parseInt(el.dataset.fileChunkSize, 10);
        }
        if (el.dataset.inputName) {
            this.options.paramName = el.dataset.inputName;
        }
        if (el.dataset.extensions) {
            this.options.acceptedFiles = el.dataset.extensions.split(',').map(ext => '.' + ext).join(',');
        }
        if (el.dataset.parallelUploads) {
            this.options.parallelChunkUploads = /^true$/i.test(el.dataset.parallelUploads);
        }
        if (el.dataset.maxParallelUploads) {
            this.options.parallelUploads = parseInt(el.dataset.maxParallelUploads, 10);
        }
        if (el.dataset.handler) {
            this.options.handler = el.dataset.handler;
        }
    }

    /**
     * Initialize the uploader
     */
    _init() {
        this._setupDropzone();
        this._bindEvents();
        this._loadExistingFiles();
    }

    /**
     * Set up the dropzone HTML structure
     */
    _setupDropzone() {
        // Check for existing message element
        const existingMessage = this.element.querySelector('.vimeify-message');

        // Only create message element if none exists
        if (!existingMessage) {
            const message = document.createElement('div');
            message.className = 'vimeify-message';
            message.innerHTML = `
                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                    <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
                    <polyline points="17 8 12 3 7 8"/>
                    <line x1="12" y1="3" x2="12" y2="15"/>
                </svg>
                <span class="vimeify-title">${window.Vimeify_Chunked_Upload?.message_title || 'Drop files here or click to upload'}</span>
                <span class="vimeify-hint">${window.Vimeify_Chunked_Upload?.message_hint || ''}</span>
            `;
            this.element.insertBefore(message, this.element.firstChild);
        }

        // Create previews container if it doesn't exist
        if (!this.element.querySelector('.vimeify-previews')) {
            const previews = document.createElement('div');
            previews.className = 'vimeify-previews';
            this.element.appendChild(previews);
        }

        // Create hidden file input
        this._createHiddenInput();
    }

    /**
     * Create hidden file input for click-to-upload
     */
    _createHiddenInput() {
        // Check if hidden input already exists
        const existingInput = this.element.querySelector('.vimeify-hidden-input');
        if (existingInput) {
            this._hiddenInput = existingInput;
            existingInput.addEventListener('change', (e) => {
                if (e.target.files.length > 0) {
                    this._handleFiles(Array.from(e.target.files));
                }
                e.target.value = '';
            });
            return;
        }

        const input = document.createElement('input');
        input.type = 'file';
        input.className = 'vimeify-hidden-input';
        input.accept = this.options.acceptedFiles;
        input.style.display = 'none';

        input.addEventListener('change', (e) => {
            if (e.target.files.length > 0) {
                this._handleFiles(Array.from(e.target.files));
            }
            e.target.value = '';
        });

        this.element.appendChild(input);
        this._hiddenInput = input;
    }

    /**
     * Bind DOM events
     */
    _bindEvents() {
        // Click to open file dialog
        this.element.addEventListener('click', (e) => {
            if (e.target.closest('.vimeify-file-preview') || e.target.closest('.vimeify-remove')) {
                return;
            }
            this._hiddenInput.click();
        });

        // Drag and drop events
        this.element.addEventListener('dragover', (e) => {
            e.preventDefault();
            e.stopPropagation();
            this.element.classList.add('vimeify-drag-hover');
        });

        this.element.addEventListener('dragleave', (e) => {
            e.preventDefault();
            e.stopPropagation();
            this.element.classList.remove('vimeify-drag-hover');
        });

        this.element.addEventListener('drop', (e) => {
            e.preventDefault();
            e.stopPropagation();
            this.element.classList.remove('vimeify-drag-hover');

            if (e.dataTransfer.files.length > 0) {
                this._handleFiles(Array.from(e.dataTransfer.files));
            }
        });
    }

    /**
     * Load existing files from input value
     */
    _loadExistingFiles() {
        const input = this.element.nextElementSibling;
        if (!input || !input.value) return;

        try {
            const existingFiles = JSON.parse(input.value);
            for (const fileData of existingFiles) {
                const mockFile = {
                    name: fileData.file_user_name,
                    size: fileData.size,
                    server_file: fileData.file,
                    nativeFile: null,
                    uuid: this._generateUUID(),
                    accepted: true,
                    status: VimeifyChunkedUploader.STATUS.SUCCESS,
                    previewElement: null,
                    upload: {
                        chunked: true,
                        chunks: [],
                        totalChunkCount: 0,
                        bytesSent: fileData.size
                    },
                    chunkResponse: JSON.stringify({ data: fileData }),
                    retries: 0
                };

                this._createPreviewElement(mockFile);
                this.files.push(mockFile);
            }
            this._toggleMessage();
        } catch (e) {
            // Ignore parse errors
        }
    }

    /**
     * Handle dropped or selected files
     * @param {File[]} nativeFiles - Array of File objects
     */
    _handleFiles(nativeFiles) {
        for (const nativeFile of nativeFiles) {
            // Check max files limit
            const acceptedCount = this.files.filter(f => f.accepted).length;
            if (acceptedCount >= this.options.maxFiles) {
                this._showError(null, this._getErrorMessage('file_limit'));
                continue;
            }

            // Create file object
            const file = this._createFileObject(nativeFile);

            // Validate file
            if (!this._validateFile(file)) {
                continue;
            }

            // Add to files array
            this.files.push(file);

            // Create preview
            this._createPreviewElement(file);

            // Emit addedfile event
            this.emit('addedfile', file);

            // Increment loading counter
            this.loading++;
            this._toggleSubmit();

            // Check file size against post max size
            if (file.size >= this.dataTransfer.postMaxSize) {
                this._handleFileTooLarge(file);
                continue;
            }

            // Start speed test then init upload
            this._speedTest(() => {
                this._initFileUpload(file);
            });
        }

        this._toggleMessage();
    }

    /**
     * Create a file object from native File
     * @param {File} nativeFile - Native File object
     * @returns {Object} File object
     */
    _createFileObject(nativeFile) {
        return {
            name: nativeFile.name,
            size: nativeFile.size,
            type: nativeFile.type,
            nativeFile: nativeFile,
            uuid: this._generateUUID(),
            accepted: true,
            status: VimeifyChunkedUploader.STATUS.PENDING,
            previewElement: null,
            upload: {
                chunked: true,
                chunks: [],
                totalChunkCount: Math.ceil(nativeFile.size / this.options.chunkSize),
                bytesSent: 0
            },
            chunkResponse: null,
            retries: 0,
            isErrorNotUploadedDisplayed: false
        };
    }

    /**
     * Validate a file
     * @param {Object} file - File object
     * @returns {boolean} Whether the file is valid
     */
    _validateFile(file) {
        // Check file extension
        if (this.options.acceptedFiles) {
            const extensions = this.options.acceptedFiles.split(',').map(e => e.trim().toLowerCase());
            const fileExt = '.' + file.name.split('.').pop().toLowerCase();

            if (!extensions.includes(fileExt)) {
                this._showError(file, this._getErrorMessage('file_extension'));
                return false;
            }
        }

        // Check file size
        if (this.options.maxFileSize && file.size > this.options.maxFileSize) {
            this._showError(file, this._getErrorMessage('file_size'));
            return false;
        }

        return true;
    }

    /**
     * Handle file that exceeds post max size
     * @param {Object} file - File object
     */
    _handleFileTooLarge(file) {
        file.accepted = false;
        file.status = VimeifyChunkedUploader.STATUS.ERROR;

        const errorMessage = this._getErrorMessage('file_not_uploaded') + ' ' + this._getErrorMessage('post_max_size');
        this._addErrorToPreview(file, errorMessage);
        file.isErrorNotUploadedDisplayed = true;

        this.loading--;
        this._toggleSubmit();
    }

    /**
     * Create preview element for a file
     * @param {Object} file - File object
     */
    _createPreviewElement(file) {
        const preview = document.createElement('div');
        preview.className = 'vimeify-file-preview';

        if (file.status === VimeifyChunkedUploader.STATUS.SUCCESS) {
            preview.classList.add('vimeify-complete');
        }

        preview.innerHTML = `
            <div class="vimeify-file-image"></div>
            <div class="vimeify-file-details">
                <div class="vimeify-filename">${this._escapeHtml(file.name)}</div>
                <div class="vimeify-filesize">${this._formatFileSize(file.size)}</div>
            </div>
            <div class="vimeify-progress">
                <div class="vimeify-progress-bar" style="width: 0%"></div>
            </div>
            <div class="vimeify-error-message"></div>
            <a href="javascript:void(0)" class="vimeify-remove" title="Remove file"></a>
        `;

        // Bind remove button
        const removeBtn = preview.querySelector('.vimeify-remove');
        removeBtn.addEventListener('click', (e) => {
            e.preventDefault();
            e.stopPropagation();
            this.removeFile(file);
        });

        file.previewElement = preview;

        const previewsContainer = this.element.querySelector('.vimeify-previews');
        previewsContainer.appendChild(preview);
    }

    /**
     * Run speed test to determine connection quality
     * @param {Function} callback - Callback when test completes
     */
    _speedTest(callback) {
        if (VimeifyChunkedUploader.isSlow !== null) {
            setTimeout(callback, 0);
            return;
        }

        const payload = this._generatePayload(VimeifyChunkedUploader.speedTestSettings.payloadSize);
        const startTime = Date.now();

        this._ajaxPost({
            action: this._prefixEndpoint('file_upload_speed_test'),
            data: payload
        }).then(() => {
            const elapsed = Date.now() - startTime;
            VimeifyChunkedUploader.isSlow = elapsed >= VimeifyChunkedUploader.speedTestSettings.maxTime;
            callback();
        }).catch(() => {
            VimeifyChunkedUploader.isSlow = true;
            callback();
        });
    }

    /**
     * Generate random payload for speed test
     * @param {number} size - Payload size in bytes
     * @returns {string} Random string
     */
    _generatePayload(size) {
        let data = '';
        for (let i = 0; i < size; i++) {
            data += String.fromCharCode(Math.round(Math.random() * 36 + 64));
        }
        return data;
    }

    /**
     * Initialize file upload - get chunk size from server
     * @param {Object} file - File object
     */
    _initFileUpload(file) {
        const requestData = {
            action: this._prefixEndpoint('upload_chunk_init'),
            key: this.dataTransfer.key,
            name: file.name,
            slow: VimeifyChunkedUploader.isSlow,
            dzuuid: file.uuid,
            dztotalfilesize: file.size
        };

        this._ajaxPost(requestData)
            .then(response => {
                // Update options from server response
                if (response.dzchunksize) {
                    this.options.chunkSize = parseInt(response.dzchunksize, 10);
                    file.upload.totalChunkCount = Math.ceil(file.size / this.options.chunkSize);
                }
                if (response.hasOwnProperty('parallelChunkUploads')) {
                    this.options.parallelChunkUploads = response.parallelChunkUploads;
                }

                // Start uploading chunks
                this._startChunkedUpload(file);
            })
            .catch(error => {
                file.status = VimeifyChunkedUploader.STATUS.ERROR;
                const errorMessage = this._extractErrorMessage(error);
                this._addErrorToPreview(file, errorMessage);
                this.emit('error', file, errorMessage);

                this.loading--;
                this._toggleSubmit();
            });
    }

    /**
     * Start chunked upload for a file
     * @param {Object} file - File object
     */
    _startChunkedUpload(file) {
        file.status = VimeifyChunkedUploader.STATUS.UPLOADING;
        file.previewElement.classList.add('vimeify-processing');

        // Initialize chunks
        const chunkCount = file.upload.totalChunkCount;
        file.upload.chunks = [];

        for (let i = 0; i < chunkCount; i++) {
            const start = i * this.options.chunkSize;
            const end = Math.min(start + this.options.chunkSize, file.size);

            file.upload.chunks.push({
                index: i,
                start: start,
                end: end,
                size: end - start,
                status: VimeifyChunkedUploader.STATUS.PENDING,
                bytesSent: 0,
                xhr: null,
                retries: 0
            });
        }

        // Upload chunks
        this._uploadNextChunks(file);
    }

    /**
     * Upload the next pending chunks
     * @param {Object} file - File object
     */
    _uploadNextChunks(file) {
        if (file.status === VimeifyChunkedUploader.STATUS.ERROR ||
            file.status === VimeifyChunkedUploader.STATUS.CANCELED) {
            return;
        }

        const pendingChunks = file.upload.chunks.filter(c => c.status === VimeifyChunkedUploader.STATUS.PENDING);
        const uploadingChunks = file.upload.chunks.filter(c => c.status === VimeifyChunkedUploader.STATUS.UPLOADING);

        if (pendingChunks.length === 0 && uploadingChunks.length === 0) {
            // All chunks done
            this._finalizeUpload(file);
            return;
        }

        // Determine how many more chunks we can upload in parallel
        const maxConcurrent = this.options.parallelChunkUploads ? this.options.parallelUploads : 1;
        const availableSlots = maxConcurrent - uploadingChunks.length;

        for (let i = 0; i < Math.min(availableSlots, pendingChunks.length); i++) {
            this._uploadChunk(file, pendingChunks[i]);
        }
    }

    /**
     * Upload a single chunk
     * @param {Object} file - File object
     * @param {Object} chunk - Chunk object
     */
    _uploadChunk(file, chunk) {
        chunk.status = VimeifyChunkedUploader.STATUS.UPLOADING;

        const blob = file.nativeFile.slice(chunk.start, chunk.end);
        const formData = new FormData();

        formData.append(this.options.paramName, blob, file.name);
        formData.append('action', this._prefixEndpoint('upload_chunk'));
        formData.append('key', this.dataTransfer.key);
        formData.append('dzuuid', file.uuid);
        formData.append('dzchunkindex', chunk.index);
        formData.append('dztotalfilesize', file.size);
        formData.append('dzchunksize', this.options.chunkSize);
        formData.append('dztotalchunkcount', file.upload.totalChunkCount);
        formData.append('dzchunkbyteoffset', chunk.start);

        const xhr = new XMLHttpRequest();
        chunk.xhr = xhr;

        xhr.open('POST', window.Vimeify_Chunked_Upload.url, true);
        xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');

        // Progress handler
        xhr.upload.addEventListener('progress', (e) => {
            if (e.lengthComputable) {
                chunk.bytesSent = e.loaded;
                this._updateProgress(file);
            }
        });

        // Load handler
        xhr.addEventListener('load', () => {
            if (xhr.status >= 200 && xhr.status < 300) {
                chunk.status = VimeifyChunkedUploader.STATUS.SUCCESS;
                chunk.bytesSent = chunk.size;
                this._updateProgress(file);
                this._uploadNextChunks(file);
            } else {
                this._handleChunkError(file, chunk, xhr.responseText);
            }
        });

        // Error handler
        xhr.addEventListener('error', () => {
            this._handleChunkError(file, chunk, 'Network error');
        });

        // Abort handler
        xhr.addEventListener('abort', () => {
            chunk.status = VimeifyChunkedUploader.STATUS.CANCELED;
        });

        // Emit sending event
        this.emit('sending', file, xhr, formData);

        xhr.send(formData);
    }

    /**
     * Handle chunk upload error
     * @param {Object} file - File object
     * @param {Object} chunk - Chunk object
     * @param {string} errorMessage - Error message
     */
    _handleChunkError(file, chunk, errorMessage) {
        chunk.retries++;

        if (this.options.retryChunks && chunk.retries < this.options.retryChunksLimit) {
            // Retry the chunk
            chunk.status = VimeifyChunkedUploader.STATUS.PENDING;
            chunk.bytesSent = 0;

            setTimeout(() => {
                this._uploadNextChunks(file);
            }, 1000 * chunk.retries);
        } else {
            // Give up
            chunk.status = VimeifyChunkedUploader.STATUS.ERROR;
            file.status = VimeifyChunkedUploader.STATUS.ERROR;
            file.previewElement.classList.add('vimeify-error');
            file.previewElement.classList.remove('vimeify-processing');

            // Try to extract actual error message from server response
            let displayError;
            if (errorMessage) {
                // Try to parse as JSON (xhr.responseText from server)
                try {
                    const parsed = JSON.parse(errorMessage);
                    displayError = this._extractErrorMessage(parsed);
                } catch (e) {
                    // Not JSON, use as-is if it's a string
                    displayError = typeof errorMessage === 'string'
                        ? errorMessage
                        : this._getErrorMessage('file_not_uploaded');
                }
            } else {
                displayError = this._getErrorMessage('file_not_uploaded');
            }

            this._addErrorToPreview(file, displayError);
            file.isErrorNotUploadedDisplayed = true;

            this.emit('error', file, displayError);

            this.loading--;
            this._toggleSubmit();
        }
    }

    /**
     * Update progress display for a file
     * @param {Object} file - File object
     */
    _updateProgress(file) {
        // Calculate total bytes sent across all chunks
        const totalSent = file.upload.chunks.reduce((sum, chunk) => {
            if (chunk.status === VimeifyChunkedUploader.STATUS.SUCCESS) {
                return sum + chunk.size;
            }
            return sum + (chunk.bytesSent || 0);
        }, 0);

        // Cap at 99.9% until finalization completes
        const progress = Math.min(99.9, (totalSent / file.size) * 100);

        file.upload.bytesSent = totalSent;

        // Update progress bar
        const progressBar = file.previewElement.querySelector('.vimeify-progress-bar');
        if (progressBar) {
            progressBar.style.width = progress + '%';
        }

        this.emit('uploadprogress', file, progress, totalSent);
    }

    /**
     * Finalize upload after all chunks complete
     * @param {Object} file - File object
     */
    _finalizeUpload(file) {
        const requestData = {
            action: this._prefixEndpoint('file_chunks_uploaded'),
            key: this.dataTransfer.key,
            name: file.name,
            dzuuid: file.uuid,
            dztotalfilesize: file.size
        };

        this._ajaxPost(requestData)
            .then(response => {
                file.chunkResponse = JSON.stringify({ data: response });
                file.status = VimeifyChunkedUploader.STATUS.SUCCESS;

                file.previewElement.classList.remove('vimeify-processing');
                file.previewElement.classList.add('vimeify-complete');

                // Set progress to 100%
                const progressBar = file.previewElement.querySelector('.vimeify-progress-bar');
                if (progressBar) {
                    progressBar.style.width = '100%';
                }

                this.loading--;
                this._toggleSubmit();
                this._updateInputValue();

                this.emit('chunksUploaded', file, () => {});
                this.emit('complete', file);
            })
            .catch(error => {
                file.retries++;

                if (file.retries < 3) {
                    // Retry finalization
                    setTimeout(() => {
                        this._finalizeUpload(file);
                    }, 5000 * file.retries);
                } else {
                    file.status = VimeifyChunkedUploader.STATUS.ERROR;
                    file.previewElement.classList.add('vimeify-error');
                    file.previewElement.classList.remove('vimeify-processing');

                    const displayError = this._extractErrorMessage(error);
                    this._addErrorToPreview(file, displayError);
                    file.isErrorNotUploadedDisplayed = true;

                    this.emit('error', file, displayError);

                    this.loading--;
                    this._toggleSubmit();
                }
            });
    }

    /**
     * Remove a file from the uploader
     * @param {Object} file - File object
     */
    removeFile(file) {
        // Cancel any pending uploads
        if (file.upload.chunks) {
            for (const chunk of file.upload.chunks) {
                if (chunk.xhr) {
                    chunk.xhr.abort();
                }
            }
        }

        // Remove from server if uploaded
        const responseJson = file.chunkResponse || (file.xhr ? file.xhr.responseText : null);
        if (responseJson) {
            try {
                const response = typeof responseJson === 'string' ? JSON.parse(responseJson) : responseJson;
                if (response?.data?.file) {
                    this._removeFromServer(response.data.file);
                }
            } catch (e) {
                // Ignore parse errors
            }
        } else if (file.server_file) {
            this._removeFromServer(file.server_file);
        }

        // Remove preview element
        if (file.previewElement) {
            file.previewElement.remove();
        }

        // Remove from files array
        const index = this.files.indexOf(file);
        if (index > -1) {
            this.files.splice(index, 1);
        }

        // Update loading counter if needed
        if (file.status === VimeifyChunkedUploader.STATUS.PENDING ||
            file.status === VimeifyChunkedUploader.STATUS.UPLOADING) {
            this.loading--;
            this._toggleSubmit();
        }

        this._updateInputValue();
        this._toggleMessage();

        this.emit('removedfile', file);
    }

    /**
     * Remove file from server
     * @param {string} filename - Server filename
     */
    _removeFromServer(filename) {
        this._ajaxPost({
            action: this._prefixEndpoint('remove_file'),
            file: filename,
            key: this.dataTransfer.key
        }).catch(() => {
            // Ignore errors
        });
    }

    /**
     * Update the hidden input value
     */
    _updateInputValue() {
        const wrapper = this.element.closest('.vimeify-chunked-uploader-wrapper');
        if (!wrapper) return;

        const input = wrapper.querySelector('input[name=' + this.dataTransfer.name + ']');
        if (!input) return;

        const values = this.files
            .filter(f => f.status === VimeifyChunkedUploader.STATUS.SUCCESS)
            .map(f => {
                try {
                    const response = typeof f.chunkResponse === 'string' ? JSON.parse(f.chunkResponse) : f.chunkResponse;
                    return response?.data;
                } catch (e) {
                    return null;
                }
            })
            .filter(v => v);

        input.value = values.length ? JSON.stringify(values) : '';

        // Trigger input event
        const event = new Event('input', { bubbles: true });
        input.dispatchEvent(event);

        // Trigger jQuery validation if available
        if (window.jQuery?.fn?.valid) {
            window.jQuery(input).valid();
        }
    }

    /**
     * Toggle the dropzone message visibility
     */
    _toggleMessage() {
        const message = this.element.querySelector('.vimeify-message');
        if (!message) return;

        const acceptedCount = this.files.filter(f => f.accepted).length;
        if (acceptedCount >= this.options.maxFiles) {
            message.classList.add('hide');
        } else {
            message.classList.remove('hide');
        }
    }

    /**
     * Toggle submit button state
     */
    _toggleSubmit() {
        const form = this.element.closest('form');
        if (!form) return;

        let btn = form.querySelector('input[type=submit]') || form.querySelector('button[type=submit]');
        if (!btn) return;

        // Ensure loading doesn't go below 0
        if (this.loading < 0) {
            this.loading = 0;
        }

        const disabled = this.loading > 0;

        if (disabled) {
            btn.disabled = true;

            if (!form.querySelector('.vimeify-submit-overlay')) {
                const parent = btn.parentElement;
                parent.classList.add('vimeify-submit-overlay-container');

                const overlay = document.createElement('div');
                overlay.className = 'vimeify-submit-overlay';
                overlay.style.width = btn.offsetWidth + 'px';
                overlay.style.height = parent.offsetHeight + 'px';
                overlay.addEventListener('click', () => this._showLoadingMessage(form));
                parent.appendChild(overlay);
            }
        } else {
            btn.disabled = false;

            const overlay = form.querySelector('.vimeify-submit-overlay');
            if (overlay) {
                overlay.remove();
            }

            const parent = btn.parentElement;
            if (parent) {
                parent.classList.remove('vimeify-submit-overlay-container');
            }

            const alert = form.querySelector('.vimeify-uploading-in-progress-alert');
            if (alert) {
                alert.remove();
            }
        }
    }

    /**
     * Show loading message when user tries to submit during upload
     * @param {HTMLFormElement} form - Form element
     */
    _showLoadingMessage(form) {
        if (!form.querySelector('.vimeify-uploading-in-progress-alert')) {
            const submitContainer = form.querySelector('.vimeify-submit-container');
            if (submitContainer) {
                const alertDiv = document.createElement('div');
                alertDiv.className = 'vimeify-error-alert vimeify-uploading-in-progress-alert';
                alertDiv.textContent = window.Vimeify_Chunked_Upload?.loading_message || 'File upload is in progress. Please wait...';
                submitContainer.parentNode.insertBefore(alertDiv, submitContainer);
            }
        }
    }

    /**
     * Add error message to preview element
     * @param {Object} file - File object
     * @param {string} message - Error message
     */
    _addErrorToPreview(file, message) {
        if (!file.previewElement) return;

        file.previewElement.classList.add('vimeify-error');

        const errorContainer = file.previewElement.querySelector('.vimeify-error-message');
        if (errorContainer) {
            const span = document.createElement('span');
            span.textContent = message;
            errorContainer.appendChild(span);
        }
    }

    /**
     * Show error for a file (creates preview if needed)
     * @param {Object|null} file - File object or null
     * @param {string} message - Error message
     */
    _showError(file, message) {
        if (file) {
            file.accepted = false;
            file.status = VimeifyChunkedUploader.STATUS.ERROR;
            this._createPreviewElement(file);
            this._addErrorToPreview(file, message);
            this.files.push(file);
        }
        this.emit('error', file, message);
    }

    /**
     * Extract error message string from various error formats
     * @param {*} error - Error in any format (string, object, etc.)
     * @returns {string} Error message string
     */
    _extractErrorMessage(error) {
        // Already a string
        if (typeof error === 'string') {
            return error;
        }

        if (error) {
            // WordPress AJAX error response: {success: false, data: "message"}
            if (typeof error.data === 'string') {
                return error.data;
            }
            // WordPress AJAX error with object data: {success: false, data: {message: "..."}}
            if (error.data && typeof error.data.message === 'string') {
                return error.data.message;
            }
            // WordPress AJAX error with array data: {success: false, data: [{message: "..."}]}
            if (Array.isArray(error.data) && error.data[0]?.message) {
                return error.data[0].message;
            }
            // Direct message property
            if (typeof error.message === 'string') {
                return error.message;
            }
            // Wrapped response: {responseJSON: {data: "message"}}
            if (error.responseJSON?.data) {
                if (typeof error.responseJSON.data === 'string') {
                    return error.responseJSON.data;
                }
                if (typeof error.responseJSON.data.message === 'string') {
                    return error.responseJSON.data.message;
                }
            }
        }

        // Fallback
        return this._getErrorMessage('file_not_uploaded');
    }

    /**
     * Get localized error message
     * @param {string} key - Error key
     * @returns {string} Error message
     */
    _getErrorMessage(key) {
        const defaults = {
            file_not_uploaded: 'This file was not uploaded.',
            file_limit: 'File limit has been reached.',
            file_extension: 'File type is not allowed.',
            file_size: 'File exceeds the max size allowed.',
            post_max_size: 'File exceeds the upload limit allowed.'
        };

        let message = window.Vimeify_Chunked_Upload?.errors?.[key] || defaults[key] || key;

        if (key === 'file_limit') {
            message = message.replace('{fileLimit}', this.options.maxFiles);
        }

        return message;
    }

    /**
     * Prefix an endpoint with the handler
     * @param {string} action - Action name
     * @returns {string} Prefixed action
     */
    _prefixEndpoint(action) {
        return this.options.handler + '_' + action;
    }

    /**
     * Make an AJAX POST request
     * @param {Object} data - Request data
     * @returns {Promise} Promise resolving to response data
     */
    _ajaxPost(data) {
        return new Promise((resolve, reject) => {
            const xhr = new XMLHttpRequest();
            const formData = new FormData();

            for (const key in data) {
                if (data.hasOwnProperty(key)) {
                    formData.append(key, data[key]);
                }
            }

            xhr.open('POST', window.Vimeify_Chunked_Upload.url, true);
            xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');

            xhr.onload = () => {
                if (xhr.status >= 200 && xhr.status < 300) {
                    try {
                        const response = JSON.parse(xhr.responseText);
                        if (response.success) {
                            resolve(response.data);
                        } else {
                            reject(response);
                        }
                    } catch (e) {
                        reject({
                            responseJSON: { success: false, data: xhr.responseText },
                            responseText: xhr.responseText
                        });
                    }
                } else {
                    reject({
                        responseJSON: null,
                        responseText: xhr.responseText,
                        status: xhr.status
                    });
                }
            };

            xhr.onerror = () => {
                reject({
                    responseJSON: null,
                    responseText: xhr.responseText,
                    status: xhr.status
                });
            };

            xhr.send(formData);
        });
    }

    /**
     * Generate a UUID
     * @returns {string} UUID
     */
    _generateUUID() {
        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
            const r = Math.random() * 16 | 0;
            const v = c === 'x' ? r : (r & 0x3 | 0x8);
            return v.toString(16);
        });
    }

    /**
     * Format file size for display
     * @param {number} bytes - File size in bytes
     * @returns {string} Formatted size
     */
    _formatFileSize(bytes) {
        if (bytes === 0) return '0 Bytes';
        const k = 1024;
        const sizes = ['Bytes', 'KB', 'MB', 'GB'];
        const i = Math.floor(Math.log(bytes) / Math.log(k));
        return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
    }

    /**
     * Escape HTML characters
     * @param {string} str - String to escape
     * @returns {string} Escaped string
     */
    _escapeHtml(str) {
        const div = document.createElement('div');
        div.textContent = str;
        return div.innerHTML;
    }

    /**
     * Register an event handler
     * @param {string} event - Event name
     * @param {Function} callback - Event handler
     * @returns {VimeifyChunkedUploader} This instance for chaining
     */
    on(event, callback) {
        if (!this.events[event]) {
            this.events[event] = [];
        }
        this.events[event].push(callback);
        return this;
    }

    /**
     * Emit an event
     * @param {string} event - Event name
     * @param {...*} args - Event arguments
     */
    emit(event, ...args) {
        if (this.events[event]) {
            for (const callback of this.events[event]) {
                callback.apply(this, args);
            }
        }
    }

    /**
     * Process the upload queue (compatibility method)
     */
    processQueue() {
        // This is a no-op for compatibility
        // Our implementation starts uploads immediately
    }
}

/**
 * Initialize all chunked uploaders on the page
 */
(function() {
    'use strict';

    function ready() {
        window.vimeify_chunked_upload_fields = window.vimeify_chunked_upload_fields || {};

        const elements = document.querySelectorAll('.vimeify-chunked-uploader');
        const uploaders = [];

        elements.forEach(el => {
            const uploader = new VimeifyChunkedUploader(el);
            uploaders.push(uploader);
        });

        window.vimeify_chunked_upload_fields.uploaders = uploaders;
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', ready);
    } else {
        ready();
    }

    // Export globally
    window.VimeifyChunkedUploader = VimeifyChunkedUploader;
    window.vimeifyChunkedFileUpload = {
        init: ready
    };
})();
