/**
 * TaskManager — thin client over /api/tasks.
 *
 * Replaces the old direct-file-operation approach. We never touch task files
 * or the queue from the browser anymore — the server owns them. State updates
 * arrive via SSE through TaskEventBus; this class just performs CRUD.
 */

class TaskManager {
  constructor() {}

  /**
   * Create a new task. Returns the server's authoritative task object.
   * Side effect: a `taskCreated` window event will fire from the SSE bus
   * within milliseconds — but the POST response also contains the task,
   * so callers that need the id immediately can use the return value.
   */
  async createTask(command, type = 'custom', app = null, config = '') {
    const res = await fetch('/api/tasks', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ command, type, app, config })
    });
    if (!res.ok) throw new Error(`Failed to create task: HTTP ${res.status}`);
    const task = await res.json();

    if (window.tasksManager) window.tasksManager.highlightedTaskId = task.id;
    return task;
  }

  async getTask(taskId) {
    if (window.taskEventBus) {
      const cached = window.taskEventBus.getTask(taskId);
      if (cached) return cached;
    }
    const res = await fetch(`/api/tasks/${taskId}`);
    if (res.status === 404) return null;
    if (!res.ok) return null;
    return res.json();
  }

  async getTaskSummary(taskId) {
    const task = await this.getTask(taskId);
    if (!task) return null;
    return {
      id: task.id,
      command: task.command,
      type: task.type,
      app: task.app,
      config: task.config,
      status: task.status,
      createdAt: task.created_at || task.createdAt,
      startedAt: task.started_at || task.startedAt,
      completedAt: task.completed_at || task.completedAt,
      hasOutput: false,
      hasError: !!task.error_message,
      outputLength: 0,
      errorLength: task.error_message ? task.error_message.length : 0
    };
  }

  async listTasks() {
    const res = await fetch('/api/tasks');
    if (!res.ok) return [];
    return res.json();
  }

  async cancelTask(taskId) {
    const res = await fetch(`/api/tasks/${taskId}/cancel`, { method: 'POST' });
    if (!res.ok) throw new Error(`Cancel failed: HTTP ${res.status}`);
    return res.json();
  }

  async deleteTask(taskId, { force = false } = {}) {
    const url = force
      ? `/api/tasks/${taskId}?force=1`
      : `/api/tasks/${taskId}`;
    const res = await fetch(url, { method: 'DELETE' });
    if (!res.ok) throw new Error(`Delete failed: HTTP ${res.status}`);
    return true;
  }

  /**
   * Read the task log. Returns the last `maxLines` lines as an array.
   * The SSE bus delivers incremental log chunks via `taskLog` events; this
   * is for "open the modal and dump the full output" use-cases.
   */
  async readTaskLog(taskId, maxLines = 1000) {
    try {
      const res = await fetch(`/api/tasks/${taskId}/log`);
      if (!res.ok) return [];
      const text = await res.text();
      const lines = text.split('\n');
      return lines.length > maxLines ? lines.slice(-maxLines) : lines;
    } catch { return []; }
  }

  async readFullTaskLog(taskId) {
    try {
      const res = await fetch(`/api/tasks/${taskId}/log`);
      if (!res.ok) return '';
      return res.text();
    } catch { return ''; }
  }

  /**
   * Lightweight log streamer for callers that want a callback per chunk.
   * Backed by SSE: registers a window listener for `taskLog` events that
   * match the requested taskId.
   */
  streamTaskLog(taskId, onNewLines, onError) {
    let stopped = false;
    const handler = (event) => {
      if (stopped) return;
      const detail = event.detail || {};
      if (detail.id !== taskId || typeof detail.chunk !== 'string') return;
      try {
        const lines = detail.chunk.split('\n').filter(l => l.length > 0);
        if (lines.length > 0) onNewLines(lines);
      } catch (err) { onError && onError(err); }
    };
    window.addEventListener('taskLog', handler);
    return {
      stop: () => { stopped = true; window.removeEventListener('taskLog', handler); },
      isStreaming: () => !stopped
    };
  }

  /**
   * ANSI -> HTML colour parser (kept from the previous TaskManager so log
   * rendering doesn't lose formatting).
   */
  parseAnsiColors(text) {
    const ansiRegex = /\x1b\[[0-9;]*m/g;
    const colorMap = {
      '30':'black','31':'red','32':'green','33':'yellow','34':'blue','35':'magenta','36':'cyan','37':'white',
      '90':'darkgray','91':'lightred','92':'lightgreen','93':'lightyellow','94':'lightblue','95':'lightmagenta','96':'lightcyan','97':'lightwhite'
    };
    const bgColorMap = {
      '40':'black','41':'red','42':'green','43':'yellow','44':'blue','45':'magenta','46':'cyan','47':'white',
      '100':'darkgray','101':'lightred','102':'lightgreen','103':'lightyellow','104':'lightblue','105':'lightmagenta','106':'lightcyan','107':'lightwhite'
    };
    let result = '';
    let cursor = 0;
    let m;
    while ((m = ansiRegex.exec(text)) !== null) {
      result += text.substring(cursor, m.index);
      const codes = m[0].slice(2, -1).split(';');
      const styles = [];
      let reset = false;
      for (const code of codes) {
        if (code === '0' || code === '') reset = true;
        else if (code === '1') styles.push('font-weight: bold');
        else if (code === '4') styles.push('text-decoration: underline');
        else if (colorMap[code]) styles.push(`color: ${colorMap[code]}`);
        else if (bgColorMap[code]) styles.push(`background-color: ${bgColorMap[code]}`);
      }
      if (styles.length) result += `<span style="${styles.join('; ')}">`;
      else if (reset) result += '</span>';
      cursor = m.index + m[0].length;
    }
    result += text.substring(cursor);
    const open = (result.match(/<span/g) || []).length;
    const close = (result.match(/<\/span>/g) || []).length;
    result += '</span>'.repeat(Math.max(0, open - close));
    return result;
  }

  generateTaskId() {
    return `task_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
  }
}

if (typeof module !== 'undefined' && module.exports) {
  module.exports = TaskManager;
} else {
  window.TaskManager = TaskManager;
}
