/**
 * Tasks Manager - Tasks Page UI Management
 * Handles the tasks page display, filtering, and UI interactions
 * Uses individual task operations from the task/ folder
 */
class TasksManager {
  constructor() {
    this.tasks = [];
    this.currentCategory = 'all';
    this.highlightedTaskId = null;
    // When true, the open row tracks whichever task is currently running
    // (queue handoffs, setup installs). Cleared the moment the user manually
    // toggles a row or switches category — their selection then wins.
    this.followRunning = false;
    // Multi-select: ids of tasks the user has ticked. When non-empty the
    // "Clear All" button morphs into "Delete Selected (N)". Both paths
    // share _showClearAllModal — same UX, different filter.
    this.selectedTaskIds = new Set();
    this.taskManager = new TaskManager();
    this.init();
    
    // Start global live log updater
    this.startGlobalLiveLogUpdater();
    
    this.refreshInterval = null;
    this.activeLogStreams = new Map(); // Track active log streams
    this.autoRefreshIntervals = new Map(); // Track auto-refresh intervals for running tasks
    
    // Initialize modular components for individual task operations (only if available)
    try {
      this.commands = new TaskCommands();
      this.actions = new TaskActions(this, this.commands);
      this.router = new TaskRouter(this, this.actions);
      this.taskManager = new TaskManager();
    } catch (error) {
      console.warn('⚠️ Task system components not yet loaded, will initialize later:', error.message);
      this.commands = null;
      this.actions = null;
      this.router = null;
      this.taskManager = null;
    }
    
    // Initialize from URL parameters
    this.initializeFromURL();
    
    // Load tasks and setup (only if task system is available)
    if (this.commands) {
      this.loadTasks();
    } else {
      //// // console.log('⏳ Task system will be initialized later');
    }
    
    // Setup global functions
    this.setupGlobalFunctions();

    // Subscribe to the SSE bus once for the page so every visible task row
    // reacts to status changes, not just ones spawned in this session.
    this.setupTaskBusListeners();

    // Setup mobile menu
    this.setupMobileMenu();

    //// // console.log('✅ TasksManager initialized with modular architecture');
  }


  // Shared "task → notification payload" resolver. Every task surface
  // (started/completed toast, delete modal, delete confirmation) builds
  // the same identity (display name, app icon, friendly action title,
  // emoji type-icon) — keep it in one place so they read consistently.
  _taskNotificationDescriptor(task) {
    const appName = (task && task.app) || null;
    const action  = (task && task.type) || '';
    const command = (task && task.command) || '';
    // System-level backups carry no app slug, so they'd otherwise resolve to a
    // blank subject + no icon. Give them the LibrePortal identity and a
    // distinct subject ("Configs" vs the whole-fleet "All apps").
    const sysBackup = command.match(/^libreportal backup (system|all)\b/);
    const sysBackupSubject = sysBackup
      ? (sysBackup[1] === 'system' ? 'Configs' : 'All apps')
      : null;
    // Any task with no app slug (verify, regen, update, …) is a LibrePortal /
    // system task. Without this it resolves to a blank subject + no icon, so
    // its notification renders an empty bold line above the message (gap) and
    // looks unlike the app/system notifications.
    const isSystemTask = action.startsWith('setup-') || appName === 'system' || !!sysBackup || !appName;
    let actionTitle = this.formatActionTitle(action);
    // Tool tasks: prefer the catalog-defined label.
    const toolCmdMatch = ((task && task.command) || '').match(/libreportal app tool (\S+) (\S+)/);
    if (toolCmdMatch) {
      const toolId = toolCmdMatch[2];
      let toolLabel = null;
      const cat = window.toolsCatalog;
      if (cat && cat.apps && cat.apps[toolCmdMatch[1]] && Array.isArray(cat.apps[toolCmdMatch[1]].tools)) {
        const t = cat.apps[toolCmdMatch[1]].tools.find(x => x.id === toolId);
        if (t && t.label) toolLabel = t.label;
      }
      if (!toolLabel) toolLabel = toolId.split(/[_-]/).map(w => w ? w.charAt(0).toUpperCase() + w.slice(1) : '').join(' ');
      actionTitle = toolLabel;
    }
    const displayName = sysBackupSubject
      ? sysBackupSubject
      : (isSystemTask
        ? 'LibrePortal'
        : ((appName && window.getAppDisplayName) ? window.getAppDisplayName(appName) : (appName || '')));
    const icon = isSystemTask
      ? '/core/icons/libreportal.svg'
      : (appName ? `/core/icons/apps/${encodeURIComponent(appName)}.svg` : null);
    const typeIcon = (this.getTaskTypeIcon ? this.getTaskTypeIcon(task)?.icon : '') || '';
    return { appName, isSystemTask, actionTitle, displayName, icon, typeIcon };
  }

