/**
 * ComponentFileUploader - File Upload Module for LiveComponents
 *
 * Features:
 * - Drag & Drop support
 * - Multi-file uploads with queue management
 * - Progress tracking (per-file and overall)
 * - Image/document previews
 * - Client-side validation
 * - CSRF protection
 * - Integration with LiveComponent state management
 *
 * @package Framework\LiveComponents
 */

/**
 * File Upload Progress Tracker
 */
export class UploadProgress {
    constructor(file, fileId) {
        this.file = file;
        this.fileId = fileId;
        this.uploadedBytes = 0;
        this.totalBytes = file.size;
        this.status = 'pending'; // pending, uploading, processing, complete, error
        this.error = null;
        this.xhr = null;
        this.startTime = null;
        this.endTime = null;
    }

    get percentage() {
        if (this.totalBytes === 0) return 100;
        return Math.round((this.uploadedBytes / this.totalBytes) * 100);
    }

    get isComplete() {
        return this.status === 'complete';
    }

    get hasError() {
        return this.status === 'error';
    }

    get isUploading() {
        return this.status === 'uploading';
    }

    get uploadSpeed() {
        if (!this.startTime || !this.isUploading) return 0;
        const elapsed = (Date.now() - this.startTime) / 1000; // seconds
        return elapsed > 0 ? this.uploadedBytes / elapsed : 0; // bytes per second
    }

    get remainingTime() {
        const speed = this.uploadSpeed;
        if (speed === 0) return 0;
        const remaining = this.totalBytes - this.uploadedBytes;
        return remaining / speed; // seconds
    }

    updateProgress(loaded, total) {
        this.uploadedBytes = loaded;
        this.totalBytes = total;
    }

    setStatus(status, error = null) {
        this.status = status;
        this.error = error;

        if (status === 'uploading' && !this.startTime) {
            this.startTime = Date.now();
        }

        if (status === 'complete' || status === 'error') {
            this.endTime = Date.now();
        }
    }

    abort() {
        if (this.xhr) {
            this.xhr.abort();
            this.setStatus('error', 'Upload cancelled');
        }
    }

    toObject() {
        return {
            fileId: this.fileId,
            fileName: this.file.name,
            fileSize: this.totalBytes,
            uploadedBytes: this.uploadedBytes,
            percentage: this.percentage,
            status: this.status,
            error: this.error,
            uploadSpeed: this.uploadSpeed,
            remainingTime: this.remainingTime
        };
    }
}

/**
 * File Validator - Client-side validation
 */
export class FileValidator {
    constructor(options = {}) {
        this.maxFileSize = options.maxFileSize || 10 * 1024 * 1024; // 10MB default
        this.allowedMimeTypes = options.allowedMimeTypes || [];
        this.allowedExtensions = options.allowedExtensions || [];
        this.minFileSize = options.minFileSize || 1; // 1 byte minimum
    }

    validate(file) {
        const errors = [];

        // File size validation
        if (file.size > this.maxFileSize) {
            errors.push(`File size (${this.formatBytes(file.size)}) exceeds maximum allowed size (${this.formatBytes(this.maxFileSize)})`);
        }

        if (file.size < this.minFileSize) {
            errors.push(`File size is too small (minimum: ${this.formatBytes(this.minFileSize)})`);
        }

        // MIME type validation
        if (this.allowedMimeTypes.length > 0 && !this.allowedMimeTypes.includes(file.type)) {
            errors.push(`File type "${file.type}" is not allowed. Allowed types: ${this.allowedMimeTypes.join(', ')}`);
        }

        // Extension validation
        if (this.allowedExtensions.length > 0) {
            const extension = file.name.split('.').pop().toLowerCase();
            if (!this.allowedExtensions.includes(extension)) {
                errors.push(`File extension ".${extension}" is not allowed. Allowed extensions: ${this.allowedExtensions.join(', ')}`);
            }
        }

        // File name validation
        if (file.name.length > 255) {
            errors.push('File name is too long (maximum 255 characters)');
        }

        return errors;
    }

    isValid(file) {
        return this.validate(file).length === 0;
    }

    formatBytes(bytes) {
        if (bytes === 0) return '0 B';
        const k = 1024;
        const sizes = ['B', 'KB', 'MB', 'GB'];
        const i = Math.floor(Math.log(bytes) / Math.log(k));
        return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
    }
}

