#!/usr/bin/env node
import { createServer } from "node:http";
import {
  existsSync,
  readdirSync,
  readFileSync,
  statSync,
} from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";

const scriptDir = path.dirname(fileURLToPath(import.meta.url));
const workspace = path.resolve(scriptDir, "../..");
const agentsDir = path.join(workspace, ".agents");
const runsDir = path.join(agentsDir, "runs");
const checkpointPath = path.join(agentsDir, "checkpoint.json");
const staticDir = path.join(agentsDir, "observability");
const port = parsePort(process.argv) || 4317;

const server = createServer((req, res) => {
  const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);

  if (url.pathname === "/api/status") {
    const tail = parseTail(url.searchParams.get("tail"));
    sendJson(res, buildStatus(tail));
    return;
  }

  serveStatic(res, url.pathname);
});

server.listen(port, "127.0.0.1", () => {
  console.log(`Agent observability dashboard: http://127.0.0.1:${port}`);
});

server.on("error", (error) => {
  console.error(`Could not start observability dashboard on 127.0.0.1:${port}`);
  console.error(error.message);
  process.exit(1);
});

function parsePort(argv) {
  const index = argv.findIndex((arg) => arg === "--port" || arg === "-p");
  if (index === -1) return null;
  const value = Number(argv[index + 1]);
  if (!Number.isInteger(value) || value < 1 || value > 65535) return null;
  return value;
}

function parseTail(value) {
  const parsed = Number(value);
  if (!Number.isInteger(parsed) || parsed < 0) return 80;
  return Math.min(parsed, 500);
}

function buildStatus(tailLines) {
  const checkpoint = readJsonIfExists(checkpointPath);
  const runs = listRuns();
  const activeRunId = checkpoint?.active_run_id || findLatestRunId(runs);
  const activeRun = runs.find((run) => run.id === activeRunId) || null;

  return {
    generated_at: new Date().toISOString(),
    workspace,
    checkpoint,
    summary: {
      run_count: runs.length,
      active_run_id: activeRunId,
      current_phase: checkpoint?.current_phase || null,
      active_branch: checkpoint?.active_branch || null,
      blocker_count: Array.isArray(checkpoint?.blockers) ? checkpoint.blockers.length : 0,
      next_action: checkpoint?.next_action || null,
    },
    runs,
    active_progress: activeRun ? readProgress(activeRun, tailLines) : null,
  };
}

function listRuns() {
  if (!existsSync(runsDir)) return [];

  return readdirSync(runsDir)
    .map((name) => path.join(runsDir, name))
    .filter((runPath) => statSync(runPath).isDirectory())
    .map((runPath) => {
      const id = path.basename(runPath);
      const state = readJsonIfExists(path.join(runPath, "state.json"));
      const agents = normalizeAgents(readJsonIfExists(path.join(runPath, "agents.json")));
      return {
        id,
        path: path.relative(workspace, runPath),
        state,
        agents: agents.map((agent) => ({
          ...agent,
          lease_expired: isExpired(agent.lease_expires_at),
        })),
        mtime_ms: statSync(runPath).mtimeMs,
      };
    })
    .sort((a, b) => b.mtime_ms - a.mtime_ms);
}

function normalizeAgents(value) {
  if (!value) return [];
  if (Array.isArray(value)) return value;
  if (Array.isArray(value.agents)) return value.agents;
  return Object.entries(value).map(([role, agent]) => ({
    role,
    ...(typeof agent === "object" && agent ? agent : { status: String(agent) }),
  }));
}

function findLatestRunId(runs) {
  if (runs.length === 0) return null;
  return runs.reduce((latest, run) => (run.mtime_ms > latest.mtime_ms ? run : latest)).id;
}

function readProgress(run, tailLines) {
  const runPath = path.join(workspace, run.path);
  const candidateNames = [
    "progress.log",
    "implementation-log.md",
    "validation.md",
    "opus-proposal.md",
    "snarky-review.md",
  ];
  const filePath = candidateNames
    .map((name) => path.join(runPath, name))
    .find((candidate) => existsSync(candidate) && statSync(candidate).isFile());

  if (!filePath) {
    return {
      run_id: run.id,
      path: null,
      lines: [],
    };
  }

  const lines = readFileSync(filePath, "utf8").split(/\r?\n/);
  if (lines.length > 0 && lines[lines.length - 1] === "") {
    lines.pop();
  }

  return {
    run_id: run.id,
    path: path.relative(workspace, filePath),
    lines: lines.slice(Math.max(0, lines.length - tailLines)),
  };
}

function isExpired(value) {
  if (!value) return false;
  const time = Date.parse(value);
  if (!Number.isFinite(time)) return false;
  return time < Date.now();
}

function readJsonIfExists(filePath) {
  if (!existsSync(filePath)) return null;
  try {
    return JSON.parse(readFileSync(filePath, "utf8"));
  } catch (error) {
    return {
      parse_error: error.message,
    };
  }
}

function sendJson(res, value) {
  const body = JSON.stringify(value);
  res.writeHead(200, {
    "Content-Type": "application/json",
    "Content-Length": Buffer.byteLength(body),
  });
  res.end(body);
}

function serveStatic(res, pathname) {
  const relative = pathname === "/" ? "index.html" : pathname.slice(1);
  const filePath = path.resolve(staticDir, relative);

  if (filePath !== staticDir && !filePath.startsWith(`${staticDir}${path.sep}`)) {
    sendNotFound(res);
    return;
  }

  if (!existsSync(filePath) || !statSync(filePath).isFile()) {
    sendNotFound(res);
    return;
  }

  const body = readFileSync(filePath);
  res.writeHead(200, {
    "Content-Type": contentType(filePath),
    "Content-Length": body.length,
  });
  res.end(body);
}

function sendNotFound(res) {
  res.writeHead(404, { "Content-Type": "text/plain" });
  res.end("Not found");
}

function contentType(filePath) {
  const extension = path.extname(filePath);
  switch (extension) {
    case ".html":
      return "text/html; charset=utf-8";
    case ".css":
      return "text/css; charset=utf-8";
    case ".js":
      return "text/javascript; charset=utf-8";
    case ".json":
      return "application/json";
    default:
      return "application/octet-stream";
  }
}