  // Initialize task system after scripts are loaded
  initializeTaskSystem() {
    try {
      //// // console.log('🔧 Initializing task system components...');
      
      // Check if TaskManager is available
      if (typeof TaskManager === 'undefined') {
        console.warn('⚠️ TaskManager not available yet, deferring initialization');
        return false;
      }
      
      this.commands = new TaskCommands();
      this.actions = new TaskActions(this, this.commands);
      this.router = new TaskRouter(this, this.actions);
      this.taskManager = new TaskManager(); // Add TaskManager for task operations
      
      // Now load tasks since system is ready
      this.loadTasks();
      
      //// // console.log('✅ Task system initialized successfully');
      return true;
    } catch (error) {
      console.error('❌ Failed to initialize task system:', error);
      return false;
    }
  }


  initializeFromURL() {
    const currentUrl = new URL(window.location.href);
    const searchParams = currentUrl.searchParams;
    
    // Check if we're on the main tasks page (not app page)
    const isMainTasksPage = currentUrl.pathname === '/tasks' || currentUrl.pathname.startsWith('/tasks/') || currentUrl.pathname === '/tasks.html';

    if (isMainTasksPage) {
      // Category + single-task deep link from the path (/tasks/<category>/<id>),
      // with legacy ?=<category> and ?task=<id> queries still honoured.
      const parts = (typeof window.taskPartsFromPath === 'function')
        ? window.taskPartsFromPath(currentUrl.pathname, currentUrl.search)
        : { category: '', taskId: searchParams.get('task') || '' };
      this.currentCategory = parts.category || searchParams.get('') || 'all';
      this.highlightedTaskId = parts.taskId || null;
    } else {
      // Not on main tasks page, get default filter from localStorage
      this.currentCategory = localStorage.getItem('tasksDefaultFilter') || 'all';
      this.highlightedTaskId = null; // Always clear when not on tasks page
    }
    
  }

  updateURL(category, taskId = null) {
    // Update URL without page reload and without hash. The task deep link is a
    // path segment (/tasks/<category>/<id>) — see window.taskPath.
    const newURL = (typeof window.taskPath === 'function')
      ? window.taskPath(category, taskId)
      : `/tasks/${category || 'all'}${taskId ? '/' + String(taskId).replace(/^task_/, '') : ''}`;
    
    // Prevent the SPA from interfering
    if (window.librePortalSPA) {
      window.librePortalSPA.currentRoute = newURL;
    }
    
    // Use a timeout to avoid conflicts with SPA routing
    setTimeout(() => {
      window.history.pushState({ category, taskId }, '', newURL);
    }, 0);
  }

  async init() {
    //// // console.log('🔧 Initializing TasksManager...');

    // Re-read the URL on every (re)mount. The SPA reuses this singleton, so a
    // navigation to /tasks/<cat>/<id> must refresh the category + deep-link
    // state — a constructor-only read goes stale after the first visit.
    this.initializeFromURL();

    // Load initial tasks and refresh sidebar counts
    await this.loadTasks();

    // Force a refresh to ensure latest data
    await this.loadTasks();

    // Open the row this visit is about (deep link, or the live task) now that
    // the first render is in the DOM, and arm running-task auto-follow.
    if (typeof this.applyInitialSelection === 'function') this.applyInitialSelection();

    // Setup auto-refresh
    this.setupAutoRefresh();
    
    // Setup global functions
    this.setupGlobalFunctions();

    // Subscribe to the SSE bus once for the page so every visible task row
    // reacts to status changes, not just ones spawned in this session.
    this.setupTaskBusListeners();

    // Setup mobile menu
    this.setupMobileMenu();
    
    //// // console.log('✅ TasksManager initialized');
  }


















  /* Detect a task that's an LibrePortal system action (no specific app) so
     the row can show the LibrePortal logo instead of a blank icon slot. */

  /* Render the leading icon(s) on a task row:
       - Per-app task → emoji type icon + app icon
       - System task → emoji type icon + LibrePortal logo
       - Anything else → emoji type icon only
     Keeps the layout consistent across every row regardless of source. */






