/**
 * File Browser for VM Backup File-Level Restore
 * Provides interactive directory navigation and file download from Proxmox Backup Server backups
 */
(function($) {
    'use strict';

    window.FileBrowser = {
        currentPath: '/',
        config: null,
        isLoading: false,
        // Set to true after the first successful directory render. Used to decide whether
        // a listing failure should replace the UI (initial load) or just show a toast and
        // keep the user on the previous directory (mid-session navigation failure).
        hasRendered: false,

        /**
         * Initialize the file browser
         * @param {Object} config Configuration object with listUrl, downloadUrl, translations
         */
        init: function(config) {
            this.config = config;
            this.bindEvents();
            this.loadDirectory('/');
        },

        /**
         * Bind event handlers. init() runs again on every modal open (the partial
         * re-registers its JS each render), so all handlers live in the
         * `.fileBrowser` namespace and are unbound first — otherwise they stack
         * and one click fires N times.
         */
        bindEvents: function() {
            var self = this;

            $(document).off('.fileBrowser');

            // Click on directory row to navigate
            $(document).on('click.fileBrowser', '.file-browser-container .file-item[data-type="d"]', function(e) {
                // Don't navigate if clicking on download button
                if ($(e.target).closest('.download-file-btn').length) {
                    return;
                }
                e.preventDefault();
                var path = $(this).data('path');
                self.loadDirectory(path);
            });

            // Click on breadcrumb to navigate
            $(document).on('click.fileBrowser', '.file-browser-container .path-link', function(e) {
                e.preventDefault();
                var path = $(this).data('path');
                self.loadDirectory(path);
            });

            // Download button click
            $(document).on('click.fileBrowser', '.file-browser-container .download-file-btn', function(e) {
                e.preventDefault();
                e.stopPropagation();
                var path = $(this).data('path');
                var type = $(this).data('type');
                self.downloadFile(path, type);
            });

            // Retry button click
            $(document).on('click.fileBrowser', '#file-browser-retry', function(e) {
                e.preventDefault();
                self.loadDirectory(self.currentPath);
            });
        },

        /**
         * Load directory contents via AJAX
         * @param {string} path Directory path to load
         */
        loadDirectory: function(path) {
            var self = this;

            if (this.isLoading) {
                return;
            }

            // Remember where we were so a listing failure can leave the user there
            // instead of stranding them on a blank error pane.
            var previousPath = this.currentPath;
            this.isLoading = true;
            this.showLoading();

            $.ajax({
                url: this.config.listUrl,
                data: { path: path },
                method: 'GET',
                dataType: 'json',
                timeout: 60000, // 60 second timeout for large directories
                success: function(response) {
                    self.isLoading = false;
                    if (response.success) {
                        self.currentPath = path;
                        self.hasRendered = true;
                        self.renderFiles(response.files, response.path);
                        self.updateBreadcrumb(response.path);
                        if (response.truncated && typeof window.showNotification === 'function') {
                            window.showNotification('warning',
                                self.config.translations.truncated || 'Large directory: showing the first entries only.');
                        }
                    } else {
                        self.handleListError(
                            response.message || self.config.translations.loadFailed || 'Failed to load directory',
                            previousPath
                        );
                    }
                },
                error: function(xhr, status, error) {
                    self.isLoading = false;
                    var message = self.config.translations.loadError || 'Error loading directory';
                    if (status === 'timeout') {
                        message = self.config.translations.timeout
                            || 'Request timed out. The backup server may be busy.';
                    } else if (xhr.responseJSON && xhr.responseJSON.message) {
                        message = xhr.responseJSON.message;
                    } else if (error) {
                        message = error;
                    }
                    self.handleListError(message, previousPath);
                }
            });
        },

        /**
         * Handle a directory-listing failure without trapping the user.
         * If we already have a successful listing rendered, show a toast and keep them on
         * that directory (the table rows are still in the DOM from the last successful
         * renderFiles). On the initial load — when there's nothing to fall back to —
         * use the inline error pane with the retry button.
         */
        handleListError: function(message, previousPath) {
            if (this.hasRendered) {
                if (typeof window.showNotification === 'function') {
                    window.showNotification('error', message);
                }
                this.currentPath = previousPath;
                this.showContent();
            } else {
                this.showError(message);
            }
        },

        /**
         * Render file list in table
         * @param {Array} files Array of file objects
         * @param {string} path Current path
         */
        renderFiles: function(files, path) {
            var self = this;
            var $tbody = $('#file-list');
            $tbody.empty();

            // Add parent directory link if not at root
            if (path !== '/' && path !== '') {
                var parentPath = this.getParentPath(path);
                $tbody.append(this.createFileRow({
                    name: '..',
                    type: 'd',
                    size: null,
                    mtime: null,
                    icon: 'ri-arrow-up-line'
                }, parentPath, true));
            }

            // Check if directory is empty
            if (files.length === 0) {
                if (path === '/' || path === '') {
                    this.showEmpty();
                    return;
                }
                // Show content area but with only parent directory link
                this.showContent();
                return;
            }

            // Render each file/directory
            for (var i = 0; i < files.length; i++) {
                var file = files[i];
                var filePath = this.joinPath(path, file.name);
                $tbody.append(this.createFileRow(file, filePath, false));
            }

            this.showContent();
        },

        /**
         * Create a table row for a file/directory
         * @param {Object} file File object with name, type, size, mtime, icon
         * @param {string} path Full path to the file
         * @param {boolean} isParentDir Whether this is the parent directory link
         * @returns {jQuery} Table row element
         */
        createFileRow: function(file, path, isParentDir) {
            var isDir = file.type === 'd';
            var sizeDisplay = (file.size && file.size > 0) ? this.formatSize(file.size) : '-';
            var dateDisplay = file.mtime ? this.formatDate(file.mtime) : '-';

            var $row = $('<tr>')
                .addClass('file-item')
                .attr('data-type', file.type)
                .attr('data-path', path);

            // Name column with icon
            var iconClass = file.icon + ' file-icon me-2';
            if (isDir) {
                iconClass += ' folder-icon';
            }

            var $nameCol = $('<td>')
                .append($('<i>').addClass(iconClass))
                .append($('<span>').addClass('file-name').text(file.name));

            if (isDir && !isParentDir) {
                $nameCol.append($('<small>').addClass('text-muted ms-2').html('<i class="ri-arrow-right-s-line"></i>'));
            }

            // Size column
            var $sizeCol = $('<td>').addClass('text-end text-muted').text(sizeDisplay);

            // Modified column
            var $dateCol = $('<td>').addClass('text-muted').text(dateDisplay);

            // Actions column
            var $actionsCol = $('<td>').addClass('text-center');

            // Virtual disk images (e.g. .fidx) are PBS metadata, not real files — they can be
            // navigated into, but Proxmox file-restore/download won't stream them. Don't render
            // a download button; show a hint icon instead so the row isn't bare.
            if (!isParentDir) {
                if (file.isVirtual) {
                    $actionsCol.append(
                        $('<span>')
                            .addClass('text-muted small')
                            .attr('title', this.config.translations.virtualDiskHint || 'Open to download partitions or files inside')
                            .append($('<i>').addClass('ri-information-line'))
                    );
                } else {
                    var downloadTitle = isDir
                        ? this.config.translations.download + ' (ZIP)'
                        : this.config.translations.download;

                    var $downloadBtn = $('<button>')
                        .addClass('btn btn-sm btn-outline-primary download-file-btn')
                        .attr('data-path', path)
                        .attr('data-type', file.type)
                        .attr('title', downloadTitle)
                        .append($('<i>').addClass('ri-download-line'));

                    $actionsCol.append($downloadBtn);
                }
            }

            $row.append($nameCol, $sizeCol, $dateCol, $actionsCol);
            return $row;
        },

        /**
         * Update breadcrumb navigation
         * @param {string} path Current path
         */
        updateBreadcrumb: function(path) {
            var $breadcrumb = $('#path-breadcrumb');
            $breadcrumb.empty();

            // Root item
            var $rootItem = $('<li>').addClass('breadcrumb-item');
            if (path === '/' || path === '') {
                $rootItem.addClass('active')
                    .html('<i class="ri-hard-drive-2-line"></i> ' + this.config.translations.root);
            } else {
                $rootItem.append(
                    $('<a>')
                        .attr('href', '#')
                        .addClass('path-link text-decoration-none')
                        .attr('data-path', '/')
                        .html('<i class="ri-hard-drive-2-line"></i> ' + this.config.translations.root)
                );
            }
            $breadcrumb.append($rootItem);

            if (path === '/' || path === '') {
                return;
            }

            // Split path and create breadcrumb items
            var parts = path.split('/').filter(function(p) { return p !== ''; });
            var currentPath = '';

            for (var i = 0; i < parts.length; i++) {
                currentPath += '/' + parts[i];
                var $item = $('<li>').addClass('breadcrumb-item');

                if (i === parts.length - 1) {
                    // Last item is active (current directory)
                    $item.addClass('active').text(parts[i]);
                } else {
                    // Intermediate items are links
                    $item.append(
                        $('<a>')
                            .attr('href', '#')
                            .addClass('path-link text-decoration-none')
                            .attr('data-path', currentPath)
                            .text(parts[i])
                    );
                }

                $breadcrumb.append($item);
            }
        },

        /**
         * Trigger a file download. A jQuery AJAX preflight runs the backend's
         * auth/path/storage validation first so failures surface as an inline
         * toast; the actual transfer then goes through the browser's native
         * download engine via a hidden iframe, so nothing is buffered in tab
         * memory regardless of archive size. (The previous fetch()+blob approach
         * held the entire archive in tab memory and saved whatever the server
         * returned — including a login page after session expiry.)
         * @param {string} path File path to download
         * @param {string} type File type ('d' for directory, 'f' for file)
         */
        downloadFile: function(path, type) {
            var self = this;
            var url = this.config.downloadUrl
                + '&path=' + encodeURIComponent(path)
                + '&type=' + encodeURIComponent(type);

            var notify = function(level, message) {
                if (typeof window.showNotification === 'function') {
                    window.showNotification(level, message);
                }
            };

            if (type === 'd') {
                notify('info', this.config.translations.preparingDownloadZip
                    || 'Preparing download... Directories are compressed as ZIP.');
            } else {
                notify('info', this.config.translations.preparingDownload
                    || 'Preparing download...');
            }

            $.ajax({
                url: url + '&precheck=1',
                method: 'GET',
                dataType: 'json'
            }).done(function(response) {
                if (response && response.success) {
                    self.triggerNativeDownload(url, notify);
                } else {
                    notify('error', (response && response.message)
                        || self.config.translations.downloadFailed || 'Download failed');
                }
            }).fail(function(xhr) {
                var message = (xhr.responseJSON && xhr.responseJSON.message)
                    || ((self.config.translations.downloadFailed || 'Download failed')
                        + ' (HTTP ' + xhr.status + ')');
                notify('error', message);
            });
        },

        /**
         * Start the real download in a hidden iframe. An attachment response is
         * handed to the browser's download manager and never fires the iframe's
         * load event; a rendered document (an error) does fire it, so we read the
         * JSON error body out of the iframe (json_errors=1 makes the backend
         * answer JSON instead of an HTML error page) and show it as a toast.
         * A hidden iframe is used instead of window.location so an error response
         * can never replace the file-browser page.
         * @param {string} url Download URL (without precheck)
         * @param {function} notify Notification callback
         */
        triggerNativeDownload: function(url, notify) {
            var iframe = document.createElement('iframe');
            iframe.style.display = 'none';
            iframe.src = url + '&json_errors=1';

            var self = this;
            iframe.addEventListener('load', function() {
                var message = self.config.translations.downloadFailed || 'Download failed';
                try {
                    var text = iframe.contentDocument.body
                        ? iframe.contentDocument.body.textContent
                        : '';
                    var data = JSON.parse(text);
                    if (data && data.message) {
                        message = data.message;
                    }
                } catch (e) {
                    // Non-JSON document (e.g. a login page after session expiry) —
                    // keep the generic message.
                }
                notify('error', message);
                if (iframe.parentNode) {
                    iframe.parentNode.removeChild(iframe);
                }
            });

            document.body.appendChild(iframe);

            // Clean up long after any plausible time-to-first-byte. Removing the
            // iframe is safe once the download is in the browser's download manager;
            // removing it too early would cancel a still-starting request.
            setTimeout(function() {
                if (iframe.parentNode) {
                    iframe.parentNode.removeChild(iframe);
                }
            }, 600000);
        },

        /**
         * Get parent directory path
         * @param {string} path Current path
         * @returns {string} Parent path
         */
        getParentPath: function(path) {
            var parts = path.split('/').filter(function(p) { return p !== ''; });
            parts.pop();
            return parts.length > 0 ? '/' + parts.join('/') : '/';
        },

        /**
         * Join base path with filename
         * @param {string} base Base path
         * @param {string} name File/directory name
         * @returns {string} Combined path
         */
        joinPath: function(base, name) {
            if (base === '/' || base === '') {
                return '/' + name;
            }
            return base.replace(/\/$/, '') + '/' + name;
        },

        /**
         * Format file size for display
         * @param {number} bytes Size in bytes
         * @returns {string} Formatted size string
         */
        formatSize: function(bytes) {
            if (bytes === 0) return '0 B';
            var k = 1024;
            var sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
            var i = Math.floor(Math.log(bytes) / Math.log(k));
            return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
        },

        /**
         * Format timestamp for display
         * @param {number} timestamp Unix timestamp
         * @returns {string} Formatted date string
         */
        formatDate: function(timestamp) {
            if (!timestamp) return '-';
            var date = new Date(timestamp * 1000);
            return date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
        },

        /**
         * Show loading state
         */
        showLoading: function() {
            $('#file-browser-loading').show();
            $('#file-browser-content').hide();
            $('#file-browser-error').addClass('d-none');
            $('#file-browser-empty').addClass('d-none');
        },

        /**
         * Show content (file list)
         */
        showContent: function() {
            $('#file-browser-loading').hide();
            $('#file-browser-content').show();
            $('#file-browser-error').addClass('d-none');
            $('#file-browser-empty').addClass('d-none');
        },

        /**
         * Show error message
         * @param {string} message Error message to display
         */
        showError: function(message) {
            $('#file-browser-loading').hide();
            $('#file-browser-content').hide();
            $('#file-browser-empty').addClass('d-none');
            $('#file-browser-error-message').text(message);
            $('#file-browser-error').removeClass('d-none');
        },

        /**
         * Show empty directory state
         */
        showEmpty: function() {
            $('#file-browser-loading').hide();
            $('#file-browser-content').hide();
            $('#file-browser-error').addClass('d-none');
            $('#file-browser-empty').removeClass('d-none');
        }
    };

})(jQuery);