/**
 * Drag & Drop Zone Manager
 */
export class DragDropZone {
    constructor(element, callbacks = {}) {
        this.element = element;
        this.onFilesDropped = callbacks.onFilesDropped || (() => {});
        this.onDragEnter = callbacks.onDragEnter || (() => {});
        this.onDragLeave = callbacks.onDragLeave || (() => {});

        this.dragCounter = 0; // Track nested drag events
        this.isActive = false;

        this.bindEvents();
    }

    bindEvents() {
        // Prevent default browser behavior for drag events
        ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
            this.element.addEventListener(eventName, (e) => {
                e.preventDefault();
                e.stopPropagation();
            });
        });

        // Handle drag enter
        this.element.addEventListener('dragenter', (e) => {
            this.dragCounter++;
            if (this.dragCounter === 1) {
                this.isActive = true;
                this.element.classList.add('drag-over');
                this.onDragEnter(e);
            }
        });

        // Handle drag leave
        this.element.addEventListener('dragleave', (e) => {
            this.dragCounter--;
            if (this.dragCounter === 0) {
                this.isActive = false;
                this.element.classList.remove('drag-over');
                this.onDragLeave(e);
            }
        });

        // Handle drag over
        this.element.addEventListener('dragover', (e) => {
            // Required to allow drop
        });

        // Handle drop
        this.element.addEventListener('drop', (e) => {
            this.dragCounter = 0;
            this.isActive = false;
            this.element.classList.remove('drag-over');

            const files = Array.from(e.dataTransfer?.files || []);
            if (files.length > 0) {
                this.onFilesDropped(files);
            }
        });
    }

    destroy() {
        this.element.classList.remove('drag-over');
        // Event listeners are automatically removed when element is removed
    }
}

/**
 * Main ComponentFileUploader Class
 */
export class ComponentFileUploader {
    constructor(componentElement, options = {}) {
        this.componentElement = componentElement;
        this.componentId = componentElement.dataset.liveId;

        // Options
        this.maxFileSize = options.maxFileSize || 10 * 1024 * 1024; // 10MB
        this.allowedMimeTypes = options.allowedMimeTypes || [];
        this.allowedExtensions = options.allowedExtensions || [];
        this.maxFiles = options.maxFiles || 10;
        this.autoUpload = options.autoUpload !== false; // default: true
        this.multiple = options.multiple !== false; // default: true
        this.endpoint = options.endpoint || `/live-component/${this.componentId}/upload`;

        // Callbacks
        this.onFileAdded = options.onFileAdded || (() => {});
        this.onFileRemoved = options.onFileRemoved || (() => {});
        this.onUploadStart = options.onUploadStart || (() => {});
        this.onUploadProgress = options.onUploadProgress || (() => {});
        this.onUploadComplete = options.onUploadComplete || (() => {});
        this.onUploadError = options.onUploadError || (() => {});
        this.onAllUploadsComplete = options.onAllUploadsComplete || (() => {});

        // State
        this.files = new Map(); // Map<fileId, UploadProgress>
        this.uploadQueue = [];
        this.activeUploads = 0;
        this.maxConcurrentUploads = options.maxConcurrentUploads || 2;

        // Validator
        this.validator = new FileValidator({
            maxFileSize: this.maxFileSize,
            allowedMimeTypes: this.allowedMimeTypes,
            allowedExtensions: this.allowedExtensions
        });

        // UI Elements (optional)
        this.dropZoneElement = options.dropZone;
        this.fileInputElement = options.fileInput;

        this.initialize();
    }

    initialize() {
        // Setup drag & drop if dropZone provided
        if (this.dropZoneElement) {
            this.dragDropZone = new DragDropZone(this.dropZoneElement, {
                onFilesDropped: (files) => this.addFiles(files),
                onDragEnter: () => this.dropZoneElement.classList.add('drag-active'),
                onDragLeave: () => this.dropZoneElement.classList.remove('drag-active')
            });
        }

        // Setup file input if provided
        if (this.fileInputElement) {
            this.fileInputElement.addEventListener('change', (e) => {
                const files = Array.from(e.target.files || []);
                this.addFiles(files);
                e.target.value = ''; // Reset input
            });
        }
    }