  setupMobileMenu() {
    // Setup mobile menu toggle (if needed)
    const mobileOverlay = document.getElementById('mobile-overlay');
    const sidebar = document.getElementById('sidebar');
    
    if (mobileOverlay && sidebar) {
      mobileOverlay.addEventListener('click', () => {
        sidebar.classList.remove('mobile-open');
        mobileOverlay.classList.remove('active');
      });
    }
  }


  setupAutoRefresh() {
    // Only refresh when tasks page is visible and every 30 seconds
    this.refreshInterval = setInterval(() => {
      // Only refresh if we're on the tasks page
      if (window.location.pathname === '/tasks' || window.location.hash.includes('tasks')) {
        this.loadTasks();
      }
    }, 30000); // 30 seconds instead of 5
  }
  
  setupGlobalFunctions() {
    // Make functions available globally for onclick handlers
    window.filterTasksByCategory = (category) => {
      event.preventDefault();
      this.filterTasksByCategoryHandler(category);
    };

    // Delegated click handler for the Task ID and App links rendered inside
    // each task row. Behaviour:
    //   * Task ID always lands on the global /tasks page with that task open.
    //     If we're already there, just push the URL and toggle the row open
    //     (no content reload); otherwise SPA-navigate to /tasks.
    //   * App link goes to that app's /app page on whatever its default tab
    //     is (we don't pin tab=… so the app's own logic picks one).
    if (!window.__taskMetaLinksBound) {
      window.__taskMetaLinksBound = true;
      document.addEventListener('click', (e) => {
        const link = e.target.closest('a.task-id-link, a.task-app-link');
        if (!link) return;
        const href = link.getAttribute('href');
        if (!href) return;
        e.preventDefault();
        e.stopPropagation();

        const navigate = (url) => {
          if (window.spaClean && typeof window.spaClean.navigate === 'function') {
            window.spaClean.navigate(url);
          } else {
            window.location.href = url;
          }
        };

        if (link.classList.contains('task-id-link')) {
          const taskId = link.dataset.taskId;
          const onTasksPage = window.location.pathname.startsWith('/tasks');
          if (onTasksPage && taskId && typeof window.toggleTaskDetails === 'function') {
            // Already on /tasks — soft-update the URL and open the row.
            window.history.pushState({}, '', href);
            if (window.tasksManager) window.tasksManager.highlightedTaskId = taskId;
            window.toggleTaskDetails(taskId);
          } else {
            // Coming from /app or anywhere else — go to the tasks page; its
            // initializeFromURL picks up `task=` and auto-expands.
            navigate(href);
          }
        } else {
          // App link — go to the app page on its default tab.
          //
          // `task-parameter-preserve.js` stashes any `task=…` from the
          // initial page URL into sessionStorage.pendingTaskId, and
          // app-tabbed-manager's init reads that as a fallback when the
          // current URL has no task param. Without clearing it here, an old
          // task id from /tasks would hijack the app page, force-switching
          // to the Tasks tab and opening that task. Clear it so the app
          // page lands on its actual default tab.
          try { sessionStorage.removeItem('pendingTaskId'); } catch {}
          navigate(href);
        }
      });
    }
    window.refreshTasks = () => this.refreshTasks();
    window.toggleTaskDetails = (taskId) => this.toggleTaskDetails(taskId);
    window.retryTask = (taskId) => this.retryTask(taskId);
    window.deleteTask = (taskId) => this.deleteTask(taskId);
    window.clearAllTasks = () => this.clearAllTasks();
    window.viewTaskLogs = (taskId) => this.viewTaskLogs(taskId);
    window.toggleLogStreaming = (taskId) => this.toggleLogStreaming(taskId);
    window.closeTaskLogsModal = () => {
      const modal = document.querySelector('.task-logs-modal');
      if (modal) {
        // Stop all active streaming for this modal
        this.activeLogStreams?.forEach((streamData, streamTaskId) => {
          if (streamData.modal === modal) {
            streamData.stream.stop();
            this.activeLogStreams.delete(streamTaskId);
          }
        });
        modal.remove();
      }
    };
    
  }

