// Auto-extracted from tasks-manager.js (verbatim) — augments TasksManager.prototype. Loaded after the base.
Object.assign(TasksManager.prototype, {
  toggleTaskDetails(taskId) {
    // A manual toggle means the user has taken control of the view — stop
    // auto-following the running task for the rest of this visit.
    this.followRunning = false;
    const details = document.getElementById(`details-${taskId}`);
    const toggleBtn = document.querySelector(`.task-btn.toggle-details[onclick*="toggleTaskDetails('${taskId}')"]`);
    
    if (details) {
      const isOpen = details.style.display === 'block';
      
      // Close all other task details and reset their buttons
      if (isOpen) {
        document.querySelectorAll('.task-details').forEach(otherDetails => {
          if (otherDetails.id !== `details-${taskId}`) {
            otherDetails.style.display = 'none';
            otherDetails.classList.remove('task-details-open');
          }
        });
        
        document.querySelectorAll('.task-btn.toggle-details').forEach(otherBtn => {
          if (!otherBtn.getAttribute('onclick').includes(taskId)) {
            otherBtn.classList.remove('expanded');
          }
        });
        
        // Close current
        details.style.display = 'none';
        details.classList.remove('task-details-open');
        if (toggleBtn) toggleBtn.classList.remove('expanded');
      } else {
        // Open current
        details.style.display = 'block';
        details.classList.add('task-details-open');
        if (toggleBtn) toggleBtn.classList.add('expanded');
        
        // Auto-load logs when opened. For active tasks, hand off to the live
        // streamer so SSE chunks keep updating the panel; for terminal tasks
        // a one-shot snapshot is enough.
        const t = this.tasks.find(x => x.id === taskId);
        if (t && (t.status === 'running' || t.status === 'queued' || t.status === 'pending')) {
          this.startLogStreaming(taskId, t);
        } else {
          this.loadTaskLogs(taskId);
        }
        
        // Scroll to task
        const taskElement = document.querySelector(`[data-task-id="${taskId}"]`);
        if (taskElement) {
          taskElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
        }
      }
      
      // Update URL to include task parameter
      this.updateURL(this.currentCategory, isOpen ? null : taskId);
      this.highlightedTaskId = isOpen ? null : taskId;
    } else {
      // Remove task parameter from URL
      this.updateURL(this.currentCategory);
      this.highlightedTaskId = null;
    }
  },
  // Programmatically open exactly one task row: collapse any other open
  // panel, expand this one, attach the right log view (live stream for
  // active tasks, snapshot for terminal ones) and bring it into view.
  // Used by deep links and the running-task auto-follow — unlike
  // toggleTaskDetails it never closes an already-open target and never
  // touches followRunning. Returns false when the row isn't in the DOM yet.
  selectTask(taskId) {
    const details = document.getElementById(`details-${taskId}`);
    if (!details) return false;

    document.querySelectorAll('.task-details.task-details-open').forEach(el => {
      if (el.id !== `details-${taskId}`) {
        el.style.display = 'none';
        el.classList.remove('task-details-open');
      }
    });
    document.querySelectorAll('.task-btn.toggle-details.expanded').forEach(btn => {
      if (!(btn.getAttribute('onclick') || '').includes(`'${taskId}'`)) btn.classList.remove('expanded');
    });

    const wasOpen = details.classList.contains('task-details-open');
    details.style.display = 'block';
    details.classList.add('task-details-open');
    const toggleBtn = document.querySelector(`.task-btn.toggle-details[onclick*="toggleTaskDetails('${taskId}')"]`);
    if (toggleBtn) toggleBtn.classList.add('expanded');

    const task = this.tasks.find(t => t.id === taskId);
    if (task && (task.status === 'running' || task.status === 'queued' || task.status === 'pending')) {
      this.startLogStreaming(taskId, task);
    } else {
      this.loadTaskLogs(taskId);
    }

    if (!wasOpen) {
      const row = document.querySelector(`.task-item[data-task-id="${taskId}"]`);
      if (row) row.scrollIntoView({ behavior: 'smooth', block: 'center' });
    }
    this.highlightedTaskId = taskId;
    return true;
  },
  // Pick which row should open when the page (re)loads, and arm auto-follow.
  //
  // A `?task=` deep link normally wins. The setup-wizard handoff is the
  // exception: it deep-links the FIRST task of its install queue
  // (&from=setup), but by the time the page renders the processor may
  // already be tasks ahead — so for setup arrivals, and for plain visits
  // with no deep link, open whatever is actually running (else next in the
  // queue) and keep following the queue until the user takes over.
  applyInitialSelection() {
    if (!document.getElementById('tasks-list')) return;

    const params = new URL(window.location.href).searchParams;
    const fromSetup = params.get('from') === 'setup';
    const isActive = (t) => t && (t.status === 'running' || t.status === 'queued' || t.status === 'pending');
    const pinned = this.highlightedTaskId
      ? this.tasks.find(t => t.id === this.highlightedTaskId)
      : null;

    this.followRunning = fromSetup || !pinned;

    let target = pinned;
    if (this.followRunning) {
      const running = this.tasks.find(t => t.status === 'running');
      // this.tasks is sorted newest-first, so the oldest queued is next to run
      const nextQueued = [...this.tasks].reverse().find(t => t.status === 'queued' || t.status === 'pending');
      target = running || (isActive(pinned) ? pinned : null) || nextQueued || pinned;
    }
    if (!target) return;
    // renderTasks restores the pre-render scroll position one frame from now;
    // selecting after that frame keeps our scrollIntoView from being cancelled.
    const id = target.id;
    requestAnimationFrame(() => requestAnimationFrame(() => this.selectTask(id)));
  },
  // The queue advanced to a new running task — if the user hasn't taken over
  // and we're on the tasks page, move the open panel along with it.
  maybeFollowRunningTask(task) {
    if (!this.followRunning || !task || task.status !== 'running') return;
    if (!document.getElementById('tasks-list')) return;
    if (this.highlightedTaskId === task.id) return;
    const current = this.tasks.find(t => t.id === this.highlightedTaskId);
    if (current && current.status === 'running') return; // never yank a live view
    if (!this.selectTask(task.id)) {
      // Row not rendered yet (task created since the last render) — rebuild
      // the list from the upserted cache, then select.
      this.renderTasks();
      this.selectTask(task.id);
    }
  },
  // Monitor a specific task. State changes now arrive via SSE through
  // TaskEventBus, so this is mostly a hook for the UI to:
  //   - auto-expand the task if it's the one we just started
  //   - start log streaming when the task transitions to running
  //   - clean up local intervals when it terminates
  // No more polling; the only fetches happen on-demand via TaskManager.
  monitorTask(taskId, appName, action) {
    if (!this.highlightedTaskId || this.highlightedTaskId === taskId) {
      setTimeout(() => this.autoExpandTask(taskId), 1500);
    }

    let statusUpdateInterval = null;

    const onUpdate = (event) => {
      const t = event.detail && event.detail.task;
      if (!t || t.id !== taskId) return;

      if (t.status === 'running') {
        this.updateTaskStructure(taskId, t);
        this.startLogStreaming(taskId, t);
        if (this.highlightedTaskId === taskId && !statusUpdateInterval) {
          statusUpdateInterval = setInterval(() => this.updateHighlightedTaskStatus(taskId), 2000);
        }
      } else {
        this.updateTaskDisplay(t);
      }
    };

    const onComplete = (event) => {
      if (!event.detail || event.detail.taskId !== taskId) return;
      if (statusUpdateInterval) { clearInterval(statusUpdateInterval); statusUpdateInterval = null; }
      this.stopLogStreaming(taskId);
      window.removeEventListener('taskUpdated', onUpdate);
      window.removeEventListener('taskCompleted', onComplete);
      if (this.taskMonitors) this.taskMonitors.delete(taskId);
    };

    window.addEventListener('taskUpdated', onUpdate);
    window.addEventListener('taskCompleted', onComplete);
    // Track this monitor so the tasks module's unmount() can release it if the
    // user navigates away while the task is still running (these window
    // listeners + interval otherwise outlive the page until the task completes).
    (this.taskMonitors = this.taskMonitors || new Map()).set(taskId, () => {
      if (statusUpdateInterval) { clearInterval(statusUpdateInterval); statusUpdateInterval = null; }
      window.removeEventListener('taskUpdated', onUpdate);
      window.removeEventListener('taskCompleted', onComplete);
    });
  },
  // Auto-expand a task when it's created/started. Waits for its row to land in
  // the DOM, then hands off to selectTask, which collapses any other open panel
  // first. Opening the row directly here (as this used to) skipped that
  // collapse and never set highlightedTaskId, so a burst of monitored tasks —
  // e.g. a multi-app first install — stacked every panel open instead of
  // showing just the active one.
  autoExpandTask(taskId) {
    let attempts = 0;
    const maxAttempts = 10;

    const tryExpand = () => {
      attempts++;
      // selectTask returns false until the row exists; once it succeeds it has
      // also attached the right log view (live stream vs snapshot), set
      // highlightedTaskId and scrolled the row into view — nothing more to do.
      if (this.selectTask(taskId)) return;
      if (attempts < maxAttempts) {
        setTimeout(tryExpand, 500);
      } else {
        console.warn(`⚠️ Could not find task element for ${taskId} after ${maxAttempts} attempts`);
      }
    };

    tryExpand();
  },
});