    /**
     * Add files to upload queue
     */
    addFiles(files) {
        const filesToAdd = Array.isArray(files) ? files : [files];

        // Check max files limit
        if (this.files.size + filesToAdd.length > this.maxFiles) {
            const error = `Cannot add files. Maximum ${this.maxFiles} files allowed.`;
            this.onUploadError({ error, fileCount: filesToAdd.length });
            return;
        }

        for (const file of filesToAdd) {
            // Generate unique file ID
            const fileId = this.generateFileId(file);

            // Validate file
            const validationErrors = this.validator.validate(file);

            const progress = new UploadProgress(file, fileId);

            if (validationErrors.length > 0) {
                progress.setStatus('error', validationErrors.join(', '));
                this.files.set(fileId, progress);
                this.onUploadError({
                    fileId,
                    file,
                    errors: validationErrors
                });
                continue;
            }

            // Add to files map
            this.files.set(fileId, progress);
            this.uploadQueue.push(fileId);

            // Callback
            this.onFileAdded({
                fileId,
                file,
                progress: progress.toObject()
            });
        }

        // Auto-upload if enabled
        if (this.autoUpload) {
            this.processQueue();
        }
    }

    /**
     * Remove file from queue
     */
    removeFile(fileId) {
        const progress = this.files.get(fileId);
        if (!progress) return;

        // Abort if uploading
        if (progress.isUploading) {
            progress.abort();
        }

        // Remove from queue
        const queueIndex = this.uploadQueue.indexOf(fileId);
        if (queueIndex !== -1) {
            this.uploadQueue.splice(queueIndex, 1);
        }

        // Remove from files map
        this.files.delete(fileId);

        // Callback
        this.onFileRemoved({ fileId, file: progress.file });
    }

    /**
     * Start uploading all queued files
     */
    uploadAll() {
        this.processQueue();
    }

    /**
     * Process upload queue
     */
    async processQueue() {
        while (this.uploadQueue.length > 0 && this.activeUploads < this.maxConcurrentUploads) {
            const fileId = this.uploadQueue.shift();
            const progress = this.files.get(fileId);

            if (!progress || progress.status !== 'pending') continue;

            this.activeUploads++;
            this.uploadFile(fileId).finally(() => {
                this.activeUploads--;
                this.processQueue(); // Process next file

                // Check if all uploads are complete
                if (this.activeUploads === 0 && this.uploadQueue.length === 0) {
                    this.onAllUploadsComplete({
                        totalFiles: this.files.size,
                        successCount: Array.from(this.files.values()).filter(p => p.isComplete).length,
                        errorCount: Array.from(this.files.values()).filter(p => p.hasError).length
                    });
                }
            });
        }
    }

    /**
     * Upload a single file
     */
    async uploadFile(fileId) {
        const progress = this.files.get(fileId);
        if (!progress) return;

        try {
            // Get current component state
            const componentState = this.getComponentState();

            // Get CSRF tokens
            const csrfTokens = await this.getCsrfTokens();

            // Create FormData
            const formData = new FormData();
            formData.append('file', progress.file);
            formData.append('state', JSON.stringify(componentState));
            formData.append('params', JSON.stringify({ fileId }));

            // Create XHR request
            const xhr = new XMLHttpRequest();
            progress.xhr = xhr;

            // Setup progress tracking
            xhr.upload.addEventListener('progress', (e) => {
                if (e.lengthComputable) {
                    progress.updateProgress(e.loaded, e.total);
                    this.onUploadProgress({
                        fileId,
                        ...progress.toObject()
                    });
                }
            });

            // Setup completion handler
            const uploadPromise = new Promise((resolve, reject) => {
                xhr.addEventListener('load', () => {
                    if (xhr.status >= 200 && xhr.status < 300) {
                        try {
                            const response = JSON.parse(xhr.responseText);
                            if (response.success) {
                                progress.setStatus('complete');

                                // Update component state if provided
                                if (response.state) {
                                    this.updateComponentState(response.state);
                                }

                                // Update component HTML if provided
                                if (response.html) {
                                    this.updateComponentHtml(response.html);
                                }

                                this.onUploadComplete({
                                    fileId,
                                    file: progress.file,
                                    response
                                });

                                resolve(response);
                            } else {
                                throw new Error(response.error || 'Upload failed');
                            }
                        } catch (e) {
                            reject(e);
                        }
                    } else {
                        reject(new Error(`Upload failed with status ${xhr.status}`));
                    }
                });

                xhr.addEventListener('error', () => {
                    reject(new Error('Network error during upload'));
                });

                xhr.addEventListener('abort', () => {
                    reject(new Error('Upload cancelled'));
                });
            });

            // Set status to uploading
            progress.setStatus('uploading');
            this.onUploadStart({ fileId, file: progress.file });

            // Open and send request
            xhr.open('POST', this.endpoint);

            // Set CSRF headers
            xhr.setRequestHeader('X-CSRF-Form-ID', csrfTokens.form_id);
            xhr.setRequestHeader('X-CSRF-Token', csrfTokens.token);
            xhr.setRequestHeader('Accept', 'application/json');
            xhr.setRequestHeader('User-Agent', navigator.userAgent);

            xhr.send(formData);

            await uploadPromise;

        } catch (error) {
            progress.setStatus('error', error.message);
            this.onUploadError({
                fileId,
                file: progress.file,
                error: error.message
            });
        }
    }

