// Live data sources. Each fetcher is a small async function the tabs call
// via React effects. They all degrade gracefully — if something can't be
// read, we surface a clear error rather than throwing.

import { readdir, stat, readFile } from "node:fs/promises";
import { existsSync } from "node:fs";
import { join } from "node:path";

// --- Services (supervisord) -----------------------------------------------

export type ServiceStatus = "RUNNING" | "STOPPED" | "FATAL" | "STARTING" | "UNKNOWN";

export interface Service {
  name: string;
  status: ServiceStatus;
  uptime: string;
  pid: string;
  raw: string;
}

interface ParsedLine {
  name: string;
  status: string;
  uptime: string;
  pid: string;
  raw: string;
}

function parseSupervisorLine(line: string): ParsedLine | null {
  // Format: "<name>     <status>   pid <pid>, uptime <uptime>"
  // Or:     "<name>     <status>   Exited too quickly..."
  const m = line.match(/^(\S+)\s+(\S+)\s+(.*)$/);
  if (!m) return null;
  const [, name, statusRaw, rest] = m;
  const status = statusRaw as ServiceStatus;
  let pid = "—";
  let uptime = "—";
  const pidMatch = rest.match(/pid (\d+), uptime ([^,]+(?:, [^,]+)*)/);
  if (pidMatch) {
    pid = pidMatch[1];
    uptime = pidMatch[2];
  } else if (rest.startsWith("Exited")) {
    pid = "—";
    uptime = rest;
  } else {
    uptime = rest;
  }
  return { name, status, uptime, pid, raw: line };
}

export async function fetchServices(): Promise<{ services: Service[]; error?: string }> {
  try {
    const proc = Bun.spawn(
      ["supervisorctl", "-c", "/etc/zo/supervisord-user.conf", "status"],
      { stdout: "pipe", stderr: "pipe" },
    );
    const [out, err] = await Promise.all([
      new Response(proc.stdout).text(),
      new Response(proc.stderr).text(),
    ]);
    const code = await proc.exited;
    if (code !== 0 && !out.trim()) {
      return { services: [], error: err.trim() || `supervisorctl exit ${code}` };
    }
    const valid: ServiceStatus[] = ["RUNNING", "STOPPED", "FATAL", "STARTING", "UNKNOWN"];
    const services: Service[] = out
      .split("\n")
      .map((l) => l.trim())
      .filter(Boolean)
      .map(parseSupervisorLine)
      .filter((s): s is ParsedLine => s !== null)
      .map((p) => ({
        ...p,
        status: (valid.includes(p.status as ServiceStatus) ? p.status : "UNKNOWN") as ServiceStatus,
      }));
    return { services };
  } catch (e) {
    return { services: [], error: String(e) };
  }
}

// --- Automations / Agents -------------------------------------------------

export interface Automation {
  id: string;
  title: string;
  rrule: string;
  delivery: string;
  model: string;
  active: boolean;
  instruction: string;
  source: string; // which json file
}

interface AgentFile {
  agents?: Automation[];
  automations?: Automation[];
}

function normalizeAutomation(a: any, source: string): Automation | null {
  if (!a || typeof a !== "object") return null;
  const id = String(a.id ?? a._id ?? a.uuid ?? crypto.randomUUID());
  const title =
    typeof a.title === "string" && a.title
      ? a.title
      : typeof a.name === "string" && a.name
        ? a.name
        : id;
  const rrule = typeof a.rrule === "string" ? a.rrule : "—";
  const delivery = typeof a.delivery_method === "string" ? a.delivery_method : (a.delivery ?? "—");
  const model = typeof a.model === "string" ? a.model : (a.model_name ?? "—");
  const active = typeof a.active === "boolean" ? a.active : true;
  const instruction = typeof a.instruction === "string" ? a.instruction : "";
  return { id, title, rrule, delivery, model, active, instruction, source };
}

function pickAutomations(obj: AgentFile, source: string): Automation[] {
  const list = obj.agents ?? obj.automations ?? [];
  return list.map((a) => normalizeAutomation(a, source)).filter((a): a is Automation => a !== null);
}

async function readJsonSafe<T>(path: string): Promise<T | null> {
  try {
    if (!existsSync(path)) return null;
    const text = await readFile(path, "utf-8");
    return JSON.parse(text) as T;
  } catch {
    return null;
  }
}