  // Single page-wide subscription to the TaskEventBus. This complements
  // monitorTask (which only fires for tasks created in this session) and
  // ensures any visible row updates when its status changes — including the
  // running -> completed/failed/cancelled transition that previously only
  // refreshed when the user switched tabs and came back.
  //
  // The guard is on `window`, not `this`, because TasksManager is constructed
  // in several places (system-loader, app-tabbed-manager, …). Without a
  // global guard each instance would attach its own listener and the user
  // would see N notifications for one task completion.
  setupTaskBusListeners() {
    if (window.__tasksManagerBusBound) return;
    window.__tasksManagerBusBound = true;

    const upsertLocal = (task) => {
      if (!task || !task.id) return;
      const idx = this.tasks.findIndex(t => t.id === task.id);
      if (idx >= 0) this.tasks[idx] = task; else this.tasks.unshift(task);
    };

    window.addEventListener('taskCreated', (e) => {
      const task = e.detail && e.detail.task;
      if (!task) return;
      upsertLocal(task);
      // Don't re-render the entire list here — `renderTasks` would blow away
      // any open dropdown's DOM. A targeted update is enough; the next
      // category switch / refresh will pull the new row in.
      this.updateTaskDisplay(task);
      this.maybeFollowRunningTask(task);
    });

    window.addEventListener('taskUpdated', (e) => {
      const task = e.detail && e.detail.task;
      if (!task) return;
      upsertLocal(task);
      if (task.status === 'running') {
        this.updateTaskStructure(task.id, task);
        this.startLogStreaming(task.id, task);
        this.maybeFollowRunningTask(task);
      }
      this.updateTaskDisplay(task);
    });

    window.addEventListener('taskCompleted', (e) => {
      const task = e.detail && e.detail.task;
      const taskId = (task && task.id) || (e.detail && e.detail.taskId);
      if (!taskId) return;
      if (task) upsertLocal(task);
      if (task) this.updateTaskDisplay(task);

      // Final render of any buffered log content, then stop the SSE listener.
      const stream = this.activeLogStreams && this.activeLogStreams.get(taskId);
      if (stream && typeof stream.render === 'function') stream.render();
      this.stopLogStreaming(taskId);

      // User-visible notification so they know without staring at the page.
      // Layout matches the "<App> task started!" format used by task-actions
      // and backup-manager so started/completed look like a matched pair.
      if (window.notificationSystem && task) {
        const { appName, actionTitle, displayName, icon, typeIcon } = this._taskNotificationDescriptor(task);
        const onAppPage = window.location.pathname.startsWith('/app') && !window.location.pathname.startsWith('/apps');
        const url = (onAppPage && appName)
          ? window.appPath(appName, 'tasks', null, taskId)
          : window.taskPath('all', taskId);
        // Per-action emoji (install ✅, backup 💾, restore 📦, …) in the
        // notification's leftmost icon slot, mirroring task-list rows.
        const customIcon = typeIcon ? `<span style="font-size:18px;line-height:1;">${typeIcon}</span>` : null;

        let body;
        let level;
        if (task.status === 'completed') {
          body = `<strong>${displayName}</strong><br>${actionTitle} task completed!`;
          level = 'success';
        } else if (task.status === 'failed') {
          body = `<strong>${displayName}</strong><br>${actionTitle} task failed.`;
          level = 'error';
        } else if (task.status === 'cancelled') {
          body = `<strong>${displayName}</strong><br>${actionTitle} task cancelled.`;
          level = 'warning';
        }
        if (body) window.notificationSystem.show(body, level, appName, url, icon, customIcon);

        // Belt-and-braces: when the completion notification fires we also
        // tell the app-tabbed manager to re-enable that app's tabs and
        // buttons. The taskCompleted listener inside app-tabbed-manager
        // does this too, plus the 5s reconcile sweep — but routing it
        // through here as well means any one path being broken or
        // de-bound still leaves the user with a usable UI rather than
        // permanently-disabled tabs.
        if (appName && window.appTabbedManager && typeof window.appTabbedManager.enableAppButtons === 'function') {
          try { window.appTabbedManager.enableAppButtons(appName); } catch {}
        }
      }
    });

    window.addEventListener('taskDeleted', (e) => {
      const id = e.detail && e.detail.id;
      if (!id) return;
      this.tasks = this.tasks.filter(t => t.id !== id);
      const el = document.querySelector(`.task-item[data-task-id="${id}"]`);
      if (!el) return;
      const parent = el.parentElement;
      el.remove();
      if (!parent || parent.querySelector('.task-item')) return;
      if (parent.id === 'tasks-list') {
        this.renderTasks();
      } else if (parent.id === 'app-tasks') {
        const appName = (window.appTabbedManager && window.appTabbedManager.currentApp) || '';
        parent.innerHTML = `<p style="color: #888;">No tasks found for ${appName}.</p>`;
      }
    });
  }



























  destroy() {
    if (this.refreshInterval) {
      clearInterval(this.refreshInterval);
    }
  }
}

// Export for use in other modules
window.TasksManager = TasksManager;
