const DIRECT_BUILD_COMMANDS = new Set(["vite", "vitest", "astro", "tsc", "playwright"]);

export function parseAllowlist(content = "") {
  const out = new Set();
  for (const line of String(content).split(/\r?\n/)) {
    const match = line.trim().match(/^(\d+)/);
    if (match) out.add(Number(match[1]));
  }
  return out;
}

export function parsePsTable(psText = "") {
  const rows = [];
  for (const line of String(psText).split(/\r?\n/)) {
    if (!line.trim() || /^\s*PID\s+PPID\s+PGID\s+/i.test(line)) continue;
    const match = line.trim().match(
      /^(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+([\d.]+)\s+(\d+)\s+(\S+)\s+(.*)$/,
    );
    if (!match) continue;
    rows.push({
      pid: Number(match[1]),
      ppid: Number(match[2]),
      pgid: Number(match[3]),
      etimes: Number(match[4]),
      pcpu: Number(match[5]),
      rss: Number(match[6]),
      comm: match[7],
      args: match[8] || "",
    });
  }
  return rows;
}

export function classifyRows(rows, options = {}) {
  const minAgeSeconds = options.minAgeSeconds ?? 7200;
  const allowlist = options.allowlist ?? new Set();
  const byPid = new Map(rows.map((row) => [row.pid, row]));

  return rows.map((row) => {
    const ancestors = ancestry(row, byPid);
    const hasClaudeAncestor = ancestors.some(isClaude);
    const hasAgentAncestor = ancestors.some((item) => isClaude(item) || isCodex(item));
    const allowlisted = allowlist.has(row.pid);
    const role = classifyRole(row, ancestors, hasClaudeAncestor, hasAgentAncestor);
    let suspect_reason = "";

    if (!allowlisted && role === "build-child" && row.etimes > minAgeSeconds) {
      suspect_reason = "build-child-age>2h";
    }

    return {
      pid: row.pid,
      ppid: row.ppid,
      pgid: row.pgid,
      age_seconds: row.etimes,
      cpu: row.pcpu,
      rss_mb: Math.round(row.rss / 1024),
      comm: row.comm,
      command: row.args,
      role,
      allowlisted,
      suspect: suspect_reason !== "",
      suspect_reason,
    };
  });
}

export function selectReaperCandidates(classifiedRows) {
  return classifiedRows.filter((row) => row.suspect);
}

function ancestry(row, byPid) {
  const out = [];
  const seen = new Set([row.pid]);
  let parent = byPid.get(row.ppid);
  while (parent && !seen.has(parent.pid)) {
    out.push(parent);
    seen.add(parent.pid);
    parent = byPid.get(parent.ppid);
  }
  return out;
}

function classifyRole(row, ancestors, hasClaudeAncestor, hasAgentAncestor) {
  if (isClaude(row)) return "claude-session";
  if (isCodex(row) && hasClaudeAncestor) return "codex-child";
  if (isBuildTool(row, ancestors) && hasAgentAncestor) return "build-child";
  if (isDesktop(row)) return "desktop";
  return "other";
}

function isClaude(row) {
  return row.comm === "claude" || /(^|[/\s])claude(\s|$)/.test(row.args);
}

function isCodex(row) {
  return row.comm === "codex" || /(^|[/\s])codex(\s|$)/.test(row.args);
}

function isDesktop(row) {
  return /^(cinnamon|gnome-shell|gnome-terminal-|firefox|firefox-esr|Xorg|pipewire|wireplumber)$/.test(
    row.comm,
  );
}

function isBuildTool(row, ancestors) {
  if (DIRECT_BUILD_COMMANDS.has(row.comm)) return true;
  if (/\b(cargo|go)\s+(test|build|check|run)\b/.test(row.args)) return true;
  if (/\b(pnpm|npm|yarn)\s+(run\s+)?(build|test|vitest|astro|vite|tsc|playwright)\b/.test(row.args)) {
    return true;
  }
  if (/\bnpx\s+(vitest|astro|vite|tsc|playwright)\b/.test(row.args)) return true;
  if (/\bbun\s+(run|x)\s+.*\b(build|test|vitest|astro|vite|tsc|playwright)\b/.test(row.args)) {
    return true;
  }
  if (/node .*[/\\](vitest|astro|vite|tsc|playwright)[^ ]* .*\b(run|build|test)\b/.test(row.args)) {
    return true;
  }
  if (row.comm === "esbuild" || /\besbuild\b.*--service=/.test(row.args)) {
    return ancestors.some((ancestor) => isExplicitBuildGate(ancestor.args));
  }
  return false;
}

function isExplicitBuildGate(args) {
  return (
    /\b(cargo|go)\s+(test|build|check|run)\b/.test(args) ||
    /\b(pnpm|npm|yarn)\s+(run\s+)?(build|test|vitest|astro|vite|tsc|playwright)\b/.test(args) ||
    /\bnpx\s+(vitest|astro|vite|tsc|playwright)\b/.test(args) ||
    /\bbun\s+(run|x)\s+.*\b(build|test|vitest|astro|vite|tsc|playwright)\b/.test(args) ||
    /node .*[/\\](vitest|astro|vite|tsc|playwright)[^ ]* .*\b(run|build|test)\b/.test(args)
  );
}