export async function fetchAutomations(): Promise<{ automations: Automation[]; error?: string }> {
  const candidates = [
    "/home/workspace/zo-agents-backup/agents.json",
    "/home/workspace/Archive/zo-agents-backup/agents.json",
    "/home/workspace/Archive/zo-agents-backup/agents-full-export.json",
    "/home/workspace/Archive/zo-agents-backup/automations-full.json",
  ];
  const seen = new Set<string>();
  const merged: Automation[] = [];
  let lastError: string | undefined;

  for (const path of candidates) {
    const data = await readJsonSafe<AgentFile | Automation[]>(path);
    if (!data) continue;
    try {
      if (Array.isArray(data)) {
        for (const a of data) {
          const norm = normalizeAutomation(a, path);
          if (!norm) continue;
          const key = `${norm.id}::${norm.title}`;
          if (!seen.has(key)) {
            seen.add(key);
            merged.push(norm);
          }
        }
      } else {
        for (const a of pickAutomations(data, path)) {
          const key = `${a.id}::${a.title}`;
          if (!seen.has(key)) {
            seen.add(key);
            merged.push(a);
          }
        }
      }
    } catch (e) {
      lastError = String(e);
    }
  }
  return { automations: merged, error: lastError };
}

// --- Files -----------------------------------------------------------------

export interface FileEntry {
  name: string;
  path: string;
  isDir: boolean;
  size: number;
  mtime: Date;
}

const WORKSPACE = "/home/workspace";
const HIDDEN = new Set([".git", "node_modules", ".pytest_cache", "Trash", ".cache", ".bun", ".npm", ".next", ".venv", "__pycache__"]);

export async function fetchDir(rel: string = ""): Promise<{ entries: FileEntry[]; error?: string; path: string }> {
  const abs = join(WORKSPACE, rel);
  try {
    const items = await readdir(abs, { withFileTypes: true });
    const entries: FileEntry[] = [];
    for (const item of items) {
      if (HIDDEN.has(item.name)) continue;
      if (item.name.startsWith(".") && item.name !== ".opencode" && item.name !== ".swamp" && item.name !== ".agents") continue;
      const full = join(abs, item.name);
      try {
        const s = await stat(full);
        entries.push({
          name: item.name,
          path: rel ? `${rel}/${item.name}` : item.name,
          isDir: item.isDirectory(),
          size: s.size,
          mtime: s.mtime,
        });
      } catch {
        // skip unreadable
      }
    }
    entries.sort((a, b) => {
      if (a.isDir !== b.isDir) return a.isDir ? -1 : 1;
      return a.name.localeCompare(b.name);
    });
    return { entries, path: rel };
  } catch (e) {
    return { entries: [], error: String(e), path: rel };
  }
}

// --- Computer stats --------------------------------------------------------

export interface ComputerStats {
  loadavg: [number, number, number];
  memTotal: number;
  memFree: number;
  memAvail: number;
  uptime: number;
  hostname: string;
  diskUsage: { used: number; total: number } | null;
}

function readMeminfo(): { total: number; free: number; avail: number } {
  try {
    const text = require("node:fs").readFileSync("/proc/meminfo", "utf-8") as string;
    const get = (k: string) => {
      const m = text.match(new RegExp(`${k}:\\s+(\\d+)`));
      return m ? parseInt(m[1], 10) * 1024 : 0;
    };
    return { total: get("MemTotal"), free: get("MemFree"), avail: get("MemAvailable") };
  } catch {
    return { total: 0, free: 0, avail: 0 };
  }
}

function readUptime(): number {
  try {
    const text = require("node:fs").readFileSync("/proc/uptime", "utf-8") as string;
    return parseFloat(text.split(" ")[0]);
  } catch {
    return 0;
  }
}

function readLoadavg(): [number, number, number] {
  try {
    const text = require("node:fs").readFileSync("/proc/loadavg", "utf-8") as string;
    const [, l1, l5, l15] = text.trim().split(/\s+/);
    return [parseFloat(l1), parseFloat(l5), parseFloat(l15)];
  } catch {
    return [0, 0, 0];
  }
}

export async function fetchStats(): Promise<ComputerStats> {
  const mem = readMeminfo();
  const os = await import("node:os");
  return {
    loadavg: readLoadavg(),
    memTotal: mem.total,
    memFree: mem.free,
    memAvail: mem.avail,
    uptime: readUptime(),
    hostname: os.hostname(),
    diskUsage: null, // could add df parsing later
  };
}

// --- Log tail (single service) --------------------------------------------

export async function tailLog(name: string, lines: number = 40): Promise<string[]> {
  const path = `/dev/shm/${name}.log`;
  try {
    if (!existsSync(path)) return [];
    const text = await readFile(path, "utf-8");
    return text.split("\n").slice(-lines);
  } catch {
    return [];
  }
}
