// Auto-extracted from tasks-manager.js (verbatim) — augments TasksManager.prototype. Loaded after the base.
Object.assign(TasksManager.prototype, {
  async retryTask(taskId) {
    if (!confirm('Are you sure you want to retry this task?')) return;

    try {
      const task = this.tasks.find(t => t.id === taskId);
      if (!task) {
        throw new Error('Task not found');
      }

      // Create a new task with the same command
      const newTask = await this.taskManager.createTask(
        task.command,
        task.type,
        task.app,
        task.config
      );
      
      //// // console.log(`✅ Task retried: ${newTask.id}`);
      
      // Refresh tasks to show the new one
      await this.loadTasks();
      
      if (window.notificationSystem) {
        // Use the source task's type icon — retrying a backup shows 💾 etc.
        const typeIcon = this.getTaskTypeIcon ? this.getTaskTypeIcon(task)?.icon : '';
        const customIcon = typeIcon ? `<span style="font-size:18px;line-height:1;">${typeIcon}</span>` : null;
        window.notificationSystem.show('Task retried successfully', 'success', null, null, null, customIcon);
      }
    } catch (error) {
      console.error('Error retrying task:', error);
      if (window.notificationSystem) {
        window.notificationSystem.error(`Failed to retry task: ${error.message}`);
      }
    }
  },
  async deleteTask(taskId) {
    // Get the latest known status before deciding what to do. The server
    // refuses to delete tasks that are still running or queued (HTTP 409),
    // and we used to just propagate that as a red banner — but the user is
    // almost always trying to clean up a row that looked finished, or a
    // genuinely active task they want gone. Either way the right answer is
    // "cancel first, then delete", which is what we do here.
    const cached = (window.taskEventBus && window.taskEventBus.getTask)
      ? window.taskEventBus.getTask(taskId)
      : null;
    let task = cached || (this.tasks && this.tasks.find(t => t.id === taskId));
    if (!task && this.taskManager && this.taskManager.getTask) {
      try { task = await this.taskManager.getTask(taskId); } catch {}
    }

    const isActive = task && (task.status === 'running' || task.status === 'queued' || task.status === 'pending');

    const confirmed = await this._showDeleteTaskModal(task, isActive);
    if (!confirmed) return;

    try {
      if (isActive) {
        // Ask the server to cancel. POST /cancel either flips status straight
        // to `cancelled` (queued -> cancelled is synchronous) or drops a
        // `<id>.cancel` marker the bash processor picks up within ~1s
        // (running -> cancelled). We then wait for the terminal status via
        // SSE before issuing the delete.
        try { await this.taskManager.cancelTask(taskId); } catch {
          // If cancel itself failed (e.g. already terminal), fall through
          // and try the delete anyway.
        }
        await this._waitForTaskTerminal(taskId, 15_000);
      }

      // Try the polite delete first. If the task is still flagged as
      // running/queued server-side (e.g. the bash processor is dead and
      // never picked up the cancel marker), fall back to the force-delete
      // override so the user isn't stuck with a permanently-undeletable row.
      try {
        await this.taskManager.deleteTask(taskId);
      } catch (err) {
        const looks409 = /\bHTTP 409\b/.test(err && err.message ? err.message : '');
        if (!looks409) throw err;
        await this.taskManager.deleteTask(taskId, { force: true });
      }

      // Remove from local array and re-render
      this.tasks = this.tasks.filter(t => t.id !== taskId);
      this.renderTasks();
      this.updateStats();
      this.updateSidebarCounts();
      this.generateAppCategories();

      if (window.notificationSystem) {
        // Match the "<App> task <verb>" two-line shape used by started/
        // completed/failed/cancelled so deletion reads as part of the same
        // family. Type emoji falls back to 🗑️ since deletion is a 🗑️ action.
        const { appName, actionTitle, displayName, icon, typeIcon } = this._taskNotificationDescriptor(task);
        const emoji = typeIcon || '🗑️';
        const customIcon = `<span style="font-size:18px;line-height:1;">${emoji}</span>`;
        const body = displayName
          ? `<strong>${displayName}</strong><br>${actionTitle} task deleted.`
          : `${actionTitle || 'Task'} deleted.`;
        window.notificationSystem.show(body, 'info', appName, null, icon, customIcon);
      }
    } catch (error) {
      console.error('Error deleting task:', error);
      if (window.notificationSystem) {
        window.notificationSystem.error(`Failed to delete task: ${error.message}`);
      }
    }
  },
  async clearAllTasks() {
    // Routes to one of two modes depending on whether any rows are ticked.
    // Both paths share _showClearAllModal — same UX, same modal, different
    // input list + title.
    const hasSelection = this.selectedTaskIds.size > 0;
    const targetTasks = hasSelection
      ? this.tasks.filter(t => this.selectedTaskIds.has(t.id))
      : this.tasks;
    const result = await this._showClearAllModal(targetTasks, hasSelection ? 'selected' : 'all');
    if (!result || !result.confirmed) return false;
    await this.performClearAll({ cancelRunning: result.cancelRunning, targets: targetTasks });
    if (hasSelection) {
      // Drop any ids we just deleted from the selection set, then refresh
      // the button label + master-checkbox state.
      this.selectedTaskIds = new Set([...this.selectedTaskIds].filter(id => this.tasks.find(t => t.id === id)));
      this._updateSelectionUI();
    }
    return true;
  },
  async performClearAll(opts) {
    opts = opts || {};
    const cancelRunning = !!opts.cancelRunning;
    // Caller (clearAllTasks) passes the subset to operate on. Falls back
    // to this.tasks for backward compatibility — old direct callers
    // continue to mean "everything".
    const universe = Array.isArray(opts.targets) ? opts.targets : (this.tasks || []);

    // Show progress notification with the trash type icon in the left slot
    // (this is conceptually a delete-all action, so 🗑️ matches the row icon).
    const customIcon = '<span style="font-size:18px;line-height:1;">🗑️</span>';
    const progressNotification = window.notificationSystem.show(
      'Clearing tasks...',
      'info',
      null,
      null,
      null,
      customIcon
    );

    const isActive = (t) => t && (t.status === 'running' || t.status === 'queued' || t.status === 'pending');
    // Partition: which tasks we attempt now, which we skip silently.
    const targets = universe.filter(t => cancelRunning || !isActive(t));
    const skipped = universe.filter(t => !targets.includes(t));

    try {
      // For active tasks (only reached when the toggle is on): cancel first,
      // wait for terminal status, then delete. Mirrors the single-row
      // deleteTask flow so the bash processor has a chance to honour the
      // cancel marker before the DELETE arrives (otherwise we'd 409).
      await Promise.all(targets.map(async (task) => {
        if (cancelRunning && isActive(task)) {
          try { await this.taskManager.cancelTask(task.id); } catch {}
          await this._waitForTaskTerminal(task.id, 15_000);
        }
        try {
          await this.taskManager.deleteTask(task.id);
        } catch (err) {
          const looks409 = /\bHTTP 409\b/.test(err && err.message ? err.message : '');
          if (!looks409) throw err;
          await this.taskManager.deleteTask(task.id, { force: true });
        }
      }));

      // Re-sync local state: keep anything we intentionally skipped.
      const deletedIds = new Set(targets.map(t => t.id));
      this.tasks = this.tasks.filter(t => !deletedIds.has(t.id));
      this.renderTasks();
      this.updateStats();
      this.updateSidebarCounts();
      this.generateAppCategories();

      if (progressNotification && progressNotification.remove) {
        progressNotification.remove();
      }

      if (window.notificationSystem) {
        const msg = skipped.length > 0
          ? `Deleted ${targets.length} tasks (skipped ${skipped.length} still running)`
          : (targets.length === 1 ? 'Task deleted' : `Deleted ${targets.length} tasks`);
        window.notificationSystem.show(msg, 'success', null, null, null, customIcon);
      }
    } catch (error) {
      console.error('Error clearing tasks:', error);

      if (progressNotification && progressNotification.remove) {
        progressNotification.remove();
      }

      if (window.notificationSystem) {
        window.notificationSystem.show(
          `Failed to clear tasks: ${error.message}`,
          'error',
          null,
          null,
          null,
          customIcon
        );
      }
    }
  },
  // Selection helpers wired by the row + master checkboxes.
  toggleTaskSelection(taskId, checked) {
    if (checked) this.selectedTaskIds.add(taskId);
    else         this.selectedTaskIds.delete(taskId);
    this._updateSelectionUI();
  },
  toggleSelectAll(checked) {
    if (checked) {
      // Tick every CURRENTLY VISIBLE row. We use the rendered checkboxes
      // rather than this.tasks so category-filtered views only select
      // what the user can see.
      const boxes = document.querySelectorAll('#tasks-list [data-task-select]');
      boxes.forEach((cb) => {
        this.selectedTaskIds.add(cb.dataset.taskSelect);
        cb.checked = true;
      });
    } else {
      this.selectedTaskIds.clear();
      document.querySelectorAll('#tasks-list [data-task-select]').forEach((cb) => { cb.checked = false; });
    }
    this._updateSelectionUI();
  },
  // Rewrites the Clear All button label + the master-checkbox indeterminate
  // state to reflect the current selection. Cheap — only touches a few
  // DOM nodes, safe to call from any selection-change path.
  _updateSelectionUI() {
    const n = this.selectedTaskIds.size;
    const btn = document.getElementById('tasks-clear-btn');
    const btnLabel = btn && btn.querySelector('.clear-btn-label');
    if (btnLabel) btnLabel.textContent = n > 0 ? `Delete Selected (${n})` : 'Clear All';
    if (btn) btn.title = n > 0 ? `Delete ${n} selected task${n === 1 ? '' : 's'}` : 'Clear All Tasks';

    // Master checkbox: checked when ALL visible are picked, indeterminate
    // when SOME are, unchecked when none.
    const master = document.getElementById('tasks-select-all');
    const visible = document.querySelectorAll('#tasks-list [data-task-select]');
    if (master) {
      if (n === 0 || visible.length === 0) {
        master.checked = false;
        master.indeterminate = false;
      } else if (n >= visible.length) {
        master.checked = true;
        master.indeterminate = false;
      } else {
        master.checked = false;
        master.indeterminate = true;
      }
    }
  },
  // Resolves once the task reaches completed/failed/cancelled, or after the
  // timeout. Used by deleteTask so a cancel request has time to take effect
  // before we issue the DELETE that would otherwise 409.
  _waitForTaskTerminal(taskId, timeoutMs = 15_000) {
    return new Promise((resolve) => {
      // Already terminal in the bus cache? No need to wait.
      const cached = (window.taskEventBus && window.taskEventBus.getTask) ? window.taskEventBus.getTask(taskId) : null;
      if (cached && (cached.status === 'completed' || cached.status === 'failed' || cached.status === 'cancelled')) {
        return resolve(cached);
      }

      let timer;
      const cleanup = () => {
        window.removeEventListener('taskCompleted', onComplete);
        window.removeEventListener('taskUpdated', onUpdate);
        if (timer) clearTimeout(timer);
      };
      const isTerminal = (s) => s === 'completed' || s === 'failed' || s === 'cancelled';
      const onComplete = (e) => {
        const t = e.detail && (e.detail.task || (e.detail.taskId === taskId ? { id: taskId, status: e.detail.status } : null));
        const id = (t && t.id) || (e.detail && e.detail.taskId);
        if (id !== taskId) return;
        cleanup();
        resolve(t);
      };
      const onUpdate = (e) => {
        const t = e.detail && e.detail.task;
        if (!t || t.id !== taskId) return;
        if (isTerminal(t.status)) { cleanup(); resolve(t); }
      };
      window.addEventListener('taskCompleted', onComplete);
      window.addEventListener('taskUpdated', onUpdate);
      timer = setTimeout(() => { cleanup(); resolve(null); }, timeoutMs);
    });
  },
});
