/**
 * Job Manager for MPV.Rocks Installer
 * Manages job state, SSE connections, and output viewer
 * 
 * Features:
 * - SSE connection for real-time job updates
 * - Job output viewer modal
 * - Progress tracking
 * - Toast notifications on completion
 */

(function() {
    'use strict';

    // Job Manager class
    class JobManager {
        constructor() {
            this.activeJobs = new Map();
            this.eventSource = null;
            this.reconnectAttempts = 0;
            this.maxReconnectAttempts = 5;
            this.reconnectDelay = 1000;
            this.jobCallbacks = new Map(); // Callbacks for specific jobs
            this.globalCallbacks = []; // Global callbacks for all job events
            
            // Connect to SSE stream
            this.connect();
        }

        /**
         * Connect to the job SSE stream
         */
        connect() {
            if (this.eventSource) {
                this.eventSource.close();
            }

            const url = '/api/jobs/stream';
            console.log('[JobManager] Connecting to SSE stream:', url);

            this.eventSource = new EventSource(url);

            // Connection opened
            this.eventSource.addEventListener('open', () => {
                console.log('[JobManager] SSE connection established');
                this.reconnectAttempts = 0;
            });

            // Job created event
            this.eventSource.addEventListener('created', (e) => {
                try {
                    const data = JSON.parse(e.data);
                    console.log('[JobManager] Job created:', data);
                    this.handleJobCreated(data);
                } catch (err) {
                    console.error('[JobManager] Failed to parse created event:', err);
                }
            });

            // Progress event
            this.eventSource.addEventListener('progress', (e) => {
                try {
                    const data = JSON.parse(e.data);
                    this.handleJobProgress(data);
                } catch (err) {
                    console.error('[JobManager] Failed to parse progress event:', err);
                }
            });

            // Output event
            this.eventSource.addEventListener('output', (e) => {
                try {
                    const data = JSON.parse(e.data);
                    this.handleJobOutput(data);
                } catch (err) {
                    console.error('[JobManager] Failed to parse output event:', err);
                }
            });

            // Status event
            this.eventSource.addEventListener('status', (e) => {
                try {
                    const data = JSON.parse(e.data);
                    this.handleJobStatus(data);
                } catch (err) {
                    console.error('[JobManager] Failed to parse status event:', err);
                }
            });

            // Complete event
            this.eventSource.addEventListener('complete', (e) => {
                try {
                    const data = JSON.parse(e.data);
                    this.handleJobComplete(data);
                } catch (err) {
                    console.error('[JobManager] Failed to parse complete event:', err);
                }
            });

            // Connection error
            this.eventSource.onerror = (err) => {
                console.error('[JobManager] SSE connection error:', err);
                
                if (this.eventSource.readyState === EventSource.CLOSED) {
                    this.handleReconnect();
                }
            };
        }

        /**
         * Handle reconnection with exponential backoff
         */
        handleReconnect() {
            if (this.reconnectAttempts >= this.maxReconnectAttempts) {
                console.error('[JobManager] Max reconnect attempts reached');
                return;
            }

            this.reconnectAttempts++;
            const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
            
            console.log(`[JobManager] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
            
            setTimeout(() => {
                this.connect();
            }, delay);
        }

        /**
         * Handle job created event
         */
        handleJobCreated(data) {
            const job = {
                id: data.job_id,
                type: data.type,
                methodId: data.methodId,
                appName: data.appName,
                status: 'pending',
                progress: 0,
                message: 'Job created',
                output: []
            };

            this.activeJobs.set(job.id, job);
            this.updateActiveTaskCount();
            this.notifyCallbacks('created', job);

            // Show toast notification that job started
            if (window.showJobStartedToast) {
                window.showJobStartedToast(job);
            }
        }

        /**
         * Handle job progress event
         */
        handleJobProgress(data) {
            const job = this.activeJobs.get(data.job_id);
            if (!job) {
                console.warn('[JobManager] Progress event for unknown job:', data.job_id);
                return;
            }

            job.progress = data.progress || 0;
            job.message = data.message || '';
            job.status = 'running';

            // Update UI elements
            this.updateJobProgressUI(job);
            this.notifyCallbacks('progress', job);
        }

        /**
         * Handle job output event
         */
        handleJobOutput(data) {
            const job = this.activeJobs.get(data.job_id);
            if (!job) {
                console.warn('[JobManager] Output event for unknown job:', data.job_id);
                return;
            }

            const outputLine = data.output || data.line || '';
            job.output.push(outputLine);
            job.status = 'running';

            // Update output viewer if open
            this.updateOutputViewer(job.id, outputLine);
            this.notifyCallbacks('output', job, outputLine);
        }

        /**
         * Handle job status event
         */
        handleJobStatus(data) {
            const job = this.activeJobs.get(data.job_id);
            if (!job) {
                console.warn('[JobManager] Status event for unknown job:', data.job_id);
                // If it's an error status, show toast anyway
                if (data.status === 'error') {
                    const errorJob = {
                        id: data.job_id,
                        status: 'error',
                        appName: data.appName || 'Application',
                        type: data.type || 'install',
                        methodId: data.methodId || '',
                        error: data.error || 'Unknown error'
                    };
                    if (window.showJobToast) {
                        window.showJobToast(errorJob);
                    }
                    // Reset install button if we have methodId
                    if (data.methodId) {
                        this.resetInstallButton(data.methodId);
                    }
                }
                return;
            }

            job.status = data.status;
            if (data.error) {
                job.error = data.error;
            }
            if (data.error_details) {
                job.errorDetails = data.error_details;
            }
            if (data.appName) {
                job.appName = data.appName;
            }
            if (data.type) {
                job.type = data.type;
            }
            if (data.methodId) {
                job.methodId = data.methodId;
            }

            this.notifyCallbacks('status', job);

            // If status is error, show toast and remove from active
            if (data.status === 'error') {
                this.showCompletionToast(job);
                setTimeout(() => {
                    this.activeJobs.delete(job.id);
                    this.updateActiveTaskCount();
                }, 1000);
            }
        }

        /**
         * Handle job complete event
         */
        async handleJobComplete(data) {
            const job = this.activeJobs.get(data.job_id);
            if (!job) {
                console.warn('[JobManager] Complete event for unknown job:', data.job_id);
                // Create a minimal job object for the toast
                const completedJob = {
                    id: data.job_id,
                    status: data.success ? 'complete' : 'error',
                    appName: data.appName || 'Application',
                    type: data.type || 'install',
                    methodId: data.methodId || '',
                    message: data.message || ''
                };
                this.showCompletionToast(completedJob);
                // Reset install button if we have methodId
                if (data.methodId) {
                    this.resetInstallButton(data.methodId);
                }
                return;
            }

            job.status = data.success ? 'complete' : 'error';
            job.progress = data.progress || 100;
            job.message = data.message || '';
            if (data.appName) {
                job.appName = data.appName;
            }
            if (data.type) {
                job.type = data.type;
            }
            if (data.methodId) {
                job.methodId = data.methodId;
            }

            // Remove from active jobs after a delay
            setTimeout(() => {
                this.activeJobs.delete(job.id);
                this.updateActiveTaskCount();
            }, 1000);

            // Show toast notification
            this.showCompletionToast(job);
            this.notifyCallbacks('complete', job);

            // Refresh installed apps list if on apps page (async, don't wait)
            this.refreshAppsListIfNeeded(job);
        }

        /**
         * Show completion toast notification
         */
        showCompletionToast(job) {
            if (window.showJobToast) {
                window.showJobToast(job);
            }
        }

        /**
         * Refresh apps list if install/update/uninstall/UI change completed successfully
         * Uses retry mechanism with exponential backoff for reliability
         */
        async refreshAppsListIfNeeded(job, retries = 3) {
            if (job.status !== 'complete') return;
            if (!['install', 'update', 'uninstall', 'ui-change'].includes(job.type)) return;

            for (let i = 0; i < retries; i++) {
                try {
                    await this.refreshAppsList();
                    console.log('[JobManager] Apps list refreshed successfully');
                    return; // Success
                } catch (e) {
                    console.warn(`[JobManager] Failed to refresh apps list (attempt ${i + 1}/${retries}):`, e);
                    if (i < retries - 1) {
                        await new Promise(r => setTimeout(r, 500 * (i + 1))); // Exponential backoff
                    }
                }
            }
            console.error('[JobManager] Failed to refresh apps list after', retries, 'attempts');
        }

        /**
         * Perform the actual apps list refresh using HTMX
         * Detects whether we're on Dashboard or Apps page and uses appropriate targets.
         * @returns {Promise} Resolves when refresh completes
         */
        async refreshAppsList() {
            if (typeof htmx === 'undefined') {
                return Promise.reject(new Error('HTMX not available'));
            }

            // Check which page we're on by detecting unique elements
            const dashboardUpdatesList = document.getElementById('available-updates-list');
            const appsInstalledList = document.getElementById('installed-apps-list');

            // Dashboard page: refresh the available updates list
            if (dashboardUpdatesList && !appsInstalledList) {
                return new Promise((resolve, reject) => {
                    htmx.ajax('POST', '/api/apps/check-updates', {
                        target: '#available-updates-list',
                        swap: 'innerHTML'
                    }).then(resolve).catch(reject);
                });
            }

            // Apps page: refresh installed apps (endpoint also does OOB swap for #available-updates)
            if (appsInstalledList) {
                return new Promise((resolve, reject) => {
                    htmx.ajax('POST', '/api/apps/refresh-installed', {
                        target: '#installed-apps-list',
                        swap: 'innerHTML'
                    }).then(resolve).catch(reject);
                });
            }

            // Neither element found - nothing to refresh
            return Promise.resolve();
        }

        /**
         * Update job progress UI elements
         */
        updateJobProgressUI(job) {
            // Update progress bar in app card
            const progressBar = document.querySelector(`[data-job-progress="${job.methodId}"]`);
            if (progressBar) {
                progressBar.style.width = `${job.progress}%`;
            }

            // Update progress text
            const progressText = document.querySelector(`[data-job-progress-text="${job.methodId}"]`);
            if (progressText) {
                progressText.textContent = `${job.message} (${job.progress}%)`;
            }
        }

        /**
         * Update output viewer modal
         */
        updateOutputViewer(jobId, outputLine) {
            const outputContainer = document.querySelector(`[data-job-output="${jobId}"]`);
            if (outputContainer) {
                const line = document.createElement('div');
                line.className = 'text-gray-300 font-mono text-sm';
                line.textContent = outputLine;
                outputContainer.appendChild(line);
                outputContainer.scrollTop = outputContainer.scrollHeight;
            }
        }

        /**
         * Update active task count in sidebar
         */
        updateActiveTaskCount() {
            const countBadge = document.getElementById('active-tasks-count');
            if (countBadge) {
                const count = this.activeJobs.size;
                countBadge.textContent = count;
                if (count > 0) {
                    countBadge.classList.remove('hidden');
                } else {
                    countBadge.classList.add('hidden');
                }
            }
        }

        /**
         * Register a callback for job events
         */
        onJob(event, callback) {
            this.globalCallbacks.push({ event, callback });
        }

        /**
         * Register a callback for a specific job
         */
        onJobComplete(jobId, callback) {
            if (!jobId) {
                console.warn('[JobManager] onJobComplete called with null/undefined jobId');
                return;
            }
            this.jobCallbacks.set(jobId, callback);
        }

        /**
         * Notify all registered callbacks
         */
        notifyCallbacks(event, job, ...args) {
            // Global callbacks
            this.globalCallbacks.forEach(cb => {
                if (cb.event === event || cb.event === '*') {
                    cb.callback(job, ...args);
                }
            });

            // Job-specific callbacks
            if (job && job.id) {
                const jobCallback = this.jobCallbacks.get(job.id);
                if (jobCallback && (event === 'complete' || event === 'status')) {
                    jobCallback(job);
                    this.jobCallbacks.delete(job.id);
                }
            }

            // Reset any install buttons associated with this job
            if ((event === 'complete' || (event === 'status' && job && job.status === 'error')) && job && job.methodId) {
                this.resetInstallButton(job.methodId);
            }
        }

        /**
         * Reset an install button to its original state
         */
        resetInstallButton(methodId) {
            const button = document.querySelector(`[data-install][data-method-id="${methodId}"]`);
            if (button && button.disabled) {
                const isUpdate = button.dataset.isUpdate === 'true';
                const originalText = button.dataset.originalText;
                
                button.disabled = false;
                button.innerHTML = originalText || `<i class="bx bx-archive-arrow-down"></i> ${isUpdate ? 'Install Update' : 'Install'}`;
                delete button.dataset.jobId;
                delete button.dataset.originalText;
            }

            // Hide progress indicator
            const progressEl = document.getElementById(`app-${methodId}-progress`);
            if (progressEl) {
                progressEl.classList.add('hidden');
            }
        }

        /**
         * Get active jobs
         */
        getActiveJobs() {
            return Array.from(this.activeJobs.values());
        }

        /**
         * Disconnect SSE
         */
        disconnect() {
            if (this.eventSource) {
                this.eventSource.close();
                this.eventSource = null;
            }
        }
    }

    // Global job manager instance
    let jobManager = null;

    /**
     * Initialize job manager
     */
    function initJobManager() {
        if (!jobManager) {
            jobManager = new JobManager();
            window.jobManager = jobManager;
        }
        return jobManager;
    }

    /**
     * Show job output in a modal
     * @param {string} jobId - The job ID to show output for
     */
    window.showJobOutput = async function(jobId) {
        if (!jobId) {
            console.error('[JobManager] No job ID provided');
            return;
        }

        // Fetch job details
        try {
            const response = await fetch(`/api/jobs/${jobId}`);
            if (!response.ok) {
                throw new Error(`HTTP error! status: ${response.status}`);
            }

            const job = await response.json();
            displayJobOutputModal(job);
        } catch (error) {
            console.error('[JobManager] Error fetching job details:', error);
            if (window.showToast) {
                window.showToast('Failed to load job details', 'error');
            }
        }
    };

    /**
     * Display the job output modal
     * @param {Object} job - The job object
     */
    function displayJobOutputModal(job) {
        // Remove existing modal if present
        const existingModal = document.getElementById('job-output-modal');
        if (existingModal) {
            existingModal.remove();
        }

        const modalHTML = buildJobOutputModalHTML(job);

        // Add modal to body
        document.body.insertAdjacentHTML('beforeend', modalHTML);

        const modal = document.getElementById('job-output-modal');

        // Focus trap and escape key
        modal.addEventListener('keydown', (e) => {
            if (e.key === 'Escape') {
                closeJobOutputModal();
            }
        });

        // Store previous active element
        modal.dataset.previousFocus = document.activeElement.id || '';
        modal.dataset.jobId = job.id;

        // Focus the close button
        const closeBtn = modal.querySelector('button');
        if (closeBtn) closeBtn.focus();

        // If job is still running, register for real-time updates
        if (job.status === 'running' || job.status === 'pending') {
            registerModalCallbacks(job.id);
        }
    }

    /**
     * Build the job output modal HTML
     * @param {Object} job - The job object
     * @returns {string} The modal HTML
     */
    function buildJobOutputModalHTML(job) {
        // Determine status styling
        const isSuccess = job.status === 'complete';
        const isError = job.status === 'error';
        const isRunning = job.status === 'running' || job.status === 'pending';
        const statusClass = isSuccess ? 'text-green-400' : isError ? 'text-red-400' : 'text-blue-400';
        const statusIcon = isSuccess ? 'bx-check-circle' : isError ? 'bx-x-circle' : 'bx-loader-alt bx-spin';
        const statusText = isSuccess ? 'Completed' : isError ? 'Failed' : 'Running';
        const progress = job.progress || 0;

        return `
            <div id="job-output-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50" 
                 role="dialog" aria-modal="true" aria-labelledby="job-output-title">
                <div class="bg-gray-800 rounded-lg shadow-2xl max-w-4xl w-full mx-4 max-h-[80vh] flex flex-col">
                    <!-- Header -->
                    <div class="p-6 border-b border-gray-700 flex items-center justify-between">
                        <div class="flex items-center gap-3">
                            <i id="job-modal-icon" class="bx ${statusIcon} text-2xl ${statusClass}"></i>
                            <div>
                                <h3 id="job-output-title" class="text-xl font-semibold text-white">
                                    ${escapeHtml(job.appName || 'Job')} - ${job.type || 'Task'}
                                </h3>
                                <p class="text-sm text-gray-400">
                                    Status: <span id="job-modal-status" class="${statusClass} font-medium">${statusText}</span>
                                    <span id="job-modal-progress-text">${progress !== undefined && isRunning ? ` (${progress}%)` : ''}</span>
                                </p>
                            </div>
                        </div>
                        <button onclick="closeJobOutputModal()" class="text-gray-400 hover:text-white transition focus:outline-none focus:ring-2 focus:ring-purple-500 rounded" aria-label="Close modal">
                            <i class="bx bx-x text-2xl"></i>
                        </button>
                    </div>

                    <!-- Progress bar (if running) -->
                    <div id="job-modal-progress-section" class="px-6 py-3 border-b border-gray-700 ${isRunning ? '' : 'hidden'}">
                        <div class="flex items-center justify-between mb-1">
                            <span id="job-modal-message" class="text-sm text-gray-400">${escapeHtml(job.message || 'Processing...')}</span>
                            <span id="job-modal-progress-pct" class="text-sm text-gray-400">${progress}%</span>
                        </div>
                        <div class="w-full bg-gray-700 rounded-full h-2">
                            <div id="job-modal-progress-bar" class="bg-purple-500 h-2 rounded-full transition-all duration-300" style="width: ${progress}%"></div>
                        </div>
                    </div>

                    <!-- Output -->
                    <div class="flex-1 overflow-auto p-6">
                        <div class="bg-gray-900 rounded-lg p-4 font-mono text-sm max-h-[50vh] overflow-auto"
                             data-job-output="${job.id}">
                            ${job.output && job.output.length > 0 
                                ? job.output.map(line => `<div class="text-gray-300">${escapeHtml(line)}</div>`).join('') 
                                : '<div class="text-gray-500">No output available</div>'}
                        </div>
                    </div>

                    <!-- Error details (if error) -->
                    <div id="job-modal-error-section" class="px-6 py-4 border-t border-gray-700 bg-red-900 bg-opacity-20 ${isError ? '' : 'hidden'}">
                        <div class="flex items-start gap-3">
                            <i class="bx bx-error text-red-400 text-xl mt-0.5"></i>
                            <div>
                                <p id="job-modal-error" class="text-red-400 font-medium">${isError && job.error ? 'Error: ' + escapeHtml(job.error) : ''}</p>
                                <p id="job-modal-error-details" class="text-gray-400 text-sm mt-1">${isError && job.errorDetails ? escapeHtml(job.errorDetails) : ''}</p>
                            </div>
                        </div>
                    </div>

                    <!-- Footer -->
                    <div class="p-6 border-t border-gray-700 flex justify-end gap-3">
                        <button id="job-modal-cancel-btn" onclick="cancelJob('${job.id}')" 
                                class="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded transition flex items-center gap-2 ${isRunning ? '' : 'hidden'}">
                            <i class="bx bx-x"></i>
                            Cancel Job
                        </button>
                        <button onclick="closeJobOutputModal()" class="bg-gray-700 hover:bg-gray-600 text-white px-4 py-2 rounded transition flex items-center gap-2">
                            <i class="bx bx-x"></i>
                            Close
                        </button>
                        <button id="job-modal-refresh-btn" onclick="closeJobOutputModal(); window.location.reload();" 
                                class="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded transition flex items-center gap-2 ${isSuccess ? '' : 'hidden'}">
                            <i class="bx bx-refresh"></i>
                            Refresh Page
                        </button>
                    </div>
                </div>
            </div>
        `;
    }

    /**
     * Register callbacks for real-time modal updates
     * @param {string} jobId - The job ID to track
     */
    function registerModalCallbacks(jobId) {
        if (!jobManager) return;

        // Store callback objects so we can unregister later
        window._jobModalCallbacks = [];

        // Register callbacks for each event type
        const events = ['progress', 'output', 'status', 'complete'];
        events.forEach(eventType => {
            const callbackObj = {
                event: eventType,
                callback: (job, ...args) => {
                    // Only process if this is the job we're tracking
                    if (job.id !== jobId) return;

                    switch (eventType) {
                        case 'progress':
                            updateModalProgress(job);
                            break;
                        case 'output':
                            // Output is already handled by updateOutputViewer() in handleJobOutput
                            // No need to call updateModalOutput here (would cause duplicates)
                            break;
                        case 'status':
                            updateModalStatus(job);
                            if (job.status === 'error') {
                                unregisterModalCallbacks();
                            }
                            break;
                        case 'complete':
                            updateModalStatus(job);
                            unregisterModalCallbacks();
                            break;
                    }
                }
            };
            jobManager.globalCallbacks.push(callbackObj);
            window._jobModalCallbacks.push(callbackObj);
        });

        window._jobModalJobId = jobId;
    }

    /**
     * Unregister modal callbacks
     */
    function unregisterModalCallbacks() {
        if (!jobManager || !window._jobModalCallbacks) return;

        // Remove each registered callback
        window._jobModalCallbacks.forEach(cb => {
            const idx = jobManager.globalCallbacks.indexOf(cb);
            if (idx > -1) {
                jobManager.globalCallbacks.splice(idx, 1);
            }
        });

        window._jobModalCallbacks = null;
        window._jobModalJobId = null;
    }

    /**
     * Update modal progress bar and message
     * @param {Object} job - The job object
     */
    function updateModalProgress(job) {
        const progressBar = document.getElementById('job-modal-progress-bar');
        const progressPct = document.getElementById('job-modal-progress-pct');
        const progressText = document.getElementById('job-modal-progress-text');
        const message = document.getElementById('job-modal-message');

        if (progressBar) {
            progressBar.style.width = `${job.progress}%`;
        }
        if (progressPct) {
            progressPct.textContent = `${job.progress}%`;
        }
        if (progressText) {
            progressText.textContent = ` (${job.progress}%)`;
        }
        if (message) {
            message.textContent = job.message || 'Processing...';
        }
    }

    /**
     * Append output line to modal
     * @param {string} jobId - The job ID
     * @param {string} line - The output line
     */
    function updateModalOutput(jobId, line) {
        const outputContainer = document.querySelector(`[data-job-output="${jobId}"]`);
        if (outputContainer) {
            const lineEl = document.createElement('div');
            lineEl.className = 'text-gray-300 font-mono text-sm';
            lineEl.textContent = line;
            outputContainer.appendChild(lineEl);
            outputContainer.scrollTop = outputContainer.scrollHeight;
        }
    }

    /**
     * Update modal status (completed, error, etc.)
     * @param {Object} job - The job object
     */
    function updateModalStatus(job) {
        const modal = document.getElementById('job-output-modal');
        if (!modal || modal.dataset.jobId !== job.id) return;

        const isSuccess = job.status === 'complete';
        const isError = job.status === 'error';
        const isRunning = job.status === 'running' || job.status === 'pending';

        // Update icon
        const icon = document.getElementById('job-modal-icon');
        if (icon) {
            icon.className = 'bx text-2xl ' + (isSuccess ? 'bx-check-circle text-green-400' : isError ? 'bx-x-circle text-red-400' : 'bx-loader-alt bx-spin text-blue-400');
        }

        // Update status text
        const status = document.getElementById('job-modal-status');
        if (status) {
            status.textContent = isSuccess ? 'Completed' : isError ? 'Failed' : 'Running';
            status.className = (isSuccess ? 'text-green-400' : isError ? 'text-red-400' : 'text-blue-400') + ' font-medium';
        }

        // Hide progress section for completed/error jobs
        const progressSection = document.getElementById('job-modal-progress-section');
        if (progressSection) {
            progressSection.classList.toggle('hidden', !isRunning);
        }

        // Update progress to 100% on success
        if (isSuccess) {
            updateModalProgress({ ...job, progress: 100 });
        }

        // Show error section
        const errorSection = document.getElementById('job-modal-error-section');
        const errorText = document.getElementById('job-modal-error');
        const errorDetails = document.getElementById('job-modal-error-details');
        if (isError && job.error) {
            if (errorSection) errorSection.classList.remove('hidden');
            if (errorText) errorText.textContent = 'Error: ' + job.error;
            if (errorDetails) errorDetails.textContent = job.errorDetails || '';
        }

        // Toggle buttons
        const cancelBtn = document.getElementById('job-modal-cancel-btn');
        const refreshBtn = document.getElementById('job-modal-refresh-btn');
        if (cancelBtn) cancelBtn.classList.toggle('hidden', !isRunning);
        if (refreshBtn) refreshBtn.classList.toggle('hidden', !isSuccess);
    }

    /**
     * Close the job output modal
     */
    window.closeJobOutputModal = function() {
        // Unregister callbacks first
        unregisterModalCallbacks();

        const modal = document.getElementById('job-output-modal');
        if (modal) {
            // Restore focus
            const previousFocusId = modal.dataset.previousFocus;
            if (previousFocusId) {
                const previousElement = document.getElementById(previousFocusId);
                if (previousElement) previousElement.focus();
            }
            modal.remove();
        }
    };

    /**
     * Cancel a running job
     */
    window.cancelJob = async function(jobId) {
        if (!jobId) return;

        try {
            const response = await fetch(`/api/jobs/${jobId}`, {
                method: 'DELETE'
            });

            if (!response.ok) {
                throw new Error(`HTTP error! status: ${response.status}`);
            }

            if (window.showToast) {
                window.showToast('Job cancelled', 'info');
            }

            closeJobOutputModal();
        } catch (error) {
            console.error('[JobManager] Error cancelling job:', error);
            if (window.showToast) {
                window.showToast('Failed to cancel job', 'error');
            }
        }
    };

    /**
     * Start a job-based installation
     * @param {string} methodId - The installation method ID
     * @param {boolean} isUpdate - Whether this is an update
     * @returns {Promise<Object>} The job object
     */
    window.startInstallJob = async function(methodId, isUpdate = false) {
        const endpoint = isUpdate ? '/api/apps/update/job' : '/api/install/job';
        
        try {
            const response = await fetch(endpoint, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({
                    method_id: methodId,
                    ui_type: window.getSelectedUIType ? window.getSelectedUIType() : 'modernz'
                })
            });

            if (!response.ok) {
                const errorData = await response.json().catch(() => ({}));
                
                // Handle 409 Conflict - job already running
                if (response.status === 409) {
                    const error = new Error(errorData.error || 'An installation is already in progress');
                    error.status = 409;
                    error.jobId = errorData.job_id;
                    throw error;
                }
                
                throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
            }

            const job = await response.json();
            console.log('[JobManager] Started job:', job);

            // Show progress indicator on the app card
            showAppCardProgress(methodId, job);

            return job;
        } catch (error) {
            console.error('[JobManager] Error starting install job:', error);
            
            // Don't show toast for 409 - let the caller handle it
            if (error.status !== 409 && window.showToast) {
                window.showToast(error.message || 'Failed to start installation', 'error');
            }
            throw error;
        }
    };

    /**
     * Show progress indicator on an app card
     */
    function showAppCardProgress(methodId, job) {
        // Find the app card
        const appCard = document.querySelector(`[data-method-id="${methodId}"]`)?.closest('.bg-gray-700');
        if (!appCard) return;

        // Check if progress container exists
        let progressContainer = appCard.querySelector(`#app-${methodId}-progress`);
        
        if (!progressContainer) {
            // Create progress container
            progressContainer = document.createElement('div');
            progressContainer.id = `app-${methodId}-progress`;
            progressContainer.className = 'mt-2';
            progressContainer.innerHTML = `
                <div class="flex items-center justify-between mb-1">
                    <span class="text-xs text-gray-400" data-job-progress-text="${methodId}">Installing... 0%</span>
                    <button onclick="window.showJobOutput('${job.id}')" class="text-xs text-purple-400 hover:text-purple-300">View Details</button>
                </div>
                <div class="w-full bg-gray-600 rounded-full h-2">
                    <div class="bg-purple-500 h-2 rounded-full transition-all duration-300" style="width: 0%" data-job-progress="${methodId}"></div>
                </div>
            `;
            
            // Insert after the main content
            const mainContent = appCard.querySelector('.flex.items-center.justify-between');
            if (mainContent) {
                mainContent.after(progressContainer);
            } else {
                appCard.appendChild(progressContainer);
            }
        }

        // Disable the install button
        const installBtn = appCard.querySelector('[data-install]');
        if (installBtn) {
            installBtn.disabled = true;
            installBtn.dataset.originalText = installBtn.innerHTML;
            installBtn.innerHTML = '<i class="bx bx-loader-alt bx-spin"></i> Installing...';
        }

        // Register callback to update UI
        if (jobManager) {
            jobManager.onJobComplete(job.id, (completedJob) => {
                // Re-enable button
                if (installBtn) {
                    installBtn.disabled = false;
                    installBtn.innerHTML = installBtn.dataset.originalText || '<i class="bx bx-archive-arrow-down"></i> Install';
                }

                // Update progress text
                const progressText = progressContainer.querySelector(`[data-job-progress-text="${methodId}"]`);
                if (progressText) {
                    if (completedJob.status === 'complete') {
                        progressText.textContent = 'Installation complete!';
                        progressContainer.querySelector('.bg-purple-500').classList.remove('bg-purple-500');
                        progressContainer.querySelector('.h-2').classList.add('bg-green-500');
                    } else if (completedJob.status === 'error') {
                        progressText.textContent = 'Installation failed';
                        progressContainer.querySelector('.bg-purple-500').classList.remove('bg-purple-500');
                        progressContainer.querySelector('.h-2').classList.add('bg-red-500');
                    }
                }
            });
        }
    }

    /**
     * Escape HTML to prevent XSS
     */
    function escapeHtml(text) {
        if (!text) return '';
        const div = document.createElement('div');
        div.textContent = text;
        return div.innerHTML;
    }

    // Initialize on DOM ready
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initJobManager);
    } else {
        initJobManager();
    }

    // Public API
    window.JobManager = JobManager;
    window.initJobManager = initJobManager;

})();
