import { writeFile, readFile, mkdir } from "node:fs/promises";
import { join } from "node:path";
import { homedir } from "node:os";
import { randomUUID } from "node:crypto";

// ─── Types ─────────────────────────────────────────────────────────────────

export type TaskStatus = "pending" | "in_progress" | "complete" | "failed" | "timed_out";

export interface Task {
  id: string;
  from_agent: string;
  to_agent: string;
  task: string;
  context?: string;
  conversation_id?: string;
  status: TaskStatus;
  result?: string;
  created_at: number;
  updated_at: number;
  completed_at?: number;
}

// ─── Constants ──────────────────────────────────────────────────────────────

const TASKS_DIR = join(homedir(), ".claude", "tasks");
const TASKS_FILE = join(TASKS_DIR, "tasks.json");

const TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
const DEBOUNCE_MS = 1000;
const SWEEP_INTERVAL_MS = 60 * 1000; // 1 minute

// ─── TaskLedger ─────────────────────────────────────────────────────────────

type BroadcastFn = (task: Task) => void;

export class TaskLedger {
  private tasks = new Map<string, Task>();
  private persistTimer: ReturnType<typeof setTimeout> | null = null;
  private sweepInterval: ReturnType<typeof setInterval>;
  private broadcast: BroadcastFn;

  constructor(broadcast: BroadcastFn) {
    this.broadcast = broadcast;
    this.sweepInterval = setInterval(() => this.sweep(), SWEEP_INTERVAL_MS);
  }

  async load(): Promise<void> {
    try {
      const data = await readFile(TASKS_FILE, "utf-8");
      const parsed: Record<string, Task> = JSON.parse(data);
      for (const [id, task] of Object.entries(parsed)) {
        this.tasks.set(id, task);
      }
      process.stderr.write(`[task-ledger] Loaded ${this.tasks.size} tasks\n`);
    } catch {
      // No existing data or parse error — start fresh
    }
    // Run initial sweep to timeout any stale tasks from a previous session
    this.sweep();
  }

  /** Dispatch a new task. Returns the created task with assigned id. */
  dispatch(
    fromAgent: string,
    toAgent: string,
    task: string,
    context?: string,
    conversationId?: string
  ): Task {
    const id = randomUUID();
    const now = Date.now();
    const t: Task = {
      id,
      from_agent: fromAgent,
      to_agent: toAgent,
      task,
      context,
      conversation_id: conversationId,
      status: "pending",
      created_at: now,
      updated_at: now,
    };
    this.tasks.set(id, t);
    this.schedulePersist();
    this.broadcast(t);
    process.stderr.write(`[task-ledger] Dispatched task ${id}: ${fromAgent} → ${toAgent}\n`);
    return t;
  }

  /** Mark a task as in_progress. Only valid from pending state. */
  setInProgress(taskId: string): Task | null {
    const t = this.tasks.get(taskId);
    if (!t) return null;
    if (t.status !== "pending") return t;
    t.status = "in_progress";
    t.updated_at = Date.now();
    this.schedulePersist();
    this.broadcast(t);
    return t;
  }

  /** Complete or fail a task. Returns null if task not found. */
  complete(taskId: string, result: string, status: "complete" | "failed"): Task | null {
    const t = this.tasks.get(taskId);
    if (!t) return null;
    // Ignore if already in a terminal state
    if (t.status === "complete" || t.status === "failed" || t.status === "timed_out") {
      return t;
    }
    const now = Date.now();
    t.status = status;
    t.result = result;
    t.updated_at = now;
    t.completed_at = now;
    this.schedulePersist();
    this.broadcast(t);
    process.stderr.write(`[task-ledger] Task ${taskId} ${status}\n`);
    return t;
  }

  /** Look up a task by id. Returns null if not found. */
  check(taskId: string): Task | null {
    return this.tasks.get(taskId) ?? null;
  }

  /** List all tasks, newest first. */
  list(): Task[] {
    return Array.from(this.tasks.values()).sort((a, b) => b.created_at - a.created_at);
  }

  /** Sweep for timed-out tasks. Called automatically every minute. */
  private sweep(): void {
    const now = Date.now();
    let changed = false;
    for (const t of this.tasks.values()) {
      if (
        (t.status === "pending" || t.status === "in_progress") &&
        now - t.created_at > TIMEOUT_MS
      ) {
        t.status = "timed_out";
        t.updated_at = now;
        t.completed_at = now;
        this.broadcast(t);
        changed = true;
        process.stderr.write(`[task-ledger] Task ${t.id} timed out (${t.from_agent} → ${t.to_agent})\n`);
      }
    }
    if (changed) this.schedulePersist();
  }

  private schedulePersist(): void {
    if (this.persistTimer) clearTimeout(this.persistTimer);
    this.persistTimer = setTimeout(() => this.persist(), DEBOUNCE_MS);
  }

  private async persist(): Promise<void> {
    try {
      await mkdir(TASKS_DIR, { recursive: true });
      const data: Record<string, Task> = {};
      for (const [id, task] of this.tasks) {
        data[id] = task;
      }
      await writeFile(TASKS_FILE, JSON.stringify(data, null, 2));
    } catch (err) {
      process.stderr.write(`[task-ledger] persist error: ${err}\n`);
    }
  }

  destroy(): void {
    clearInterval(this.sweepInterval);
    if (this.persistTimer) clearTimeout(this.persistTimer);
  }
}
