import { addTask, getTaskById, getTasks, setTaskCanceled, setTaskComplete, setTaskFailed, setTaskRunning } from './db.mjs';

export function createTaskScheduler({
  maxUserTaskConcurrency = 2,
  onTaskUpdate = () => {},
  isAutonomyPaused = () => false
} = {}) {
  const pending = [];
  const running = new Map();
  const byId = new Map();

  let drainScheduled = false;

  function emit() {
    onTaskUpdate(getSnapshot());
  }

  function getCounts() {
    let runningUser = 0;
    let runningAutonomy = 0;
    let userCreatesFiles = 0;
    let pendingUserCreatesFiles = 0;
    for (const task of running.values()) {
      if (task.source === 'autonomy') runningAutonomy++;
      else runningUser++;
      if (task.source !== 'autonomy' && task.createsFiles) userCreatesFiles++;
    }
    for (const task of pending) {
      if (task.source !== 'autonomy' && task.createsFiles) pendingUserCreatesFiles++;
    }
    return { runningUser, runningAutonomy, userCreatesFiles, pendingUserCreatesFiles };
  }

  function globalUserFileLockActive() {
    const counts = getCounts();
    return counts.userCreatesFiles > 0 || counts.pendingUserCreatesFiles > 0;
  }

  function canRun(task) {
    const counts = getCounts();
    if (task.source === 'autonomy') {
      if (isAutonomyPaused()) return false;
      if (counts.runningAutonomy >= 1) return false;
      if (task.createsFiles && globalUserFileLockActive()) return false;
      return true;
    }

    if (counts.runningUser >= maxUserTaskConcurrency) return false;
    return true;
  }

  function scheduleDrain() {
    if (drainScheduled) return;
    drainScheduled = true;
    setImmediate(() => {
      drainScheduled = false;
      void drain();
    });
  }

  async function runTask(task) {
    setTaskRunning(task.id);
    task.state = 'running';
    running.set(task.id, task);
    emit();

    try {
      await task.execute();
      if (task.cancelRequested) {
        setTaskCanceled(task.id);
        task.state = 'canceled';
      } else {
        setTaskComplete(task.id);
        task.state = 'completed';
      }
    } catch (err) {
      setTaskFailed(task.id, err?.message || 'Task failed');
      task.state = 'failed';
      task.error = err?.message || 'Task failed';
    } finally {
      running.delete(task.id);
      emit();
      scheduleDrain();
    }
  }

  async function drain() {
    let startedAny = false;
    for (let i = 0; i < pending.length; i++) {
      const task = pending[i];
      if (!canRun(task)) continue;
      pending.splice(i, 1);
      i--;
      startedAny = true;
      void runTask(task);
    }
    if (startedAny) emit();
  }

  function enqueue({ source, kind, conversationId = null, skill = null, args = null, createsFiles = false, execute }) {
    if (typeof execute !== 'function') throw new Error('Task requires execute()');

    // Deduplicate: if the same skill + conversationId combo is already
    // queued or running, skip to prevent duplicate execution.
    if (skill && conversationId) {
      const isDuplicate = pending.some(t => t.skill === skill && t.conversationId === conversationId)
        || [...running].some(id => { const t = byId.get(id); return t && t.skill === skill && t.conversationId === conversationId; });
      if (isDuplicate) {
        console.log(`[task-scheduler] Skipping duplicate: ${skill} for conversation ${conversationId}`);
        return -1;
      }
    }

    const id = addTask({ source, kind, conversationId, skill, args, createsFiles });
    const task = {
      id,
      source,
      kind,
      conversationId,
      skill,
      args,
      createsFiles: !!createsFiles,
      execute,
      state: 'queued',
      cancelRequested: false,
      error: null
    };
    pending.push(task);
    byId.set(id, task);
    emit();
    scheduleDrain();
    return id;
  }

  function cancel(taskId) {
    const task = byId.get(taskId);
    if (!task) return false;

    const queuedIdx = pending.findIndex(t => t.id === taskId);
    if (queuedIdx >= 0) {
      pending.splice(queuedIdx, 1);
      setTaskCanceled(taskId);
      task.state = 'canceled';
      emit();
      return true;
    }

    if (running.has(taskId)) {
      task.cancelRequested = true;
      return true;
    }
    return false;
  }

  function retry(taskId, executeFactory) {
    const row = getTaskById(taskId);
    if (!row) return null;
    let args = null;
    try { args = row.args_json ? JSON.parse(row.args_json) : null; } catch {}
    const execute = executeFactory?.(row);
    if (typeof execute !== 'function') return null;
    return enqueue({
      source: row.source,
      kind: row.kind,
      conversationId: row.conversation_id,
      skill: row.skill,
      args,
      createsFiles: !!row.creates_files,
      execute
    });
  }

  function getSnapshot() {
    const counts = getCounts();
    return {
      queued: pending.length,
      running: running.size,
      queued_user: pending.filter(t => t.source !== 'autonomy').length,
      queued_autonomy: pending.filter(t => t.source === 'autonomy').length,
      running_user: counts.runningUser,
      running_autonomy: counts.runningAutonomy,
      user_file_lock_active: globalUserFileLockActive()
    };
  }

  return {
    enqueue,
    cancel,
    retry,
    getSnapshot,
    getTaskRows: (limit = 100) => getTasks({ limit }),
    scheduleDrain
  };
}
