/**
 * Client-side ELPX Generator for eXeLearning exports
 *
 * Generates .elpx files on-the-fly by:
 * 1. Reading the manifest from window.__ELPX_MANIFEST__
 * 2. Fetching all files listed in the manifest
 * 3. Creating a ZIP file using fflate
 * 4. Triggering the download
 *
 * The manifest is generated by Html5Exporter and embedded in HTML pages.
 *
 * Released under Attribution-ShareAlike 4.0 International License.
 * Author: eXeLearning team (http://exelearning.net/)
 * License: http://creativecommons.org/licenses/by-sa/4.0/
 */
(function (global) {
    'use strict';

    // Check if fflate is available
    if (typeof fflate === 'undefined') {
        console.error('[ELPX Download] fflate library is not loaded');
        return;
    }

    /**
     * Get a translated string from $exe_i18n (set by common_i18n.js) with English fallback.
     * @param {string} key - Translation key
     * @param {string} fallback - English fallback if $exe_i18n is unavailable
     * @returns {string}
     */
    function i18n(key, fallback) {
        return (typeof $exe_i18n !== 'undefined' && $exe_i18n[key]) ? $exe_i18n[key] : fallback;
    }

    /**
     * Check if running in file:// protocol (where fetch is blocked by same-origin policy)
     * @returns {boolean}
     */
    function isFileProtocol() {
        return window.location.protocol === 'file:';
    }

    /**
     * Main download function - generates and downloads .elpx
     * @param {Object} options - Optional configuration
     * @param {string} options.filename - Override filename (without extension)
     * @returns {Promise<void>}
     */
    async function downloadElpx(options) {
        options = options || {};

        // In file:// context, use folder picker approach (fetch is blocked by same-origin)
        if (isFileProtocol()) {
            return downloadElpxViaFolderPicker(options);
        }

        try {
            // Show loading indicator if available
            showLoadingIndicator(true);

            // 1. Read manifest
            var manifest = window.__ELPX_MANIFEST__;
            if (!manifest || !manifest.files) {
                throw new Error('ELPX manifest not found. Export may not include download-source-file support.');
            }

            // 2. Determine base path for fetching
            var basePath = manifest.basePath || getBasePath();

            // 3. Fetch all files from manifest
            var files = {};
            var total = manifest.files.length;
            var completed = 0;

            // For preview mode, capture the current HTML as index.html
            if (manifest.isPreview) {
                // Clone the document to avoid modifying the live DOM
                var htmlClone = document.documentElement.cloneNode(true);
                // Remove the manifest script (don't need it in the download)
                var manifestScripts = htmlClone.querySelectorAll('script');
                manifestScripts.forEach(function (script) {
                    if (script.textContent && script.textContent.indexOf('__ELPX_MANIFEST__') !== -1) {
                        script.remove();
                    }
                });
                files['index.html'] = stringToUint8Array('<!DOCTYPE html>\n' + htmlClone.outerHTML);
            }

            // Fetch all manifest files with a sliding concurrency pool
            var concurrency = 10;
            var fileEntries = manifest.files;
            var fetchIndex = 0;

            async function fetchFile(path) {
                // Skip HTML files in preview (already captured above)
                if (manifest.isPreview && (path === 'index.html' || path.startsWith('html/'))) {
                    completed++;
                    updateProgress(completed, total);
                    return null;
                }

                try {
                    var url = basePath + path;
                    var response = await fetch(url);
                    if (!response.ok) {
                        console.warn('[ELPX Download] Failed to fetch: ' + path + ' (' + response.status + ')');
                        return null;
                    }
                    var buffer = await response.arrayBuffer();
                    return { path: path, data: new Uint8Array(buffer) };
                } catch (e) {
                    console.warn('[ELPX Download] Error fetching: ' + path, e);
                    return null;
                } finally {
                    completed++;
                    updateProgress(completed, total);
                }
            }

            async function fetchWorker() {
                while (fetchIndex < fileEntries.length) {
                    var currentIndex = fetchIndex++;
                    var result = await fetchFile(fileEntries[currentIndex]);
                    if (result) {
                        files[result.path] = result.data;
                    }
                }
            }

            var workers = [];
            for (var w = 0; w < Math.min(concurrency, fileEntries.length); w++) {
                workers.push(fetchWorker());
            }
            await Promise.all(workers);

            // 4. Create ZIP and download
            var projectName = options.filename || manifest.projectTitle || 'eXeLearning-project';
            await createZipAndDownload(files, sanitizeFilename(projectName));

            showLoadingIndicator(false);
        } catch (error) {
            showLoadingIndicator(false);
            console.error('[ELPX Download] Error:', error);
            alert('Error generating ELPX file: ' + error.message);
        }
    }

    /**
     * Timeout for folder picker dialog (2 minutes).
     * If neither onchange nor cancel fires within this time, the dialog likely didn't open.
     * @type {number}
     */
    var FOLDER_PICKER_TIMEOUT = 120000;

    /**
     * Download ELPX by letting user select the export folder
     * Works in file:// context where fetch() is blocked by same-origin policy
     * @param {Object} options - Optional configuration
     * @returns {Promise<void>}
     */
    async function downloadElpxViaFolderPicker(options) {
        options = options || {};
        var manifest = window.__ELPX_MANIFEST__;
        var projectName =
            options.filename || (manifest && manifest.projectTitle) || 'eXeLearning-project';

        return new Promise(function (resolve, reject) {
            var timeoutId = null;

            // Create hidden folder input
            var input = document.createElement('input');
            input.type = 'file';
            input.webkitdirectory = true;
            input.multiple = true;
            input.style.display = 'none';
            document.body.appendChild(input);

            function cleanup() {
                if (timeoutId) {
                    clearTimeout(timeoutId);
                    timeoutId = null;
                }
                if (input.parentNode) {
                    document.body.removeChild(input);
                }
            }

            input.onchange = async function () {
                cleanup();
                try {
                    showLoadingIndicator(true);

                    var files = {};
                    var fileArray = Array.from(input.files);

                    if (fileArray.length === 0) {
                        throw new Error(i18n('elpxFolderPickerEmpty', 'No files were returned by the folder picker. This is a known limitation when opening exported files with the file:// protocol in some browsers. Try using a different browser or opening the file through a local web server.'));
                    }

                    // Get the folder name prefix to strip
                    // webkitRelativePath is like "folder-name/index.html"
                    var firstPath = fileArray[0].webkitRelativePath;
                    var folderPrefix = firstPath.split('/')[0] + '/';

                    // Read all files in parallel
                    var readPromises = fileArray
                        .filter(function (file) {
                            var relativePath = file.webkitRelativePath;
                            if (relativePath.startsWith(folderPrefix)) {
                                relativePath = relativePath.substring(folderPrefix.length);
                            }
                            // Skip hidden files and system files
                            return !relativePath.startsWith('.') && !relativePath.includes('/.');
                        })
                        .map(async function (file) {
                            var relativePath = file.webkitRelativePath;
                            if (relativePath.startsWith(folderPrefix)) {
                                relativePath = relativePath.substring(folderPrefix.length);
                            }
                            var buffer = await file.arrayBuffer();
                            return { path: relativePath, data: new Uint8Array(buffer) };
                        });

                    var readResults = await Promise.all(readPromises);
                    readResults.forEach(function (result) {
                        files[result.path] = result.data;
                    });

                    // Generate ZIP and download
                    await createZipAndDownload(files, sanitizeFilename(projectName));

                    showLoadingIndicator(false);
                    resolve();
                } catch (error) {
                    showLoadingIndicator(false);
                    console.error('[ELPX Download] Folder picker error:', error);
                    alert(error.message);
                    reject(error);
                }
            };

            // Handle cancel (user closes dialog without selecting)
            input.addEventListener('cancel', function () {
                cleanup();
                resolve(); // User cancelled, not an error
            });

            // Set timeout: if neither onchange nor cancel fires, the dialog likely didn't open
            timeoutId = setTimeout(function () {
                cleanup();
                var errorMsg = i18n('elpxFolderPickerTimeout', 'The folder picker did not respond. This may happen when opening exported files directly from the filesystem (file:// protocol). Try opening the file through a local web server instead.');
                console.error('[ELPX Download] Folder picker timeout');
                alert(errorMsg);
                reject(new Error(errorMsg));
            }, FOLDER_PICKER_TIMEOUT);

            // Open folder picker immediately (no alert - would consume user activation)
            input.click();
        });
    }

    /**
     * Add tooltip to download buttons and warning icon when in file:// context
     * Called on page load to inform users about folder selection requirement
     */
    function addFileProtocolWarning() {
        if (!isFileProtocol()) return;

        var warningMessage = i18n('elpxFileProtocolWarning', 'Local mode: Due to browser security policy, you will need to select the folder from which you opened this file. On a web server this will not be necessary.');
        var buttons = document.querySelectorAll('.exe-download-package-link a, .exe-download-package-link button');

        buttons.forEach(function (btn) {
            // Skip if already has warning
            if (btn.hasAttribute('data-file-protocol-warning')) return;

            // Add native tooltip to entire button
            btn.title = warningMessage;
            btn.setAttribute('data-file-protocol-warning', 'true');

            // Add warning icon with Bootstrap tooltip to the right of the button
            var warning = document.createElement('span');
            warning.className = 'exe-file-protocol-warning';
            warning.innerHTML = ' ⚠️';
            warning.style.cssText = 'cursor: help; font-size: 0.9em; margin-left: 0.3em;';

            // Bootstrap tooltip attributes
            warning.setAttribute('data-bs-toggle', 'tooltip');
            warning.setAttribute('data-bs-placement', 'right');
            warning.setAttribute('title', warningMessage);

            // Insert after the button
            if (btn.nextSibling) {
                btn.parentNode.insertBefore(warning, btn.nextSibling);
            } else {
                btn.parentNode.appendChild(warning);
            }

            // Initialize Bootstrap tooltip if available
            if (typeof bootstrap !== 'undefined' && bootstrap.Tooltip) {
                new bootstrap.Tooltip(warning);
            }
        });
    }

    // Add warning on DOM ready
    if (typeof document !== 'undefined') {
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', addFileProtocolWarning);
        } else {
            // DOM already loaded
            setTimeout(addFileProtocolWarning, 100);
        }
    }

    /**
     * Get base path for asset URLs (for non-manifest fallback)
     * @returns {string}
     */
    function getBasePath() {
        // Check if we're in index.html (root) or html/ subdirectory
        var path = window.location.pathname;
        if (path.indexOf('/html/') >= 0) {
            return '../';
        }
        return '';
    }

    /**
     * Sanitize string for use as filename
     * @param {string} str - String to sanitize
     * @returns {string}
     */
    function sanitizeFilename(str) {
        if (!str) return 'eXeLearning-project';

        // Decode HTML entities
        var temp = document.createElement('div');
        temp.innerHTML = str;
        str = temp.textContent || temp.innerText || str;

        // Remove or replace invalid characters
        return str
            .replace(/[<>:"/\\|?*]/g, '-')
            .replace(/\s+/g, ' ')
            .trim()
            .substring(0, 100);
    }

    /**
     * Create ZIP and trigger download
     * @param {Object} files - Map of path -> Uint8Array
     * @param {string} projectName - Project name for filename
     */
    // File extensions that are already compressed — use STORE (level 0) to skip wasting CPU
    var STORE_EXTENSIONS =
        /\.(mp4|mp3|ogg|ogv|webm|woff|woff2|zip|gz|elpx)$/i;

    async function createZipAndDownload(files, projectName) {
        return new Promise(function (resolve, reject) {
            try {
                // Set per-file compression: skip already-compressed formats
                var zipInput = {};
                var fileKeys = Object.keys(files);
                for (var f = 0; f < fileKeys.length; f++) {
                    var key = fileKeys[f];
                    if (STORE_EXTENSIONS.test(key)) {
                        zipInput[key] = [files[key], { level: 0 }];
                    } else {
                        zipInput[key] = [files[key], { level: 6 }];
                    }
                }

                // Use fflate.zip for async compression
                fflate.zip(zipInput, function (err, data) {
                    if (err) {
                        reject(err);
                        return;
                    }

                    // Create blob and download
                    var blob = new Blob([data], { type: 'application/zip' });
                    var url = URL.createObjectURL(blob);

                    var a = document.createElement('a');
                    a.href = url;
                    a.download = projectName + '.elpx';
                    document.body.appendChild(a);
                    a.click();
                    document.body.removeChild(a);

                    // Clean up
                    setTimeout(function () {
                        URL.revokeObjectURL(url);
                    }, 1000);

                    resolve();
                });
            } catch (e) {
                reject(e);
            }
        });
    }

    /**
     * Convert string to Uint8Array (UTF-8)
     * @param {string} str - String to convert
     * @returns {Uint8Array}
     */
    function stringToUint8Array(str) {
        return new TextEncoder().encode(str);
    }

    /**
     * CSS classes added to the host button so the absolutely positioned
     * progress bar is clipped and stacks correctly (Bootstrap utility classes).
     * @type {string[]}
     */
    var PROGRESS_HOST_CLASSES = ['position-relative', 'overflow-hidden'];

    /**
     * Find all download-source-file buttons currently in the DOM.
     * @returns {NodeListOf<Element>}
     */
    function getDownloadButtons() {
        return document.querySelectorAll('.exe-download-package-link a, .exe-download-package-link button');
    }

    /**
     * Show/hide the in-button progress indicator.
     *
     * On show, the original button text is wrapped in a Bootstrap-styled
     * progress bar overlay so the user sees a determinate progress bar
     * without leaving the button (matches the project's Bootstrap UI).
     *
     * @param {boolean} show - Whether to show the indicator
     */
    function showLoadingIndicator(show) {
        var buttons = getDownloadButtons();
        buttons.forEach(function (btn) {
            if (show) {
                // Preserve original markup/classes so we can restore on completion/error
                btn.setAttribute('data-original-html', btn.innerHTML);
                btn.setAttribute('data-original-class', btn.className);
                PROGRESS_HOST_CLASSES.forEach(function (cls) {
                    btn.classList.add(cls);
                });
                btn.style.pointerEvents = 'none';
                btn.setAttribute('aria-busy', 'true');

                var label = i18n('elpxProcessing', 'Processing...');
                btn.innerHTML =
                    '<span class="exe-elpx-progress-bar position-absolute top-0 start-0 h-100 bg-dark bg-opacity-25"' +
                    ' style="width: 0%;" aria-hidden="true"></span>' +
                    '<span class="exe-elpx-progress-label position-relative">' + label + '</span>';
            } else {
                var originalHtml = btn.getAttribute('data-original-html');
                if (originalHtml !== null) {
                    btn.innerHTML = originalHtml;
                    btn.removeAttribute('data-original-html');
                }
                var originalClass = btn.getAttribute('data-original-class');
                if (originalClass !== null) {
                    btn.className = originalClass;
                    btn.removeAttribute('data-original-class');
                } else {
                    PROGRESS_HOST_CLASSES.forEach(function (cls) {
                        btn.classList.remove(cls);
                    });
                }
                btn.style.pointerEvents = '';
                btn.removeAttribute('aria-busy');
            }
        });
    }

    /**
     * Update the in-button progress bar width and label.
     *
     * @param {number} completed - Number of completed items
     * @param {number} total - Total number of items
     */
    function updateProgress(completed, total) {
        if (window.__ELPX_DEBUG__) {
            console.log('[ELPX Download] Progress: ' + completed + '/' + total);
        }

        var percent = 0;
        if (total > 0) {
            percent = Math.max(0, Math.min(100, Math.round((completed / total) * 100)));
        }

        var label = i18n('elpxProcessing', 'Processing...') + ' ' + percent + '%';
        var buttons = getDownloadButtons();
        buttons.forEach(function (btn) {
            var bar = btn.querySelector('.exe-elpx-progress-bar');
            if (bar) {
                bar.style.width = percent + '%';
            }
            var labelEl = btn.querySelector('.exe-elpx-progress-label');
            if (labelEl) {
                labelEl.textContent = label;
            }
            btn.setAttribute('aria-valuenow', String(percent));
        });
    }

    // Expose to global scope
    global.downloadElpx = downloadElpx;

    // Also expose helper functions for testing
    if (typeof module !== 'undefined' && module.exports) {
        module.exports = {
            downloadElpx: downloadElpx,
            downloadElpxViaFolderPicker: downloadElpxViaFolderPicker,
            sanitizeFilename: sanitizeFilename,
            getBasePath: getBasePath,
            isFileProtocol: isFileProtocol,
        };
    }
})(typeof window !== 'undefined' ? window : this);