    /**
     * Cancel all uploads
     */
    cancelAll() {
        this.uploadQueue = [];
        this.files.forEach(progress => {
            if (progress.isUploading) {
                progress.abort();
            }
        });
    }

    /**
     * Clear all files (completed and pending)
     */
    clearAll() {
        this.cancelAll();
        this.files.clear();
    }

    /**
     * Get overall upload progress
     */
    getOverallProgress() {
        if (this.files.size === 0) return 100;

        const totalBytes = Array.from(this.files.values()).reduce((sum, p) => sum + p.totalBytes, 0);
        const uploadedBytes = Array.from(this.files.values()).reduce((sum, p) => sum + p.uploadedBytes, 0);

        return totalBytes > 0 ? Math.round((uploadedBytes / totalBytes) * 100) : 0;
    }

    /**
     * Get upload statistics
     */
    getStats() {
        const files = Array.from(this.files.values());
        return {
            total: files.length,
            pending: files.filter(p => p.status === 'pending').length,
            uploading: files.filter(p => p.status === 'uploading').length,
            complete: files.filter(p => p.status === 'complete').length,
            error: files.filter(p => p.status === 'error').length,
            overallProgress: this.getOverallProgress()
        };
    }

    /**
     * Helper: Generate unique file ID
     */
    generateFileId(file) {
        return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}-${file.name}`;
    }

    /**
     * Helper: Get CSRF tokens
     */
    async getCsrfTokens() {
        try {
            const response = await fetch(`/api/csrf/token?action=${encodeURIComponent(this.endpoint)}&method=post`, {
                headers: {
                    'Accept': 'application/json',
                    'User-Agent': navigator.userAgent
                }
            });

            if (!response.ok) {
                throw new Error(`CSRF token request failed: ${response.status}`);
            }

            return await response.json();
        } catch (error) {
            console.error('Failed to get CSRF tokens:', error);
            throw error;
        }
    }

    /**
     * Helper: Get component state from DOM
     */
    getComponentState() {
        const stateElement = this.componentElement.querySelector('[data-live-state]');
        if (stateElement) {
            try {
                return JSON.parse(stateElement.textContent || '{}');
            } catch (e) {
                console.warn('Failed to parse component state:', e);
            }
        }
        return {};
    }

    /**
     * Helper: Update component state in DOM
     */
    updateComponentState(newState) {
        const stateElement = this.componentElement.querySelector('[data-live-state]');
        if (stateElement) {
            stateElement.textContent = JSON.stringify(newState);
        }
    }

    /**
     * Helper: Update component HTML
     */
    updateComponentHtml(html) {
        // Find the component content container (usually the component itself)
        const contentElement = this.componentElement.querySelector('[data-live-content]') || this.componentElement;
        if (contentElement) {
            contentElement.innerHTML = html;
        }
    }

    /**
     * Cleanup
     */
    destroy() {
        this.cancelAll();
        this.clearAll();

        if (this.dragDropZone) {
            this.dragDropZone.destroy();
        }
    }
}

export default ComponentFileUploader;
